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.