With a bash release which has been patched for shellshock
$ bash --version
GNU bash, version 3.2.52(1)-release (x86_64-apple-darwin12)
Copyright (C) 2007 Free Software Foundation, Inc.
$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `x'
this is a test
another similar exploit still works and has been assigned CVE-2014-7169
$ env X='() { (a)=>\' bash -c "echo date"; cat echo
bash: X: line 1: syntax error near unexpected token `='
bash: X: line 1: `'
bash: error importing function definition for `X'
Thu Sep 25 12:47:22 EDT 2014
$ ls echo
echo
Looking for a breakdown of this as well.
The bug
CVE-2014-7169 is a bug in bash's parser. Bash's parser uses a variable eol_ungetc_lookahead
to ungetc characters across lines. That variable wasn't being properly reset from the reset_parser
function, which is called e.g. on some syntax errors. Using that bug, it's possible to inject a character into the start of the next bash input line.
So the test code forces a syntax error, using either (a)=
or function a a
, adds the redirection character to prepend to the next line >
, and adds a line continuation \
, which leads to either version of the test code:
() { (a)=>\
() { function a a>\
When bash is executed, it processes variables from the environment, finds that variable X
is a exported function, and evaluates it to import the function. But the evaluation fails with a parse error, leaving the >
character in the eol_ungetc_lookahead
variable. Then, when parsing the command argument echo date
, it prepends the >
character, leading to >echo date
, which runs date
redirected to a file named echo
.
Its relation to the previous bug
The above bug is obviously very different to the original shellshock bug. There are actually several problems:
- Bash evaluates completely a variable that looks like an exported function (starts with the four characters
() {
). CVE-2014-6271.
- Under some conditions, it is possible to inject a character into an ungetc variable, that will be prepended to the next input line. CVE-2014-7169.
- Bash allows every environment variable to be treated like an exported function, so long as it starts with the four characters
() {
. CVE-2014-6271, CVE-2014-7169, all the other CVEs where a bug is triggered in bash's parser.
- There is a limited stack for here-doc redirection, and there is no check for overflow. CVE-2014-7186, which leads to memory corruption, and can probably be leveraged for arbitrary code execution.
- There is a limited stack for nested control structures (
select
/for
/while
), with checks for overflow. That stack is still corrupted. CVE-2014-7187.
The fixes
- The first patch restricts bash to evaluating a single function definition in each variable that looks like a exported function.
- The second patch properly resets
eol_ungetc_lookahead
on reset_parser
.
- The third patch changes how functions are exported: now they are exported in variables named
BASH_FUNC_functionname%%
.
Attack surface
The big problem here has been that every environment variable could be used as a vector for attack. Typically, attackers cannot control arbitrary environment variables, otherwise there are already other known attacks (think of LD_PRELOAD
, PATH
, IFS
, ...).
sudo
is not affected because it strips exported bash functions from the environment, as mentioned by Gilles on security.SE.
ssh
is affected. Typical sshd installations only allow a limited set of environment variables to be exported as configured in AcceptEnv in sshd_config
, e.g: LANG
and LC_*
. Even with this aggressive whitelisting approach, in shellshock any variable could be an attack vector.
Not only was every environment variable a potential attack vector, they exposed a >6000 lines parser.
Lessons relearned
system
, popen
, and others are potentially dangerous. Not only should you take care with their arguments: even when the arguments are fixed at compile-time, the environment is a potential attack vector. Use fork()/execve()
, preferably with a clean environment (but at least restrict the environment to white-listed variables, preferably with their values sanity-checked). Remember that a good quality system does what it is supposed to do, while a secure system does what it is supposed to do and nothing more. Invoking a full-blown shell makes doing nothing more a little bit harder.
Complexity is the enemy of security. These days you can easily find people recommending simpler shells. Most shells are free from shellshock by not supporting exported functions at all. Conversely, bash has received lots of security features over the years (you need to invoke it with -p
to avoid it dropping privileges on startup, it sanitizes IFS, ...), so don't assume I'm advocating switching shells, this is more of a general advice.
Some excerpts from David Wheeler's ancient "Secure Programming for Linux and UNIX HOWTO" chapter on environment variables are still worth rereading.
§5.2.3 ¶1:
For secure setuid/setgid programs, the short list of environment
variables needed as input (if any) should be carefully extracted. Then
the entire environment should be erased, followed by resetting a small
set of necessary environment variables to safe values. There really
isn't a better way if you make any calls to subordinate programs;
there's no practical method of listing ``all the dangerous values''.
§5.2.3 ¶6:
If you really need user-supplied values, check the values first (to
ensure that the values match a pattern for legal values and that they
are within some reasonable maximum length).
Waving my hands a lot, I suspect the new exploit does the following:
- The backslash helps bypass the original patch, so that the string is still evaluated.
- The
>
combines with echo
as an output redirection for the bash
shell
- With
echo
being consumed by the evaluation to define the function, the only part of the -c
argument left to execute is date
, whose output goes to a file name echo
instead of standard output.
That's the best I can come up with short of reading the bash
source, but I suspect the backslash facilitates some sort of buffer overflow that allows the environment string and the argument to -c
to be merged.