Shellshock mitigation with BIG-IP iRules

Yesterday, NIST released information on a new network exploitable vulnerability in the GNU Bash shell as demonstrated by vectors involving parts of OpenSSH sshd, the mod_cgi, and mod_cgid modules in the Apache HTTP Server, scripts executed by DHCP clients, and other situations where setting the environment around a bash process occurs.  Vulnerability CVE-2014-6271, or Shellshock as everyone is calling it, allows remote attackers to execute arbitrary code on the target systems.  Redhat has a great overview in their security blog posting on the topic.

What makes this vulnerability serious is the fact that server-side CGI applications running under the Apache Web Server with mod_cgi or mod_cgid are susceptible.

F5's Jeff Costlow is maintaining an article on the Shellshock vulnerability so make sure you check back to that article for F5's official stance.

The first step to mitigation is to setup a plan to patch the bash shell on all of your systems.  In the interum, if you believe that any of your backend servers are vulnerable (if they are Unix-based then they likely are) and you are fronting them with a BIG-IP, below is an iRule solution you can apply to your Virtual Servers that will protect your servers against attacks.

Block-Shellshocked

The Block-Shellshock iRule searches for the pattern "*() {*" in the URI and in the HTTP headers.  It is rare that this pattern of characters will occur in a URI or HTTP Header so we believe the number of false-positives will be minimal.  If the pattern is found, an audit entry will be written to the system log with the client ip as well as the URI, and optionally the HTTP header, that the attack was detected.  I chose to issue a reject on the connection.  If you want to be more polite to the hackers, you can substitute the "reject" with a 403 - Forbidden.

when HTTP_REQUEST {
  set pattern 
"*() \{*"
;  
  if { [string match $pattern [HTTP::uri]] } {
    log local0. "Detected CVE-2014-6271 attack from '[IP::client_addr]' in URI '[HTTP::uri]'";
    reject;
  } else {
    foreach header_name [HTTP::header names] {
      foreach header_value [HTTP::header values $header_name] {
        if { [string match $pattern $header_value] } {
          log local0. "Detected CVE-2014-6271 attack from '[IP::client_addr]' in HTTP Header $header_name = '$header_value'; URI = '[HTTP::uri]'";
          reject;
          break;
        }
      }
    }
  }
}

Block-Shellshock-full

The first iRule gives a more detailed audit if the attack is detected.  In the case where that is not a concern and you are more concerned with performance, the HTTP::request method can be used to perform a single search across all of the request header information.  This does not cover the POST body, but at this point there is no indication that the exploit is exposed within the body portion of a POST request.  This irule reports the client IP along with the requested URI that is the source of the attack.

when HTTP_REQUEST {
  if { [string match 
"*() \{*"
 [HTTP::request]] } {
    log local0. "Detected CVE-2014-6271 attack from '[IP::client_addr]'; URI = '[HTTP::uri]'";
    reject;
  }
}

We will be monitoring this vulnerability over the coming weeks and update the iRule solution if a more optimal one becomes available.  Please leave a comment if you have any suggestions.  And, make sure you monitor Jeff's article for updates.

Special Considerations

Please be aware that this iRule will reject any connections containing the string "() {" in the URI or header.  While we are confident that this is not a valid pattern for standard headers, there could be situations where custom application values contain this string pattern and this iRule would cause issues to those applications.  We suggest running the longer iRule and actively monitoring the system log for false positives.  If you find a case where your custom application is being blocked, you can customize the first iRule to exclude the header your application relies on.

Updated Jun 06, 2023
Version 2.0
  • Quick question. I know this irule script will reject and log. However is there a way to set it to only log and not reject? If I simply remove reject; from the script will it monitor instead of using the reject action? when HTTP_REQUEST { set pattern "*() \{*"; if { [string match $pattern [HTTP::uri]] } { log local0. "Detected CVE-2014-6271 attack from '[IP::client_addr]' in URI '[HTTP::uri]'"; reject; Is the reject; syntax an action command or is it simply a message that the hacker receives? This part is what is throwing me off. "I chose to issue a reject on the connection. If you want to be more polite to the hackers, you can substitute the "reject" with a 403 - Forbidden." Thanks!
  • @David Hwang - if you want to make the script passive, just comment out the "reject" lines by putting a pound symbol (comment) before the reject (or remove the line completely). The reject will send a connection reset to the client. An alternate approach would be to send a HTTP response 403. That's not as brash has terminating the connection. With a terminated connection, the hacker wouldn't be able to know for sure the cause of it and thus not necessarily know if their attack worked or not. By sending a forbidden message, might lead them to try something else. That's what I was getting at with the comment about reject vs. forbidden.
  • @rgrojas - AFAIK, there is no direct way to send an email from within an iRule. What I would likely suggest is that you issue a log message and then setup a notification with syslog when it catches that message. Here's a F5 solution on setting email notifications on snmp alerts - http://support.f5.com/kb/en-us/solutions/public/3000/600/sol3667.html. Hope this helps...
  • I wrote the script if you need put the irule in something virtual server, this is: !/bin/bash while read line do PART=`echo $line | awk -F ' ' '{print $1}'` VIRTUAL=`echo $line | awk -F ' ' '{print $2}'` VER=`tmsh show sys version | head -n5 | tail -n1 | awk '{ print $2}'` if [ "$VER" == "11.5.1" ]; then { HAB=`tmsh list ltm virtual /$PART/$VIRTUAL rules |grep Irule | awk '{ print $1}'` if [ "$HAB" == '' ]; then { echo "El virtual $VIRTUAL no tiene Irule $HAB" tmsh modify ltm virtual /$PART/$VIRTUAL rules { Irule_Mitigacion_Vulnerabilidad } } else { echo "El virtual $VIRTUAL tiene estos Irule $HAB" tmsh modify ltm virtual /$PART/$VIRTUAL rules { Irule_Mitigacion_Vulnerabilidad $HAB } } fi tmsh modify ltm virtual /$PART/$VIRTUAL rules { Irule_Mitigacion_Vulnerabilidad } } elif [ "$VER" == "10.2.4" ]; then { tmsh modify cli admin-partitions update-partition $PART tmsh modify ltm virtual $VIRTUAL rules { Irule_Mitigacion_Vulnerabilidad } } fi done