Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

There is apparently a vulnerability (CVE-2014-6271) in bash: Bash specially crafted environment variables code injection attack

I am trying to figure out what is happening, but I'm not entirely sure I understand it. How can the echo be executed as it is in single quotes?

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
vulnerable
this is a test

EDIT 1: A patched system looks like this:

$ 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

EDIT 2: There is a related vulnerability / patch: CVE-2014-7169 which uses a slightly different test:

$ env 'x=() { :;}; echo vulnerable' 'BASH_FUNC_x()=() { :;}; echo vulnerable' bash -c "echo test"

unpatched output:

vulnerable
bash: BASH_FUNC_x(): line 0: syntax error near unexpected token `)'
bash: BASH_FUNC_x(): line 0: `BASH_FUNC_x() () { :;}; echo vulnerable'
bash: error importing function definition for `BASH_FUNC_x'
test

partially (early version) patched output:

bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `x'
bash: error importing function definition for `BASH_FUNC_x()'
test

patched output up to and including CVE-2014-7169:

bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `BASH_FUNC_x'
test

EDIT 3: story continues with:

share|improve this question
    
It's not the echo that gets executed. its the function definition of x. If the function defined in x does some sneaky underhanded work, there is no way bash can check the return value if function x were real. Notice the function is empty in the test code. An unchecked return value can lead to script injection. Script injection leads to privilege escalation and privilege escalation leads to root access. The patch disables the creation of x as a function – eyoung100 Sep 24 '14 at 20:25
20  
eyoung100, no the echo is getting executed. You can see it's getting executed because the word vulnerable appears in the output. The main problem is that bash is parsing and executing the code after the function definition as well. See the /bin/id part of seclists.org/oss-sec/2014/q3/650 for another example. – Mikel Sep 25 '14 at 1:42
4  
Just a quick side comment. Red Hat have advised that the patch that has been released is only a partial patch and leaves systems still at risk. – Peter Sep 25 '14 at 13:49
2  
@eyoung100 the difference is that the code inside the function only executes when the environment variable is explicitly called. The code after the function definition executes every time a new Bash process starts. – David Farrell Sep 26 '14 at 17:42
1  
See stackoverflow.com/questions/26022248/… for additional details – Barmar Oct 1 '14 at 19:24

bash stores exported function definitions as environment variables. Exported functions look like this:

$ foo() { bar; }
$ export -f foo
$ env | grep -A1 foo
foo=() {  bar
}

That is, the environment variable foo has the literal contents:

() {  bar
}

When a new instance of bash launches, it looks for these specially crafted environment variables, and interprets them as function definitions. You can even write one yourself, and see that it still works:

$ export foo='() { echo "Inside function"; }'
$ bash -c 'foo'
Inside function

Unfortunately, the parsing of function definitions from strings (the environment variables) can have wider effects than intended. In unpatched versions, it also interprets arbitrary commands that occur after the termination of the function definition. This is due to insufficient constraints in the determination of acceptable function-like strings in the environment. For example:

$ export foo='() { echo "Inside function" ; }; echo "Executed echo"'
$ bash -c 'foo'
Executed echo
Inside function

Note that the echo outside the function definition has been unexpectedly executed during bash startup. The function definition is just a step to get the evaluation and exploit to happen, the function definition itself and the environment variable used are arbitrary. The shell looks at the environment variables, sees foo, which looks like it meets the constraints it knows about what a function definition looks like, and it evaluates the line, unintentionally also executing the echo (which could be any command, malicious or not).

This is considered insecure because variables are not typically allowed or expected, by themselves, to directly cause the invocation of arbitrary code contained in them. Perhaps your program sets environment variables from untrusted user input. It would be highly unexpected that those environment variables could be manipulated in such a way that the user could run arbitrary commands without your explicit intent to do so using that environment variable for such a reason declared in the code.

Here is an example of a viable attack. You run a web server that runs a vulnerable shell, somewhere, as part of its lifetime. This web server passes environment variables to a bash script, for example, if you are using CGI, information about the HTTP request is often included as environment variables from the web server. For example, HTTP_USER_AGENT might be set to the contents of your user agent. This means that if you spoof your user agent to be something like '() { :; }; echo foo', when that shell script runs, echo foo will be executed. Again, echo foo could be anything, malicious or not.

