Hacker News new | past | comments | ask | show | jobs | submit login

Read a bit about this because I didn't understand CGI. tl;dr version of what's going on here, if I'm not mistaken (assuming apache/php for this example):

1. Web server (apache) gets request to route to CGI script (PHP)

2. Per the CGI spec, apache passes the request body to PHP as stdin & sets the HTTP headers as environment variables, so PHP can access them

3. In the PHP script, `exec`/`passthru`/`shell_exec` etc. is called to do something in the shell/on the system level. This spawns a new shell (which may be bash)

4. bash interprets the environment variables set by apache

The rub lies in step 4: when bash interprets an environment variable called `HTTP_USER_AGENT` containing the value `() { :;}; /bin/ping -c 1 198.x.x.x` it "gets confused" & interprets that first part (before the second semicolon) as a function, then executes the second part as well

Hopefully this answers the "how does the exploit get from the browser to bash?"

Further question: "If I do not use exec/shell_exec/popen etc, am I still vulnerable (just by virtue of using mod_php)?" AFAICT No, but I am not really sure (I hope someone clears this up).

Additional note about PHP: disabling all these passthru type functions has been recommended for years: http://www.cyberciti.biz/faq/linux-unix-apache-lighttpd-phpi...




Can someone who understands CGI enlighten me why (oh why) everyone treats #4 as the main problem instead of #2 ?

I mean, looking at it from an outside perspective, I can interpret #4 as working as intendeed (attacker calls my shell with arbitrary parameters and, naturally, my shell does arbitrary things controlled by the attacker), and #2 as a total WTF - why is apache passing arbitrary input data to global-scoped (as in, affecting the subsequent bash invocations) environment variables; and can it stop doing it? If not, why is apache passing this data without any verification/sanitization/escaping, and can it stop doing it?

The fixes to the bash flaw seem like a band-aid - if some web service invokes other programs that somehow get called with attacker-defined environment variables, then it seems like a potential for future exploits on other targets than bash; many other things will change their behavior depending on the environment vars.


It's not setting arbitrary environment variables, it's setting specific ones. Bash is basically calling eval on the list of envvars without re-escaping the values.

The people who wrote the CGI spec didn't expect that to happen at startup.

csh and zsh don't do it, and I can't think of any reason why someone would want that.

CGI was always a kludge, and envvars weren't a great choice, but they were presumed to be safe for arbitrary data.


The flaw lies in the combination of 2/3.

The HTTP_ environment variables are user input.

The PHP (or whatever CGI script) should be sanitizing user input before using it.

If this was PHP scripts doing something like this: system("/bin/sh ".$_GET['x']);

Nobody would be freaking out... because that's just stupid.


In your opinion, what is the difference between php blindly passing unsanitized user input onwards to bash, and apache blindly passing unsanitized user input onwards to php?

Furthermore, in the sample attacks the php scripts don't 'use' that user input in any way; bash gets them because, well, it shares the same environment and its variables. If you'd want a php script 'sanitizing' those variables then it would mean checking for any possible HTTP_ environment variables and explicitly altering them even if the script doesn't recognize them - which seems ridiculous as well.


PHP is specifically designed to deal with unsanitized user input.

bash is not, seems pretty obvious


You're missing the point. Look at this code.

  export evil='() { :;}; exit';
  echo $evil
# prints '() { :;}; exit'

  bash
# new bash session immediately exits

Imagine a dev using eval() in PHP. The PHP interpreter creates a new environment, executes your (presumably sanitized) code, but also automatically calls eval on every global variable in your app.

That would be a huge bug in PHP, and it's the equivalent of what bash is doing.


Thanks. I'm still confused though:

1: Why does Bash "interpret" the environment variables' values? What is the expected result of setting a function definition as the value of an environment variable? In my worldview (which is clearly wrong) there is no reason for Bash to look at environment variables' values until they're evaluated.

2: This is probably besides the point: but since when is the empty string a valid function name? I can't get Bash to accept "() { :; }" (as opposed to "f() { :; }" as valid bash.

Edit: see my self-reply for answers.


Aha, I found answers here: http://seclists.org/oss-sec/2014/q3/650

Basically, Bash interprets environment variables at start up as a means for you to pass functions into subshells. Whenever an environment variable's value starts with "() {", it is interpreted as a function, which is named according to the environment variable's name.

I guess even without the bug under discussion this feature is a theoretical security flaw. If you set your user agent to "() { ping your.domain.com}", a function named HTTP_USER_AGENT, which pinged your.domain.com would exist in any shellThe name of the function is passed as the Apache spawned. It's not hard to imagine a buggy script accidentally executing it.


As far as I can see, this is largely correct. But do realize:

