PHP version 5.3.1 was just released. This release contains a patch for a denial of service condition we’ve reported some time ago.
The problem is related with PHP’s handling of RFC 1867 (Form-based File Upload in HTML).
When you send a POST request to a PHP script with the content-type of “multipart/form-data” and include a list of files in that request, PHP will create a temporary file for each file from the request.
PHP will create those files regardless if the script can handle file uploading or not.
After the script was executed, the temporary files will be deleted.
The problem is that you can include a very large number of files in the request.
PHP will need to create those files before the script is executed and delete them afterwards.
The denial of service condition appears when you create a bunch of requests, each containing a large number (15000+) of files.
When you send these requests to the web server, the web server collapses and stops responding becasue it has to process (create & delete) an insane number of files in a very short period of time.
Any website that runs PHP and where file uploading is enabled (which is the default configuration) is vulnerable.
You don’t need to have a file upload script.
PHP does include 2 configuration settings that are related to this situation: upload_max_filesize and post_max_size.
However, these are not enough to protect us against this denial of service attack.
Workarounds
————
Currently, I’m aware of three workarounds for this problem:
1. Disable file uploads
If you don’t need file uploading, you can disable this feature from php.ini
file_uploads = Off
2. Install PHP 5.3.1
If you cannot disable file uploading on your website, it’s recommended to install the latest version of PHP.
PHP 5.3.1 includes a patch for this problem:
– Added “max_file_uploads” INI directive, which can be set to limit the number of file uploads per-request to 20 by default, to prevent possible DOS via temporary file exhaustion.
3. Install Suhosin PHP extension
The Suhosin PHP extension has an option named “suhosin.upload.max_uploads”.
This option defines the maximum number of files that may be uploaded with one request and by default is set to 25.
Suhosin PHP extension should not be confused with the Suhosin Patch which does not protect against this attack.
Quote from the hardened-php website:
“Suhosin comes in two independent parts, that can be used separately or in combination.
The first part is a small patch against the PHP core, that implements a few low-level protections against bufferoverflows or format string vulnerabilities and the second part is a powerful PHP extension that implements all the other protections.”
It’s recommended to apply one of the workarounds described above as soon as possible.
Bellow are some conclusions I’ve gathered from testing this on different systems.
Conclusions and real life results
———————————-
This attack can make the web server unresponsive in a short period of time (under 2 minutes) with a very small number of requests.
Also, this attack doesn’t leave any obvious tracks in the logs (only a bunch of POST requests) and can be executed through a proxy server.
Some operating systems will handle this condition very badly.
For example in one case (a FreeBSD 7.1), the network stack completely crashed and the server was unreachable from the local network.
I had to manually restart it from the console.
On Linux (Ubuntu), the web server will not be reachable for hours after being attacked for 1-2 minutes.
Real life results:
1. PHP on Linux (Ubuntu 8.10)
=============================
PHP Version 5.2.6-2ubuntu4.3
Timeline:
14:50 – started the attack
14:51 : web server is no longer responsive.
load average: 102.02, 30.68, 10.68
14:52 :  web server is not responsive.
load average: 129.95, 49.29, 18.05
14:52 – attack is aborted
14:53 – web server is not responsive.
load average: 143.58, 67.90, 26.41
14:54 – web server is not responsive.
load average: 149.60, 89.58, 37.93
16:05 – web server is not responsive.
load average: 151.64, 120.91, 60.94
I wanted to check how many temporary files were created:
$ls -la /tmp/php* | wc -l
-bash: /bin/ls: Argument list too long
0
I’ve created a script to count the files:
$php count_files_from_dir.php /tmp/php*
2.419.649
So, one hour later, the web server is not responsive and there are 2.419.649 temporary files.
If you restart the web server, these files are not deleted.
2. PHP on FreeBSD 7.2
======================
PHP Version 5.2.9
Timeline:
14:00 – attack is started.
14:01 – web server is no longer responsive (Chrome message: Error 101 (net::ERR_CONNECTION_RESET): Unknown error.))
load average: 87:22, 22.61, 9.9
14:02 – attack is aborted.
14:06 – web server is no longer responsive.
load averages: 45.42, 42.35, 22.59
14:11 – web server is not responsive.
load averages: 26.77, 35.78, 23.49
The system is slowed down to a crawl.
Basically you cannot even write a command in a remote PUTTY session.
14:17 – web server is not responsive.
The console is continuously displaying kernel error messages like:
swap_pager_getswapspace(2): failed
swap_pager_getswapspace(16): failed
swap_pager_getswapspace(3): failed
pid 61248 (httpd), uid 80 inumber 5 on /var: out of inodes
pid 61251 (httpd), uid 80 inumber 5 on /var: out of inodes
pid 61146 (httpd), uid 80 inumber 5 on /var: out of inodes
pid 61103 (httpd), uid 80 inumber 5 on /var: out of inodes
pid 61103 (httpd), uid 80 inumber 5 on /var: out of inodes
pid 61063 (httpd), uid 80 inumber 5 on /var: out of inodes
pid 61101 (httpd), uid 80 inumber 5 on /var: out of inodes
14:23 – web server is responsive.
load averages:  0.79, 29.10, 37.13
I was trying to count the number of temporary files from the server:
$ls -la /var/tmp/php* | wc -l
-bash: /bin/ls: Argument list too long
0
$ls -la /var/tmp/php
Display all 117490 possibilities? (y or n)
So, there are 117490 temporary files left on the server.
One another FreeBSD 7.1 server I’ve had a very weird situation:
After one minute the network stack crashed and the server was unreachable from the local network.
I had to manually restart the network interface from the console.
The message log contains error messages like:
Oct 23 10:55:17 daemon kernel: Approaching the limit on PV entries, consider increasing either the vm.pmap.shpgperproc or the vm.pmap.pv_entry_max tunable.
Oct 23 10:56:17 daemon kernel: Approaching the limit on PV entries, consider increasing either the vm.pmap.shpgperproc or the vm.pmap.pv_entry_max tunable.
Oct 23 10:57:17 daemon kernel: Approaching the limit on PV entries, consider increasing either the vm.pmap.shpgperproc or the vm.pmap.pv_entry_max tunable.
Oct 23 10:58:17 daemon kernel: Approaching the limit on PV entries, consider increasing either the vm.pmap.shpgperproc or the vm.pmap.pv_entry_max tunable.
Oct 23 10:59:17 daemon kernel: Approaching the limit on PV entries, consider increasing either the vm.pmap.shpgperproc or the vm.pmap.pv_entry_max tunable.
Oct 23 11:00:17 daemon kernel: Approaching the limit on PV entries, consider increasing either the vm.pmap.shpgperproc or the vm.pmap.pv_entry_max tunable.
Oct 23 11:01:17 daemon kernel: Approaching the limit on PV entries, consider increasing either the vm.pmap.shpgperproc or the vm.pmap.pv_entry_max tunable.
Oct 23 11:02:17 daemon kernel: Approaching the limit on PV entries, consider increasing either the vm.pmap.shpgperproc or the vm.pmap.pv_entry_max tunable.
3. PHP on Windows: XAMPP
=========================
XAMPP for Windows setup filename:  xampp-win32-1.7.2.exe
PHP Version 5.3.0
Timeline:
12:30 – started the attack
12:30 + few seconds: CPU usage => 100%
In a few seconds, the web server is not responding anymore, 65535 temporary files are created and no more files could be created anymore.
on XAMPP on Windows, PHP is creating the temporary files in C:xampptmp (if your XAMPP installation was in C:xampp)
The filenames are named phpXXXX.tmp (where X’s charset is ‘a’-‘z’, ‘A’-‘Z’, ‘0’-‘9’). Example: php1A00.tmp
This 4 char random number is a limitation of PHP on Windows.
PHP on Unix is using 6 chars for its temporary filenames so it doesn’t reach this condition.
12:31 – attack is aborted
12:39 – CPU usage is still 100%, web server is not responsive.
13:08 – CPU usage is still 100%, web server is responsive.
14:08 – CPU usage is 97%
14:34 – CPU usage is 97%
Two hours later the CPU usage didn’t get back to normal.
However, the web server is responding.
After I manually restart the Apache process, CPU usage gets back to normal.
However, those 65535 temporary files were not deleted.
4. PHP on OpenBSD 4.6
======================
PHP Version 5.2.10
Timeline:
12:00 – started the attack
12:00 + few seconds: CPU usage => 100%
12:01 – attack is aborted
12:01 – web server is no longer responsive.
load averages: 120.42, 50.35, 20.59
12:04 – web server is no longer responsive.
load averages: 147.17, 80.74, 36.46
The system is slowed down to a crawl.
12:06 – web server is responsive.
load averages: 122.59, 96.03, 48.31
So, at this point the web server is working again but the system is still slowed down to a crawl.
But it can serve web pages.
12:07 – web server is responsive.
load averages: 63.67, 85.01, 47.26
12:10 – web server is responsive.
load averages:  6.56, 52.75, 40.03
12:12 – web server is responsive.
load averages:  0.55, 16.36, 26.50
The system is back to normal.
OpenBSD recovered very well from this attack, the effect only lasted for a few minutes.
Why is that? Because of the Suhosin PHP extension. OpenBSD has this extension enabled by default.
LFI2RCE
——–
In some cases, this attack can be used to convert a local file inclusion exploit to remote code execution.
Most operating systems don’t delete the temporary files created by this attack even after you restart the web server.
Therefore, a large number of temporary files are left in the temporary directory (usually /tmp for Unix systems).
You can try to guess the name of one of these filenames and include it.
For this to work, all the uploaded files should contain some PHP script like: <?php eval($_REQUEST[x]); ?>.
On Windows systems there are only 4 characters used for generating temporary files (phpXXXX.tmp).
After the web server is responsive again, there are 65.535 temporary files in the temporary directory.
Therefore, it’s possible to guess the name of one of those files and include it.
On Unix, 6 characters are used for the temporary filenames and therefore it’s almost impossible to guess the name of the temporary filesname.
Or at least, it could take a very long time. As a funny note, I managed to exploit this on a web server with 800.000 temporary files.
After randomly guessing for 5 minutes I managed to guess the name of one of the temp files and execute PHP code.
Proof of concept
—————–
I’m not going to publish the proof of concept Python script.
If you have a valid reason why you would need the proof of concept, you can contact me at this email address (bogdan@acunetix.com).