share|improve this answer
3  
Could this affect any other Bash-like shell then, like Zsh? – Amelio Vazquez-Reina Sep 24 '14 at 22:52
3  
@user815423426 No, zsh doesn't have this feature. Ksh has it but implemented differently, I think functions can only be transmitted in very narrow circumstances, only if the shell forks, not through the environment. – Gilles Sep 24 '14 at 23:19
19  
@user815423426 rc is the other shell that passes functions in the environment, but its with variable with names prefixed with "fn_" and they are only interpreted when invoked. – Stéphane Chazelas Sep 24 '14 at 23:48
17  
@StéphaneChazelas - thanks for reporting the bug. – Deer Hunter Sep 25 '14 at 4:55
13  
@gnclmorais You mean you run export bar='() { echo "bar" ; }'; zsh -c bar and it displays bar rather than zsh:1: command not found: bar? Are you sure you aren't confusing the shell you're invoking with the shell that you're using to set up the test? – Gilles Sep 25 '14 at 13:06

This may help to further demonstrate what is going on:

$ export dummy='() { echo "hi"; }; echo "pwned"'
$ bash
pwned
$

If you are running a vulnerable shell, then when you start a new subshell (here, simply by using the bash statement), you will see that the arbitrary code (echo "pwned") is immediately executed as part of its initiation. Apparently, the shell sees that the environment variable (dummy) contains a function definition, and evaluates the definition in order to define that function in its environment (note that it is not executing the function: that would print 'hi'.)

Unfortunately, it does not just evaluate the function definition, it evaluates the entire text of the environment variable's value, including the possibly malicious statement(s) that follow the function definition. Note that without the initial function definition, the environment variable would not be evaluated, it would merely be added to the environment as a text string. As Chris Down pointed out, this is a specific mechanism to implement the importing of exported shell functions.

We can see the function that has been defined in the new shell (and that it has been marked as exported there), and we can execute it. Furthermore, dummy has not been imported as a text variable:

$ declare -f
dummy ()
{
    echo "hi"
}
declare -fx dummy
$ dummy
hi
$echo $dummy
$

Neither the creation of this function, nor anything it would do were it to be run, is part of the exploit - it is only the vehicle by which the exploit is executed. The point is that if an attacker can supply malicious code, preceded by a minimal and unimportant function definition, in a text string that gets put into an exported environment variable, then it will be executed when a subshell is started, which is a common event in many scripts. Furthermore, it will be executed with the privileges of the script.

share|improve this answer
17  
While the accepted answer does actually say this if you read it carefully, I found this answer even clearer and more helpful in understanding that it is the evaluation of the definition (rather than executing the function itself) that is the problem. – natevw Sep 25 '14 at 18:21
1  
why does this example has export command while the others had env? i was thinking env is being used to define the environmental variables that would be called when another bash shell is launched. then how is this working with export – Haris Sep 25 '14 at 19:12
    
Up until this moment there has not been an accepted answer. I'll probably wait for another couple days before accepting one. The downside of this answer is that it doesn't break down the original command, nor discusses how to get from the original command in the question to the commands in this answer, showing they are identical. Apart from that it is a good explanation. – jippie Sep 25 '14 at 19:31
    
@ralph - both env and export export environment definitions so that they are available in a subshell. The problem is actually in how these exported definitions are imported into a subshell's environment, and specifically in the mechanism that imports function definitions. – sdenham Sep 26 '14 at 14:46
1  
@ralph - env runs a command with some options and environment variables set. Note that in the original question examples, env sets x to a string, and calls bash -c with a command to run. If you do env x='foo' vim, Vim will launch, and in there you can call out to its containing shell/environment with !echo $x, and it will print foo, but if you then exit and echo $x, it won't be defined, as it only existed while vim was running via the env command. The export command instead sets persistent values in the current environment so that a subshell run later will use them. – Gary Fixler Sep 27 '14 at 8:15

I wrote this as a tutorial-style recasting of the excellent answer by Chris Down above.


In bash you can have shell variables like this

$ t="hi there"
$ echo $t
hi there
$

By default, these variables are not inherited by child processes.

$ bash
$ echo $t

$ exit

But if you mark them for export, bash will set a flag that means they will go into the environment of subprocesses (although the envp parameter is not much seen, the main in your C program has three parameters: main(int argc, char *argv[], char *envp[]) where that last array of pointers is an array of shell variables with their definitions).

So let's export t as follows:

$ echo $t
hi there
$ export t
$ bash
$ echo $t
hi there
$ exit

Whereas above t was undefined in the subshell, it now appears after we exported it (use export -n t if you want to stop exporting it).

But functions in bash are a different animal. You declare them like this:

$ fn() { echo "test"; }

And now you can just invoke the function by calling it as if it were another shell command:

$ fn
test
$

Once again, if you spawn a subshell, our function is not exported:

$ bash
$ fn
fn: command not found
$ exit

We can export a function with export -f:

