Last Updated: February 25, 2016
·
11.33K
· bt3gl

Understanding the Shellshock Vulnerability

Almost a week ago, a new (old) type of OS command Injection was reported. The CWE (Common Weakness Enumeration) describes this type of vulnerability as:

> (...) The software constructs all or part of an OS command using externally-influenced
> input from an upstream component,  but it does not neutralize or incorrectly neutralizes
> special elements that could modify the intended OS command when it is sent to a downstream
> component.

The Shellshock vulnerability, also know as CVE-2014-6271, allows attackers to inject their own code into Bash using specially crafted environment variables, and it was disclosed with the following description:

> Bash supports exporting not just shell variables, but also shell
> functions to other bash instances, via the process environment to
> (indirect) child processes.  Current bash versions use an environment
> variable named by the function name, and a function definition
> starting with “() {” in the variable value to propagate function
> definitions through the environment.  The vulnerability occurs because
> bash does not stop after processing the function definition; it
> continues to parse and execute shell commands following the function
> definition.
> For example, an environment variable setting of
>
>  VAR=() { ignored; }; /bin/id
>
> will execute /bin/id when the environment is imported into the bash
> process.  (The process is in a slightly undefined state at this point.
> The PATH variable may not have been set up yet, and bash could crash
> after executing /bin/id, but the damage has already happened at this
> point.)
>
> The fact that an environment variable with an arbitrary name can be
> used as a carrier for a malicious function definition containing
> trailing commands makes this vulnerability particularly severe; it
> enables network-based exploitation.

Even more scary, the NIST vulnerability database has rated this vulnerability “10 out of 10” in terms of severity. At this point, there are people claiming that the Shellshock attacks could already top 1 Billion and honeypots are catching several exploit payloads. Pretty nasty stuff, huh?

I thought so. So I decided it would be worth to write a comprehensive guide about it. There are a lot of things I want to talk about so I divided this guide in two. In this first part, I explain:

1. Bash functions and environment variables.
2. What's the Shellshock vulnerability.
3. Suggestions of how to protect your system.

In the second part I take a deeper look at this vulnerability, explaining:

4. More details about CVE-2014-6271 and the four others CVEs that were created after it.
5. The systems that are vulnerable, with some proof of concept code.
6. Details of how the patches were created.

Ready?


Understanding the Bash Shell

To understand this vulnerability, we need to understand how Bash handles functions and environment variables.

The [GNU Bourne Again shell (BASH)] is a [Unix shell] and [command language interpreter]. It was released in 1989 by [Brian Fox] for the [GNU Project] as a free software replacement for the [Bourne shell], which was born back in 1977.

$ man bash

BASH(1)                      General Commands Manual                      BASH(1)

NAME
       bash - GNU Bourne-Again SHell

SYNOPSIS
       bash [options] [file]

COPYRIGHT
       Bash is Copyright (C) 1989-2011 by the Free Software Foundation, Inc.

DESCRIPTION
       Bash  is  an sh-compatible command language interpreter that executes com‐
       mands read from the standard input or from a file.  Bash also incorporates
       useful features from the Korn and C shells (ksh and csh).
(...)

Of course, there are [other command shells out there]. However, Bash is the default shell for most of the Linux systems (and Linux-based systems), including any Debian-based and Red Hat & Fedora.

Functions in Bash

The interesting stuff comes from the fact that Bash is also a scripting language, with the ability to define functions. This is super useful when you are writing scripts. For example, hello.sh:

#!/bin/bash
function hello {
  echo Hello!
}
hello

which can be called as:

$ chmod a+x hello.sh
$ ./hello.sh
Hello!

A function may be compacted into a single line. You just need to choose a name and put a () after it. Everything inside {} will belong to the scope of your function.

For example, we can create a function bashiscool that uses echo to display message on the standard output:

$ bashiscool() { echo "Bash is actually Fun"; }
$ bashiscool
Bash is actually Fun

Child Processes and the export command

We can make things even more interesting. The statement bash -c can be used to execute a new instance of Bash, as a subprocess, to run new commands. The catch is that the child process does not inherit the functions or variables that we defined in the parent:

$ bash -c bashiscool # spawn nested shell
bash: bashiscool: command not found

So before executing a new instance of Bash, we need to export the environment variables to the child. That's why we need the export command. In the example bellow, the flag -f means read key bindings from filename:

$ export -f bashiscool
$ bash -c bashiscool # spawn nested shell
Bash is actually Fun

In other words, first the export command creates a regular environment variable containing the function definition. Then, the second shell reads the environment. If it sees a variable that look like a function, it evaluates this function!

A Simple Example of an Environment Variable

Let's see how environment variables work examining some builtin Bash command. For instance, a very popular one, grep, is used to search for pattern in files (or the standard input).

Running grep in a file that contains the word 'fun' will return the line where this word is. Running grep with a flag -v will return the non-matching lines, i.e. the lines where the word 'fun' does not appear:

$ echo 'bash can be super fun' > file.txt
$ echo 'bash can be dangerous' >> file.txt
$ cat file.txt
 bash can be super fun
 bash can be dangerous
$ grep fun file.txt
 bash can be super fun
$ grep -v fun file.txt
 bash can be dangerous

The grep command uses an environment variable called GREP_OPTIONS to set default options. This variable is usually set to:

$ echo $GREP_OPTIONS
--color=auto

To update or create a new environment variable, it is not enough to use the Bash syntax GREP_OPTIONS='-v', but instead we need to call the builtin export:

$ GREP_OPTIONS='-v'
$ grep fun file.txt
 bash can be super fun
$ export GREP_OPTIONS='-v'
$ grep fun file.txt
 bash can be dangerous

The env command

Another Bash builtin, the env prints the environment variables. But it can also be used to run a single command with an exported variable (or variables) given to that command. In this case, env starts a new process, then it modifies the environment, and then it calls the command that was provided as an argument (the env process is replaced by the command process).

In practice, to use env to run commands, we:

1. set the environment variable value with env,
2. spawn a new shell using bash -c,
3. pass the command/function we want to run (for example, grep fun file.txt).

For example:

$ env GREP_OPTIONS='-v' | grep fun file.txt    # this does not work, we need another shell
bash can be super fun
$ env GREP_OPTIONS='-v' bash -c 'grep fun file.txt'  # here we go
bash can be dangerous

Facing the Shellshock Vulnerability

What if we pass some function to the variable definition?

$ env GREP_OPTIONS='() { :;};' bash -c 'grep fun file.txt'
grep: {: No such file or directory
grep: :;};: No such file or directory
grep: fun: No such file or directory
file.txt:bash can be super fun
file.txt:bash can be dangerous

Since the things we added are strange when parsed to the command grep, it won't understand them.

Now, what if we add some stuff after the function?

$ env GREP_OPTIONS='() { :;}; echo NOOOOOOOOOOOOOOO!' bash -c 'grep fun file.txt'
NOOOOOOOOOOOOOOO!
grep: {: No such file or directory
grep: :: No such file or directory
grep: }: No such file or directory
grep: fun: No such file or directory

Did you notice that echo NOOOOOOOOOOOOOOO! was executed normally? This is the Shellshock bug!

This works because when the new shell sees an environment variable beginning with (), it gets the variable name and executes the following string. This includes executing anything after the function, i.e, the evaluation does not stop when the end of the function definition is reached!

Remember that echo is not the only thing we can do. The possibilities are unlimited! For example, we can issue any /bin command:

$ env GREP_OPTIONS='() { :;}; /bin/ls' bash -c 'grep fun file.txt'
anaconda  certificates  file.txt  IPython
(...)

WOW.

Worse, we actually don't need to use a system environment variable nor even call a real command:

$ env test='() { :;}; echo STILL NOOOOOOOO!!!!' bash -c :
STILL NOOOOOOOO!!!!

In the example above, env runs a command with a given variable set (test) to some function (in this case is just a single :, a Bash command defined as doing nothing). The semi-colon signals the end of the function definition. Again, the bug is in the fact that there's nothing stopping the parsing of what is after the semi-colon!

Now it's easy to see if your system is vulnerable, all you need to do is run:

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

That simple.

[GNU Bourne Again shell (BASH)]: http://www.gnu.org/software/bash/
[Unix shell]: http://en.wikipedia.org/wiki/Bash_(Unix_shell)
[Brian Fox]: http://en.wikipedia.org/wiki/Brian_Fox_(computer_programmer)
[GNU Project]: http://www.gnu.org/gnu/thegnuproject.html
[Bourne shell]: http://en.wikipedia.org/wiki/Bourne_shell
[command language interpreter]:http://en.wikipedia.org/wiki/Command-line_interface
[other command shells out there]: http://en.wikipedia.org/wiki/Comparison_of_command_shells


How to Protect Your System

Although a patch for CVE-2014-6271 was released right after the bug was disclosed, it was incomplete and new issues were tracked as [CVE-2014-7169], [CVE-2014-7186], [CVE-2014-7187], [CVE-2014-6277], and CVE-2014-6278. Several patches have been released since then, including in the weekend. The fight is not over yet and I recommend the follow measures:

  • Update your system! And keep updating it... Many Linux distributions have released new Bash software versions so follow the instructions of your distribution. In most of the cases, a simple yum update or apt-get update or similar will do it. If you have several servers, the script bellow can be helpful:
#!/bin/bash
servers=(
120.120.120.120
10.10.10.10
22.22.22.22
)
for server in ${servers[@]}
do
ssh $server 'yum -y update bash'
done
  • HTTP requests to CGI scripts have been identified as the major attack vector (I will show more details in the part II). So disable any CGI scripts that call on the shell (however, it does not fully mitigate the vulnerability). To check if your system is vulnerable you can use [this online scanner]. Consider [mod_security] if you're not already using it.

  • Keep an eye on all of your accounts for signs of unusual activity. Consider changing important passwords.

  • Because the HTTP requests used by Shellshock exploits are quite unique, monitor logs with keywords such as
    grep '() {' access_logor cat access_log |grep "{ :;};". Some common places for http logs are: cPanel: /usr/local/apache/domlogs/, Debian/Apache: /var/log/apache2/, or CentOS: /var/log/httpd/.

  • If case of an attack, publish the attacker's information! You can use [awk] and uniq to get its IP, for example:

$ cat log_file |grep "{ :;};" | awk '{print $1}'|uniq
  • Update firmware on your router or any other web-enabled devices, as soon as they become available. Remember to only download patches from reputable sites (only HTTPS please!), since scammers will likely try to take advantage of Shellshock reports.

  • If you are on a managed hosting subscription, check your company's status. For example: [Acquia], [Heroku], [Mediatemple], and [Rackspace].

  • Update your Docker containers and AWS instances.

  • If you are running production systems that don't need exported functions at all, take a look at [this wrapper] that refuses to run bash if any environment variable's value starts with a left-parent.

[this wrapper]: https://github.com/dlitz/bash-shellshock
[this online scanner]: http://milankragujevic.com/projects/shellshock/
[mod_security]: https://www.modsecurity.org/
[CVE-2014-6278]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-6278
[CVE-2014-6277]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-6277
[CVE-2014-7187]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-7187
[CVE-2014-7186]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-7186
[CVE-2014-7169]:https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-7169
CVE-2014-6271:https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-6271
[Acquia]: https://docs.acquia.com/articles/september-2014-gnu-bash-upstream-security-vulnerability
[Rackspace]: https://status.rackspace.com/
[Mediatemple]: http://status.mediatemple.net/
[Heroku]: https://status.heroku.com/incidents/665
[awk]: http://www.grymoire.com/Unix/Awk.html
[uniq]: http://en.wikipedia.org/wiki/Uniq