PHP version 5.3.1 was just released. This release contains a patch for a denial of service condition we’ve reported on 27 th October 2009. The problem is related with PHP’s handling of RFC 1867 (Form-based File Upload in HTML).

When you send a POST request to a PHP script with the content-type of “multipart/form-data”

and include a list of files in that request, PHP will create a temporary file for each file from the request. PHP will create those files regardless if the script can handle file uploading or not. After the script was executed, the temporary files will be deleted.

The problem is that you can include a very large number of files in the request. PHP will need to create those files before the script is executed and delete them afterwards.

The denial of service condition appears when you create a bunch of requests, each containing a large number (15000+) of files.

When you send these requests to the web server, the web server collapses and stops responding because it has to process (create & delete) an insane number of files in a very short period of time.

Any website that runs PHP and where file uploading is enabled (which is the default configuration) is vulnerable. You don’t need to have a file upload script.

PHP does include 2 configuration settings that are related to this situation: upload_max_filesize and post_max_size.
However, these are not enough to protect us against this denial of service attack.

Workarounds

Currently, I’m aware of three workarounds for this problem:

1. Disable file uploads

If you don’t need file uploading, you can disable this feature from php.ini

file_uploads = Off

2. Install PHP 5.3.1

If you cannot disable file uploading on your website, it’s recommended to install the latest version of PHP.