- that environments are inherited by child processes. - It's not just Web servers that might execute scripts (DHCP could also be vulnerable, SSH with command restrictions, many other things too).

That's the big shitstorm about this bug. It's very hard to determine when you're actually vulnerable. The safest thing to do is to upgrade bash everywhere. But wait! The patches they rolled out don't actually fix the issue all that well. So there's no easy one-stop guide you can follow to fix this. Everybody actually has to think about every single system that might potentially be vulnerable and come up with a good solution all by themselves.


I don't quite get it yet. Let's suppose someone sends a header like this: 'User-Agent:() { :; }; touch /tmp/shellshocked'

Then in my cgi script I do a harmless system('date') call. The file /tmp/shellshocked won't be created right? The file would have been created had I done for example a system('echo $HTTP_USER_AGENT') call. (That is, one needs to explicitly reference the environment variable that gets passed to bash). Is my understanding correct?


No, bash interprets as functions ALL environment variables that begin with '() {' on startup. If you have an unpatched bash it will also excute any trailing commands after the function definition. You don't need to manually reference any environment variables at all to be vulnerable. This is to how the bash feature of exporting functions to subshells ("export -f") works.


This is not my understanding. My understanding is that because this mechanism is intended to pass functions down to the system() call, all environment variables are parsed up front in case they contain any. Because the vulnerability lies in the parsing, it doesn't matter whether you reference the variable in the subsequent shell process or not.


Odd. I am running quite old software:

  echo $0 -> bash
  ls -l /bin/sh -> /bin/bash
  GNU bash, version 3.1.17(2)-release
  Apache/2.2.25
And I couldn't reproduce the vurnerability in a perl cgi script unless I had explicitly referenced an environment variable in the system() call like I posted above. I thought all versions are vurnerable.

  #test-cgi.pl
  use strict;
  use warnings;
  use CGI;
 
  print "Content-Type: text/plain\n\n";

  my $q = CGI->new();

  print "\nHEADERS:\n==============\n";
  my %headers = map { $_ => $q->http($_) } $q->http();
  foreach my $k ( keys %headers ) {
          print "$k\n $headers{$k}\n";
  }

  system("echo hello");


  #request.py
  import socket
  def build_request(meth, host, path, headers=None):
      req = "%s %s HTTP/1.0\r\nHost: %s\r\n" % (meth, path,   host)
      if headers is not None:
          req = req + "\r\n".join(headers) + "\r\n"  
      return req + "\r\n"

  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  ip_addr = 'xxx.xxx.xxx.xxx'
  sock.connect((ip_addr, 80))
  headers = [      
      'User-Agent:() { :; }; /bin/touch /tmp/testshellshock;',  
  ]
  req = build_request("GET", ipaddr, "/cgi-bin/test-cgi.pl", headers) 
  sock.sendall(req)


From "man perlfunc" unser "system":

If there is only one scalar argument, the argument is checked for shell metacharacters, and if there are any, the entire argument is passed to the system's command shell for parsing (this is "/bin/sh -c" on Unix platforms, but varies on other platforms). If there are no shell metacharacters in the argument, it is split into words and passed directly to "execvp", which is more efficient.

So your example does not invoke the shell.


Passing a shell metacharacter to the system function does indeed then trigger the vurnerability. Thanks as I didn't realize it wasn't calling the shell otherwise.


A CGI script may not be PHP - it could actually just be a bash script, perl, whatever.


And regardless, the cgi script could invoke bash, or invoke something that invokes bash, etc.


I'm curious -- is FastCGI vulnerable as well?

My understanding was that although regular CGI respawns the command interpreter every time the web server is queried, FastCGI reuses the same command interpreter and just updates the environment.


I have no sympathy really for people who use `system()`-style calls, or bash in their web servers. Bash is fairly obviously designed with complete disregard for security. I seriously doubt this is its last major flaw.

But anyway, is it really that common? I would have thought most CGI scripts are Python, Perl, PHP, etc. and don't use `system()` type calls. Right?


No, super wrong. Underneath the hood there are all kinds of things that go on and result in system() or similar calls. Think about every module in CPAN / PyPI / whatever. Any non-trivial Web app has a high likelihood of eventually, somehow, somewhere, causing a system() type call to fire off.


This is actually quite common and acceptable if you want to interact with a piece of software for which there is no library or api. One real world example is producing thumbnails from Word documents using Libre Office.


You don't have to do it explicitly either. In Perl, for instance, doing stuff like open($tgz, "|-", "gzip | tar -cvf /tmp/file.tgz"); is going to cause execution of /bin/sh behind the scenes. Assuming that /bin/sh is never called during processing of external requests is risky.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: