32

Here is the command I have used to check my bash shell for the Shellshock bug:

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

Can anyone please explain the command in details?

45

This answer is a derivative of an original article on Fedora Magazine by Matthew Miller, licensed under the Creative Commons Attribution-Share Alike 4.0 license.

Let me explain:

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

This will print “OOPS” on a vulnerable system, but exit silently if bash has been patched.

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

This will print “OOPS” on a vulnerable system, but print “this is a test” if bash has been patched.

And you’ve probably heard that it has something to do with environment variables. But, why is code in environment variables getting executed? Well, it’s not supposed to be — but, because of a feature which I’m tempted to call a bit too clever for its own good, there’s some room for a flaw. Bash is what you see as a terminal prompt, but it also is a scripting language, and has the ability to define functions. You do that like this:

$ Ubuntu()  { echo "Ubuntu is awesome."; }

and then you have a new command. Keep in mind that the echo here isn’t actually run yet; it’s just saved as what will happen when we run our new command. This will be important in a minute!

$ Ubuntu
 Ubuntu is awesome.

Useful! But, let’s say, for some reason, We need to execute a new instance of bash, as a subprocess, and want to run my awesome new command under that. The statement bash -c somecommand does exactly this: runs the given command in a new shell:

$ bash -c Ubuntu
  bash: Ubuntu: command not found

Ooh. Sad. The child didn’t inherit the function definition. But, it does inherent the environment — a collection of key-value pairs that have been exported from the shell. (This is a whole ‘nuther concept; if you are not familiar with this, trust me for now.) And, it turns out, bash can export functions as well. So:

$ export -f Ubuntu
$ bash -c Ubuntu
  Ubuntu is awesome.

Which is all well and good — except that the mechanism by which this is accomplished is sorta dodgy. Basically, since there is no Linux/Unix magic for doing functions in environment variables, the export function actually just creates a regular environment variable containing the function definition. Then, when the second shell reads the “incoming” environment and encounters a variable with contents that look like a function, it evaluates it.

In theory, this is perfectly safe, because, remember, defining a function doesn’t actually execute it. Except — and this is why we’re here — there was a bug in the code where the evaluation didn’t stop when the end of the function definition was reached. It just kepts going.

That would never happen when the function stored in an environment variable is made legitimately, with export -f. But, why be legit? An attacker can just make up any old environment variable, and if it looks like a function, new bash shells will think it is!

So, in our first example:

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

The env command runs a command with a given variable set. In this case, we’re setting x to something that looks like a function. The function is just a single :, which is actually a simple command which is defined as doing nothing. But then, after the semi-colon which signals the end of the function definition, there’s an echo command. That’s not supposed to be there, but there’s nothing stopping us from doing it.

Then, the command given to run with this new environment is a new bash shell, again with a “echo this is a test” or “do nothing :” command, after which it will exit, completely harmlessly.

But — oops! When that new shell starts up and reads the environment, it gets to the x variable, and since it looks like a function, it evaluates it. The function definition is harmlessly loaded — and then our malicious payload is triggered too. So, if you run the above on a vulnerable system, you’ll get “OOPS” printed back at you. Or, an attacker could do a lot worse than just print things.

  • 1
    Muchas gracias for an excellent explanation of why this works. – Doug R. Oct 2 '14 at 14:16
  • 2
    Note that env isn't necessary. You can get the same result (pass/fail depending on whether Bash has been updated) by using the command without it: x='() { :;}; echo OOPS' bash -c "echo this is a test". This is because preceding a command with a variable assignment passes that variable and its value into the command's (bash -c "..." in this case) environment. – Dennis Williamson Oct 8 '14 at 22:06
  • 1
    ...but it may be necessary in some of the more recent patches. Things are in flux. – Dennis Williamson Oct 8 '14 at 22:26
  • 4
    @DennisWilliamson Whether or not env is necessary is determined by the shell from which one runs the test, not the shell being tested. (These may be the same. Even then, we're testing how bash processes its own environment.) Bourne-style shells accept NAME=value command syntax; C-style shells (e.g., csh, tcsh) don't. So the test is a little more portable with env (at the cost of sometimes creating confusion about how it works). – Eliah Kagan Oct 9 '14 at 16:00
2

In unpatched version of bash it stores exported function definitions as environment variables.

Store a function x as,

$ x() { bar; }
$ export -f x

And check its definition as,

$ env | grep -A1 x
x=() {  bar
}

So one could exploit this by defining his own environment variables, and interprets them as function definitions. For example env x='() { :;}' would be treated as

x() { :;
}

What does the command to check shellshock do,

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

From man env,

  1. env - run a program in a modified environment.

  2. : do nothing but exits with exit status 0. see more

  3. When a new instance of unpatched bash launched as bash -c "echo this is a test", the crafted environmental variable is treated as a function and loaded. Accordingly one gets the output

    vulnerable
    this is a test

Note: 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 x, 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). Also See this

  • I still found that any defined bash function, if exported being evaluated in the child shell in patched version of bash. See this: chayan@chayan:~/testr$ test() { echo "anything"; }; export -f test; bash -c test Ouput: anything So your answer is somewhat not properly directed. kasiyA's explanation of the bug as expanding the variable beyond its definition is correct i think. – heemayl Sep 28 '14 at 7:02
  • @heemayl this behavior is natural. But if you try env test='() { echo "anything"; }' bash -c "echo otherthing" you will see at output otherthing. That is corrected in the patch. feel free if I am still not clear. – souravc Sep 28 '14 at 7:28
  • Please make me clear once again. In your last comment we are basically defining the function and then telling bash to execute echo. In this example we did not have called the function in bash. Would not this have the same output in both patched and non-patched bash? I am having a notion that the bug was basically as bash was executing commands placed after the function definition whereas the function was never called anywhere later for example if we do this env test='() { echo "anything"; }; echo "foo"' bash -c "echo otherthing". Please clarify me in this context. – heemayl Sep 28 '14 at 8:00
  • @heemayl I have edited my answer, hope now it is clear. You are correct in the example in my last comment we have not called the function. But the difference is that in an unpatched bash you can call the function as it is defined, but in a patched bash the definition itself is not there. – souravc Sep 28 '14 at 12:03
  • @heemayl: No, that is incorrect. A patched Bash will still pass the function definition into the child's environment. The difference that the patch makes is that code that follows the function definition (echo vulnerable) is not executed . Note that in the latest patches, the passed function must have a specific prefix (env 'BASH_FUNC_x()'='() { :;}; echo vulnerable' bash -c "echo this is a test"). Some more recent patches may use %% instead of the first (). – Dennis Williamson Oct 8 '14 at 22:30

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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