// HackTricks · Web Pentesting

LFI2RCE via PHPSESSIONUPLOADPROGRESS

LFI2RCE via PHP_SESSION_UPLOAD_PROGRESS

Basic Info

With an LFI, PHP’s upload-progress feature can create a session file even when the application did not otherwise start a session and session.auto_start=Off. This requires session.upload_progress.enabled=On, a multipart upload containing the configured progress field (normally PHP_SESSION_UPLOAD_PROGRESS), and a controllable session identifier.[4]

$ curl http://127.0.0.1/ -H 'Cookie: PHPSESSID=iamorange'
$ ls -a /var/lib/php/sessions/
. ..
$ curl http://127.0.0.1/ -H 'Cookie: PHPSESSID=iamorange' -d 'PHP_SESSION_UPLOAD_PROGRESS=blahblahblah'
$ ls -a /var/lib/php/sessions/
. ..
$ curl http://127.0.0.1/ -H 'Cookie: PHPSESSID=iamorange' -F 'PHP_SESSION_UPLOAD_PROGRESS=blahblahblah'  -F 'file=@/etc/passwd'
$ ls -a /var/lib/php/sessions/
. .. sess_iamorange

In the last example the session will contain the string blahblahblah

The progress value becomes part of serialized session data. If the LFI can include that session file while it exists, attacker-controlled PHP code in the value may execute.

[!TIP] PHP defaults session.upload_progress.cleanup to On, so progress data is removed as soon as the upload is processed. Exploitation is therefore a race unless cleanup has been disabled.[4]

The CTF

In the original CTF where this technique is described, winning the race was not enough: the loaded content also needed to start with the string @<?php.[1]

The default session.upload_progress.prefix adds upload_progress_ before the attacker-controlled key in the serialized session data, producing content such as upload_progress_controlledcontentbyattacker.

The trick to remove the initial prefix was to base64encode the payload 3 times and then decode it via convert.base64-decode filters, this is because when base64 decoding PHP will remove the weird characters, so after 3 times only the payload sent by the attacker will remain (and then the attacker can control the initial part).[1]

More information is available in the original writeup and the final exploit.[1][2]
Another writeup in https://spyclub.tech/2018/12/21/one-line-and-return-of-one-line-php-writeup/[3]

References

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.