PHP 5.3.1 includes a patch for this problem:

– Added

“max_file_uploads”

INI directive, which can be set to limit the number of file uploads per-request to 20 by default, to prevent possible DOS via temporary file exhaustion.

3. Install Suhosin PHP extension

The Suhosin PHP extension has an option named

“suhosin.upload.max_uploads”

. This option defines the maximum number of files that may be uploaded with one request and by default is set to 25. Suhosin PHP extension should not be confused with the Suhosin Patch which does not protect against this attack.

Quote from the hardened-php website:

“Suhosin comes in two independent parts, that can be used separately or in combination.

The first part is a small patch against the PHP core, that implements a few low-level protections against bufferoverflows or format string vulnerabilities and the second part is a powerful PHP extension that implements all the other protections.”

It’s recommended to apply one of the workarounds described above as soon as possible. Below are some conclusions I’ve gathered from testing this on different systems.

Conclusions and real life results

This attack can make the web server unresponsive in a short period of time (under 2 minutes) with a very small number of requests.

Also, this attack doesn’t leave any obvious tracks in the logs (only a bunch of POST requests) and can be executed through a proxy server.

Some operating systems will handle this condition very badly.

For example in one case (a FreeBSD 7.1), the network stack completely crashed and the server was unreachable from the local network. I had to manually restart it from the console.