$ export -f fn
$ bash
$ fn
test
$ exit

Here's the tricky part: an exported function like fn is converted into an environment variable just like our export of the shell variable t was above. This doesn't happen when fn was a local variable, but after export we can see it as a shell variable. However, you can also have a regular (ie, non function) shell variable with the same name. bash distinguishes based on the contents of the variable:

$ echo $fn

$ # See, nothing was there
$ export fn=regular
$ echo $fn
regular
$ 

Now we can use env to show all shell variables marked for export and both the regular fn and the function fn show up:

$ env
.
.
.
fn=regular
fn=() {  echo "test"
}
$

A sub-shell will ingest both definitions: one as a regular variable and one as a function:

$ bash
$ echo $fn
regular
$ fn
test
$ exit

You can define fn as we did above, or directly as a regular variable assignment:

$ fn='() { echo "direct" ; }'

Note this is a high unusual thing to do! Normally we would define the function fn as we did above with fn() {...} syntax. But since bash exports it through the environment, we can "short cut" directly to the regular definition above. Note that (counter to your intuition, perhaps) this does not result in a new function fn available in the current shell. But if you spawn a **sub**shell, then it will.

Let's cancel export of the function fn and leave the new regular fn (as shown above) intact.

$ export -nf fn

Now the function fn is no longer exported, but the regular variable fn is, and it contains () { echo "direct" ; } in it.

Now when a subshell sees a regular variable that begins with () it interprets the rest as a function definition. But this is only when a new shell begins. As we saw above, just defining a regular shell variable starting with () does not cause it to behave like a function. You have to start a subshell.

And now the "shellshock" bug:

As we just saw,when a new shell ingests the definition of a regular variable starting with () it interprets it as a function. However, if there is more given after the closing brace that defines the function, it executes whatever is there as well.

These are the requirements, once more:

  1. New bash is spawned
  2. An environment variable is ingested
  3. This environment variable starts with "()" and then contains a function body inside braces, and then has commands afterward

In this case, a vulnerable bash will execute the latter commands.

Example:

$ export ex='() { echo "function ex" ; }; echo "this is bad"; '
$ bash
this is bad
$ ex
function ex
$

The regular exported variable ex was passed to the subshell which was interpreted as a function ex but the trailing commands were executed (this is bad) as the subshell spawned.


Explaining the slick one-line test

A popular one-liner for testing for the Shellshock vulnerability is the one cited in @jippie's question:

env x='() { :;}; echo vulnerable' bash -c "echo this is a test"

Here is a break-down: first the : in bash is just a shorthand for true. true and : both evaluate to (you guessed it) true, in bash:

$ if true; then echo yes; fi
yes
$ if :; then echo yes; fi
yes
$

Second, the env command (also built into bash) prints the environment variables (as we saw above) but also can be used to run a single command with an exported variable (or variables) given to that command, and bash -c runs a single command from its command-line:

$ bash -c 'echo hi'
hi
$ bash -c 'echo $t'

$ env t=exported bash -c 'echo $t'
exported
$

So sewing all of this stuff together, we can run bash as a command, give it some dummy thing to do (like bash -c echo this is a test) and export a variable that starts with () so the subshell will interpret it as a function. If shellshock is present, it will also immediately execute any trailing commands in the subshell. Since the function we pass is irrelevant to us (but must parse!) we use the shortest valid function imaginable:

$ f() { :;}
$ f
$ 

The function f here just executes the : command, which returns true and exits. Now append to that some "evil" command and export a regular variable to a subshell and you win. Here is the one-liner again:

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"

So x is exported as a regular variable with a simple valid function with echo vulnerable tacked on to the end. This is passed to bash, and bash interprets x as a function (which we don't care about) then perhaps executes the echo vulnerable if shellshock is present.

We could shorten the one-liner a little by removing the this is a test message:

$ env x='() { :;}; echo vulnerable' bash -c :

This doesn't bother with this is a test but runs the silent : command yet again. (If you leave off the -c : then you sit in the subshell and have to exit manually.) Perhaps the most user-friendly version would be this one:

$ env x='() { :;}; echo vulnerable' bash -c "echo If you see the word vulnerable above, you are vulnerable to shellshock"
share|improve this answer
10  
Nice explanation. This question is receiving a lot of views (probably not everybody being as proficient in bash as the others) and I believe no-one yet spent a couple words on what { :;}; actually says. That would be a nice addition to your answer in my opinion. May explain how you get from your example to the original command in the question? – jippie Sep 26 '14 at 5:27
3  
Very clear and concise! Thank you. – kbro Sep 26 '14 at 9:24

If you can feed arbitrary environment variables to a program, you can cause it to do just about anything by having it load libraries of your choosing. In most cases this is not considered a vulnerability in the program receiving those environment variables, but rather in the mechanism by which an outsider could feed in arbitrary environment variables.

However CVE-2014-6271 is different.

There is nothing wrong in having untrusted data in an environment variable. One just has to ensure it doesn't get put in any of those environment variables which can modify program behavior. Put a bit more abstract, for a particular invocation, you can create a whitelist of environment variable names, which are allowed to be specified directly by an outsider.

An example which has been put forward in the context of CVE-2014-6271 is scripts used for parsing logfiles. Those may have a very legitimate need for passing untrusted data around in environment variables. Of course the name for such an environment variable is chosen such that it doesn't have any adverse affects.

But here is what is bad about this particular bash vulnerability. It can be exploited through any variable name. If you create an environment variable called GET_REQUEST_TO_BE_PROCESSED_BY_MY_SCRIPT, you wouldn't expect any other program besides your own script to interpret the contents of that environment variable. But by exploiting this bash bug, every single environment variable becomes an attack vector.

Notice that this doesn't mean names of environment variables are expected to be secret. Knowing the names of the environment variables involved doesn't make an attack any easier.

If program1 calls program2 which in turn calls program3, then program1 could pass data to program3 through environment variables. Each program has a specific list of environment variables which it sets and a specific list which it acts upon. If you chose a name not recognized by program2, you can pass data from program1 to program3 without worrying about this having any adverse affects on program2.

An attacker knowing the exact names of variables exported by program1 and names of variables interpreted by program2 cannot exploit this knowledge to modify the behavior of 'program2` if there is no overlap between the set of names.

But this broke down if program2 was a bash script, because due to this bug bash would interpret every environment variable as code.

share|improve this answer
1  
"every single environment variable becomes an attack vector" - that's the part that I was missing. Thanks. – wrschneider Sep 27 '14 at 1:17

It is explained in the article you linked ...

you can create environment variables with specially-crafted values before calling the bash shell. These variables can contain code, which gets executed as soon as the shell is invoked.

Which means the bash that is called with -c "echo this is a test" executes the the code in the single quotes when it is invoked.

Bash has functions, though in a somewhat limited implementation, and it is possible to put these bash functions into environment variables. This flaw is triggered when extra code is added to the end of these function definitions (inside the enivronment variable).

Means the code example you posted exploits the fact that the invoked bash does not stop evaluating this string after performing the assignment. A function assignment in this case.

The actually special thing about the code snippet you posted, as I understand it, is that by using putting a function definition before the code we want to execute, some security mechanisms can be circumvented.

share|improve this answer

The bash manpage documents the -c option thus...

   -c string   If  the  -c  option  is  present, then commands are read from
               string.  If there are arguments after the  string,  they  are
               assigned to the positional parameters, starting with $0.

The -c option takes the next argument as a string of commands (a list in bash manpage terminology). For example, on an unpatched bash 4.1.0 running as root...

[ root@LX03:~/lib ] bash -c 'echo hello; echo goodbye; x () { echo "in function"; }; x; echo "after function"'
hello
goodbye
in function
after function

To make a string of space-delimited words a single argument, the string must be in either single- or double-quotes. Single is used here.

The text strings within the command list must then use double-quotes since we are already inside single-quotes.

Note that -c places no limit (that I've found) on the argument string's length. It could be an entire program hundreds of lines long AFAIK as long as things are escaped as necessary to maintain proper context for code inside a string.

Executing code that is inside strings is a core functionality of bash used in lots of system scripts. Just one example from /usr/bin/libtools ...

old_postinstall_cmds="chmod 644 \$oldlib~\$RANLIB \$oldlib"

Security in a Unix/Linux filesystem is primarily controlled by permissions, groups and mandatory access control lists (MACLs) when tools like SELinix are installed.

Hopefully the information above will assist in your understanding of how the bash -c functionality works.

I don't yet understand how bash has any real role in security other than by obscurity. Though with only 30 years of programming shell scripts on numerous Unix and Linux platforms, I could be wrong. Most people think I am.

To all those poised to now downvote because you disagree, let me ask you to keep an open mind. Security vulnerabilities tend to bring out the panic in all of us. Hopefully, this answer will help bring us to a complete solution for Shellshock.

I do look forward to being shown the error of my understanding. Shell scripting is what makes everything on Unix/Linux systems, and thus the Internet, possible. We need to finds ways to safely and securely do it. -- docsalvager

share|improve this answer

protected by Michael Mrozek Sep 25 '14 at 17:16

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.