PHPStress: DOS for Apache / NGINX servers running PHP-FPM or PHP-CGI
This Denial of Service attack boils down to a way which most modern web servers are configured. I first noticed this problem with one of my WordPress websites. I have been noticing this type of attack more and more as an unintentional result of search engines crawling websites! In a nutshell, simple and steady PHP calls will cause web servers to fill up with open PHP processes. It is a very simple way for a single person to take down a web server by consuming all available resources.
What this attack is capable of achieving
Using a standard cable/DSL connection, this attack can flood a Linux web server’s CPU and RAM using standard HTTP requests. This attack effects Apache or NGINX web servers that handle dynamic PHP content using either PHP-CGI or PHP-FPM (which includes WordPress websites). In addition, the requests made by the attack will continue to keep the server’s resources in use far past the end of the attack.
It is also important to note that this attack has the same premise as Slowloris. The main difference is that Slowloris focuses on eating up HTTP (apache) connections, while this attack focuses on eating up PHP-CGI or PHP-FPM connections (within either Apache or NGINX).
Who is effected
It’s difficult to say exactly how many potential websites can be effected by this attack, so here are some statistics from Netcraft. The following statistics are true as of April, 2014:
- 37.74% of web servers run Apache
- 15.25% of web servers run NGINX
- PHP is used by 81.9% of all identified websites (* Reference: Usage Stats and Market Share W3 Techs)
- Deduction: 82% (PHP) of 53% (of all websites) leaves 64% of all websites vulnerable to this attack.
Why this attack works: PHP and dynamic content handling
In most environments, dynamic (PHP) content is handled one of two ways. Either with PHP-FPM or PHP FastCGI (mod_fcgid). While modern control panels on shared hosting such as Plesk, Cpanel, ISPConfig have started moving towards PHP-FPM as the standard for dynamic content processing, the majority (by default) provide processing of PHP content through FastCGI.
FastCGI / mod_fcgid
A FastCGI application is executed outside of the web server (Apache or other wise), and waits for requests from the web server using a socket. When FastCGI/PHP-CGI is tuned properly, it will ignore requests instead of continuing to allocate memory. During that time, clients will time out. Things will go back to normal when the heavy traffic subsides.
One important item to note: When PHP-CGI runs out of available processes, Apache will kick in and start spawning additional processes to try and meet the demand. This process quickly fills the server with memory heavy httpd processes (which include the whole PHP interpreter embedded in each process). The maximum number of Apache processes spawned will be the number set in your ServerLimit or MaxClients setting (usually 256 or 512). It will take the web server a very long time to recover from launching 256 Apache processes..
PHP-FPM (FastCGI Process Manager)
FPM (FastCGI Process Manager) is an alternative PHP FastCGI implementation with some additional features (mostly) useful for heavy-loaded sites. In my own experience, PHP-FPM is considerably faster in processing and handling PHP.
Setting up the attack
We have developed a Proof of Concept PHP attack script that can be run via command line. Special thanks for d4rk0s for his excellent work in PHP coding.
First, download PHPStress from GitHub
or clone the repo:
git clone https://github.com/nightlionsecurity/phpstress phpstress
To execute the attack, set your target URL and time delay parameters and the script will do the rest. In order to achieve the results below, the script needs to be run using a 0 delay for request and delay parameters.
php phpstress.php www.targeturl.com -d 0 -r 0
In both cases, the FPM and/or CGI processes are completely consumed by the requests; the server’s memory is maxed out, and everything grinds to a halt. In the case of Apache or NGINX servers running PHP-FCGI, there are some interesting things to note.
On PHP-FPM servers, the maximum number of spawned processes is maintained based on what is in the config file. If your
pm.max_children = 25, then the maximum number of spawned processes will stay at 25.
Depending on how the timeout settings are set, the processes will stay open for at least that amount of time following the attack (generally two minutes).
The results with FastCGI are much more interesting. As the FastCGI buffer filles up, the requests seem to get queued and processed by Apache. Rather than continuing to spawn more FastCGI processes, Apache goes into its MaxClients directive and launches processes to match. The result is a completely overrun server with hundreds of spawned Apache processes. See below.
Settings to review for mitigation
Here are some of the standard config settings for NGINX and FastCGI that you will want to tweak. The settings below are the default values, they are NOT optimal and should be tweaked to fit your needs. I generally keep my timeout settings to 1-2 seconds.
# Number of seconds of idle time before a process is terminated FcgidIOTimeout 1000 # maximum period of time the module will wait while trying to read from or write to a FastCGI application FcgidMaxProcessesPerClass 100 #maximum number of processes per class (user) FcgidIdleTimeout 240 # application processes which have not handled a request for this period of time will be terminated FcgidProcessLifeTime 3600 # maximum lifetime of a single process (seconds) FcgidMaxProcesses 1000 #maximum number of FastCGI application processes which can be active at one time.
Apache – httpd.conf
Timeout 60 KeepAliveTimeout 15 KeepAlive Off MaxKeepAliveRequests 100 StartServers 8 MinSpareServers 5 MaxSpareServers 20 ServerLimit 256 MaxClients 256
; Time limit for child processes to wait for a reaction on signals from master. ; Available units: s(econds), m(inutes), h(ours), or d(ays) ; Default Unit: seconds ; Default Value: 0 process_control_timeout = 10s
; By default use ondemand spawning (this requires php-fpm >= 5.3.9) pm = ondemand pm.max_children = 50 pm.process_idle_timeout = 60s