I’ve seen a ton of scary articles about a newly discovered Bash vulnerability that has been affectionately named Shellshock by the security community. People are saying Shellshock is bigger than Heartbleed, and that it can affect not just millions of web servers, but also routers, smartphones, and even light bulbs.

These articles all follow the same basic template. They say that there is a bug in Bash that can allow a remote attacker to execute any code they want on a vulnerable machine. Then they say that millions of computers run Bash, and as a result we are all doomed. The ones that lean more on the technical side present you with a snippet of Bash code that, when you run it, prints out something menacing like “You’ve been hacked!” if your version of Bash is vulnerable. As a developer, it’s been frustrating that these articles, in an effort to not confuse and lose the attention of their reader base, have shied away from going into the technical details of the bug. I think the details of Shellshock are instructional, and they’re way more interesting to read about than statistics on the pervasiveness of Bash. Yes, you should all patch your Bash right away. But let’s talk about the bug itself.

For fun, here’s that line of code that you can run to see if your version of Bash is vulnerable. Bash comes pre-installed in almost all Linux distributions, and it is the default shell in OS X Terminal. Windows users are safe, unless you manually installed Bash using Cygwin.

If your Bash version is vulnerable to Shellshock, it will print “Vulnerable!”, but why? That’s definitely not the intended behavior of this code. We are setting an environment variable foo to be the string '() { :; }; echo "Vulnerable!"' and then invoking a sub-shell that, in this case, does nothing. The end result should be that nothing is printed on the screen.

The problem stems from the funky way that Bash stores functions in environment variables. Let’s say you open up Bash and define a simple “Hello world” function.

You can run the function all you want in this shell, but you can’t call it in programs that run inside it, which are also known as sub-shells.

Let’s say you really want to run a sub-shell that uses the function foo. The standard way to do this is to use the export command to turn foo into an environment variable. The -f flag tells export that you are referring to a function.

You can also use the export command to make plain-old string variables into environment variables.

Using the env command, you can see all of the current environment variables as a list where each element is of the form <variable>=<value>. For functions, Bash uses special characters to distinguish it from the rest of the variables.

I’ve highlighted the special function characters so you can see them. This is where the vulnerability comes in. All Bash environment variables are strings, even when they represent functions. Bash uses the characters () { to distinguish a function string from a regular string. When a sub-shell is invoked, a copy of each environment variable is created and made available to the sub-shell. When Bash gets to an environment variable that starts with () {, it realizes this is a function string and evaluates the line in order to turn it into a real function. Unfortunately, up until a few days ago, Bash would just evaluate the entire string as code, blindly, with the same user permissions as given to the sub-shell, and without actually checking if the string is only a function definition. Therefore, it would not only run the function definition, but potentially any code, malicious or otherwise, that followed it. Let’s come back to the original one-line test.

Here, I’m using the () { characters to denote a function definition. However, I’m also ending the function definition and following it with more code. When I invoke the sub-shell using the bash command, the string inside of foo gets evaluated, and the echo is executed!

It gets worse. Exploiting this vulnerability on the web is shockingly (pun-intended) easy. Many web servers invoke Bash scripts in response to requests. One of the many ways that they can do this is by using the Common Gateway Interface, or CGI. It’s common for the web server to pass HTTP request information into the shell script, and the common way to do this is with environment variables. Things like the user agent string, cookies, and the GET parameters are stored in environment variables before running the sub-shell. Since users have access to all of these pieces of information, a malicious user could change their user agent string to be, say, '() { :; }; <malicious code>' and can force the web server to run any code they want.

Since this vulnerability was announced to the public last week, the Bash source code has gotten lots of new attention from security researchers. Many similar bugs in Bash have popped up, most of them similar to the original, and all allow unintended code execution. The latest version of Bash, version 4.3, has been patched three times in the last week to fix the discovered Shellshock variants, and there will likely be more variants discovered in the coming weeks. The best thing you can do is update Bash on all of your machines, even if they aren’t running network services. In addition, we’ll be updating the Synopsys Web Scanner in the next few days to scan for all of the known variants of Shellshock on your website.