On Linux (Ubuntu), the web server will not be reachable for hours after being attacked for 1-2 minutes.

Real life results:

1. PHP on Linux (Ubuntu 8.10)

PHP Version 5.2.6-2ubuntu4.3

Timeline:

14:50 – started the attack
14:51 : web server is no longer responsive.

load average: 102.02, 30.68, 10.68

14:52 :  web server is not responsive.

load average: 129.95, 49.29, 18.05

14:52 – attack is aborted
14:53 – web server is not responsive.

load average: 143.58, 67.90, 26.41

14:54 – web server is not responsive.

load average: 149.60, 89.58, 37.93

16:05 – web server is not responsive.

load average: 151.64, 120.91, 60.94

I wanted to check how many temporary files were created:

$ls -la /tmp/php* | wc -l
-bash: /bin/ls: Argument list too long
0

I’ve created a script to count the files:

$php count_files_from_dir.php /tmp/php*
2.419.649

So, one hour later, the web server is not responsive and there are 2.419.649 temporary files.
If you restart the web server, these files are not deleted.

2. PHP on FreeBSD 7.2

PHP Version 5.2.9

Timeline:
14:00 – attack is started.

14:01 – web server is no longer responsive (Chrome message: Error 101 (net::ERR_CONNECTION_RESET): Unknown error.))

load average: 87:22, 22.61, 9.9

14:02 – attack is aborted.
14:06 – web server is no longer responsive.

load averages: 45.42, 42.35, 22.59

14:11 – web server is not responsive.

load averages: 26.77, 35.78, 23.49

The system is slowed down to a crawl.

Basically you cannot even write a command in a remote PUTTY session.

14:17 – web server is not responsive.

The console is continuously displaying kernel error messages like:

swap_pager_getswapspace(2): failed
swap_pager_getswapspace(16): failed
swap_pager_getswapspace(3): failed

pid 61248 (httpd), uid 80 inumber 5 on /var: out of inodes
pid 61251 (httpd), uid 80 inumber 5 on /var: out of inodes
pid 61146 (httpd), uid 80 inumber 5 on /var: out of inodes
pid 61103 (httpd), uid 80 inumber 5 on /var: out of inodes
pid 61103 (httpd), uid 80 inumber 5 on /var: out of inodes
pid 61063 (httpd), uid 80 inumber 5 on /var: out of inodes
pid 61101 (httpd), uid 80 inumber 5 on /var: out of inodes

14:23 – web server is responsive.

load averages:  0.79, 29.10, 37.13

I was trying to count the number of temporary files from the server:
$ls -la /var/tmp/php* | wc -l
-bash: /bin/ls: Argument list too long
0

$ls -la /var/tmp/php

Display all 117490 possibilities? (y or n)
So, there are 117490 temporary files left on the server.

One another FreeBSD 7.1 server I’ve had a very weird situation:

After one minute the network stack crashed and the server was unreachable from the local network. I had to manually restart the network interface from the console. The message log contains error messages like:

Oct 23 10:55:17 daemon kernel: Approaching the limit on PV entries, consider increasing either the vm.pmap.shpgperproc or the vm.pmap.pv_entry_max tunable.
Oct 23 10:56:17 daemon kernel: Approaching the limit on PV entries, consider increasing either the vm.pmap.shpgperproc or the vm.pmap.pv_entry_max tunable.
Oct 23 10:57:17 daemon kernel: Approaching the limit on PV entries, consider increasing either the vm.pmap.shpgperproc or the vm.pmap.pv_entry_max tunable.
Oct 23 10:58:17 daemon kernel: Approaching the limit on PV entries, consider increasing either the vm.pmap.shpgperproc or the vm.pmap.pv_entry_max tunable.
Oct 23 10:59:17 daemon kernel: Approaching the limit on PV entries, consider increasing either the vm.pmap.shpgperproc or the vm.pmap.pv_entry_max tunable.
Oct 23 11:00:17 daemon kernel: Approaching the limit on PV entries, consider increasing either the vm.pmap.shpgperproc or the vm.pmap.pv_entry_max tunable.
Oct 23 11:01:17 daemon kernel: Approaching the limit on PV entries, consider increasing either the vm.pmap.shpgperproc or the vm.pmap.pv_entry_max tunable.
Oct 23 11:02:17 daemon kernel: Approaching the limit on PV entries, consider increasing either the vm.pmap.shpgperproc or the vm.pmap.pv_entry_max tunable.

