Update Your Gems: Security Vulnerability in Cocaine

Jon Yurek

We were recently informed of a security vulnerability in cocaine, our gem for running shell commands. There is lots of work done in cocaine in order to make sure that nasty things like someone passing in rm -rf / into a command won’t actually do anything to your systems. But Holger Just was able to point out a potentially exploitable vulnerability that would let you sneak in dangerous commands if you know how the command line was being built.

The details of the problem are contained in CVE-2013-4457, but I can explain better here.

Interpolation

The way cocaine works is that it turns user-supplied inputs into shell-safe strings before interpolating them into your command. You give it a hash, and it replaces the keys in the string with the shell-safe values of those keys, like so:

line = Cocaine::CommandLine.new("echo", ":foo, :bar")
line.command(foo: "Hello", bar: "world") # => "echo 'Hello', 'world'"

Notice that each of the interpolated values are shell quoted. This prevents things like trying to slip an rm -rf / in there:

line = Cocaine::CommandLine.new("cat", ":file")
line.command(:file => "ohyeah?'`rm -rf /`.ha!") # => "cat 'ohyeah?'\\''`rm -rf /`.ha!'"

This is a safe command to run, even if it looks dangerous at first glance.

Recursive Interpolation

The problem comes about due to the way the values are interpolated. cocaine loops over the keys and replaces each one with its value. The part where this breaks is when it does a gsub during each iteration. Take the following example:

line = Cocaine::CommandLine.new("echo", ":foo, :bar")
line.command(foo: ":bar", bar: "`cat /etc/passwd`") # => "echo ''`cat /etc/passwd`'', '`cat /etc/passwd`'"

Assuming we have Ruby 1.9’s ordered hashes, the :foo key is interpolated first, replacing it with :bar, and the command line will look like cat ':bar' :bar. When the :bar key is interpolated, the first argument becomes double-quoted, resulting in an unquoted subshell command in your command line.

(Incidentally, this exploit is still possible on Ruby 1.8, but less likely as the hash keys aren’t guaranteed to be returned in any specific order.)

The Fix

This was brought to our attention Tuesday by Holger Just, who also helpfully provided a fix as well. The solution was to stop interpolating once for each key in your hash, and only interpolate once ever.

String#gsub has a wonderful ability to take a block which will be executed each time it finds a match, and it will use the result of that match as the replacement. After this patch was applied, the resulting code above looks like this:

line = Cocaine::CommandLine.new("echo", ":foo, :bar")
line.command(foo: ":bar", bar: "`cat /etc/passwd`") # => "echo ':bar', '`cat /etc/passwd`'"

And this is, again, safe to run. You can see the fix on GitHub.

What you need to know

If you’re using a cocaine version between 0.4.0 and 0.5.2, you should upgrade to 0.5.3. If you’re using a version in the 0.3.x branch, you don’t have to upgrade.

Because Paperclip 3.x depends on Cocaine, you should upgrade to the latest version (3.5.2 as of this writing). If you can’t, you should at least bundle update cocaine. If you’re still using Paperclip 2.7.x, then you won’t need to update for this one, as it uses an older version of Cocaine.

Paperclip is probably not exploitable to this vulnerability, though, as by default it only accepts one user-supplied input into its shell commands. But better safe than sorry.