the_open_door
coffee
ESEN
← Back to index

LFI to RCE via phpinfo() Race Condition

Full command arsenal in the cheatsheet

An LFI (local file inclusion) lets you read files, but on its own it does not execute code. The classic technique to turn it into RCE when PHP has file_uploads = On exploits one detail: when it receives a multipart/form-data, PHP saves the uploaded file to a temporary path (/tmp/phpXXXXXX) before the script decides what to do with it, and deletes it as soon as the request finishes. The file exists for just a few milliseconds.

The bridge is a page that exposes phpinfo(). Its output prints the $_FILES variable and, with it, the exact tmp_name of the temporary file just created. The attack chain is a race condition:

  1. POST a multipart to the page running phpinfo(), with a file that contains a webshell.
  2. In the same response, read the tmp_name that phpinfo() leaks.
  3. Include that tmp_name through the LFI before PHP deletes it.

If the LFI executes the temporary file as PHP, the webshell runs and you can launch the reverse shell. Doing this by hand is hard because the window is so narrow; it is automated with a loop that keeps uploading and trying to include, or directly with the reference public exploit (Insomnia Security's phpinfolfi.py), which spawns many threads to win the race:

python2.7 phpinfolfi.py <ip> 80 100    # 100 parallel threads

Requirements. An LFI that includes and executes the pointed file, file_uploads = On (the PHP default) and a page that reveals phpinfo() to leak the tmp_name. No writable upload directory is needed: the /tmp temporary is enough.

How to detect it. The signature is an anomalous volume of requests in a very short time (hundreds per second) alternating multipart POSTs to the phpinfo() page with GETs to the LFI parameter pointing at /tmp/php...: the race condition's brute-force pattern is obvious in the access log, and a WAF correlating bursts + inclusion of /tmp/ paths blocks it. On the host, the web process including a /tmp/phpXXXXXX temporary and spawning sh/bash right after is the confirmation (auditd/Sysmon for Linux). A page exposing phpinfo() at all is already the prior exposure to audit.

Defence. Never expose phpinfo() in production, disable file_uploads if unused and sanitise the include parameter so it cannot accept arbitrary paths. Grows out of an LFI and shares its goal with webshell upload.

Machines where it's used