3. PHP on Windows: XAMPP

XAMPP for Windows setup filename:  xampp-win32-1.7.2.exe

PHP Version 5.3.0

Timeline:

12:30 – started the attack
12:30 + few seconds: CPU usage => 100%

In a few seconds, the web server is not responding anymore, 65535 temporary files are created and no more files could be created anymore.

On XAMPP for Windows, PHP is creating the temporary files in C:xampptmp (if your XAMPP installation was in C:xampp)

The files are named phpXXXX.tmp (where X’s charset is ‘a’-‘z’, ‘A’-‘Z’, ‘0’-‘9’).
Example: php1A00.tmp

This 4 char random number is a limitation of PHP on Windows. PHP on Unix is using 6 chars for its temporary filenames so it doesn’t reach this condition.

12:31 – attack is aborted
12:39 – CPU usage is still 100%, web server is not responsive.
13:08 – CPU usage is still 100%, web server is responsive.
14:08 – CPU usage is 97%
14:34 – CPU usage is 97%

Two hours later the CPU usage didn’t get back to normal. However, the web server is responding. After I manually restart the Apache process, CPU usage gets back to normal.

However, those 65535 temporary files were not deleted.

4. PHP on OpenBSD 4.6

PHP Version 5.2.10

Timeline:

12:00 – started the attack
12:00 + few seconds: CPU usage => 100%
12:01 – attack is aborted
12:01 – web server is no longer responsive.

load averages: 120.42, 50.35, 20.59

12:04 – web server is no longer responsive.

load averages: 147.17, 80.74, 36.46

The system is slowed down to a crawl.

12:06 – web server is responsive.

load averages: 122.59, 96.03, 48.31

So, at this point the web server is working again but the system is still slowed down to a crawl.
But it can serve web pages.

12:07 – web server is responsive.

load averages: 63.67, 85.01, 47.26

12:10 – web server is responsive.

load averages:  6.56, 52.75, 40.03

12:12 – web server is responsive.

load averages:  0.55, 16.36, 26.50

The system is back to normal.
OpenBSD recovered very well from this attack, the effect only lasted for a few minutes.
Why is that? Because of the Suhosin PHP extension. OpenBSD has this extension enabled by default.

LFI2RCE

In some cases, this attack can be used to convert a local file inclusion exploit to remote code execution. Most operating systems don’t delete the temporary files created by this attack even after you restart the web server. Therefore, a large number of temporary files are left in the temporary directory (usually /tmp for Unix systems). You can try to guess the name of one of these filenames and include it.

For this to work, all the uploaded files should contain some PHP script like: <?php eval($_REQUEST[x]); ?>.
On Windows systems there are only 4 characters used for generating temporary files (phpXXXX.tmp). After the web server is responsive again, there are 65.535 temporary files in the temporary directory.Therefore, it’s possible to guess the name of one of those files and include it.

On Unix, 6 characters are used for the temporary filenames and therefore it’s almost impossible to guess the name of the temporary filesname. Or at least, it could take a very long time.

As a funny note, I managed to exploit this on a web server with 800.000 temporary files.  After randomly guessing for 5 minutes I managed to guess the name of one of the temp files and execute PHP code.

Proof of concept

I’m not going to publish the proof of concept Python script. If you have a valid reason why you would need the proof of concept, you can contact me at this email address (bogdan [at] acunetix.com).

SHARE THIS POST
THE AUTHOR
Bogdan Calin

Acunetix developers and tech agents regularly contribute to the blog. All the Acunetix developers come with years of experience in the web security sphere.

Comments are closed.