深入剖析 pydash 原型链污染(五):漏洞利用与防御实战
引言:原型链污染的威胁演变
在 JavaScript 生态中,原型链污染(Prototype Pollution)已成为近年来最危险的漏洞类型之一。从 Lodash 到 pydash(Python 实现的 Lodash 兼容库),攻击者通过构造恶意输入篡改对象原型,进而控制应用程序行为。2022 年披露的 pydash 漏洞(CVE-2022-24706)允许攻击者通过 _.mergeWith 和 _.setWith 函数注入任意属性,甚至覆盖 __proto__ 或 constructor 等关键属性,导致远程代码执行(RCE)或拒绝服务(DoS)。本文将通过漏洞复现、攻击链分析、防御机制对比三个维度,揭示其技术本质与防御策略。
一、漏洞复现:从代码注入到 RCE
1.1 漏洞触发条件与 PoC 构造
pydash 的漏洞本质在于其 mergeWith 函数未正确处理 __proto__ 属性。当攻击者提交包含 __proto__ 的恶意 JSON 时,库会错误地将该属性合并到目标对象的原型链中。以下是典型的攻击场景:
from pydash import mergeWith
# 正常业务逻辑:合并用户配置
user_config = {"theme": "dark"}
new_config = {"theme": "light"}
merged = mergeWith(user_config, new_config)
print(merged) # 输出: {'theme': 'light'}
# 恶意构造:注入原型链污染
malicious_input = {"__proto__": {"exploit": "alert('hacked')"}}
merged = mergeWith(user_config, malicious_input)
print(merged) # 输出: {'__proto__': {'exploit': "alert('hacked')"}}
此时,user_config 的原型链已被污染。若此对象被传递给前端 JavaScript 代码(如通过 JSONP 或 API 响应),攻击者可通过以下方式触发 RCE:
// 前端代码(假设从 API 获取 user_config)
const userConfig = JSON.parse(response.data);
userConfig.exploit(); // 执行恶意代码
1.2 攻击链的完整实现
攻击者需满足三个条件:
输入点:应用程序使用 pydash 的
mergeWith或setWith处理用户可控数据(如 API 参数、文件上传)。原型访问:污染后的对象被传递给前端或其他依赖原型链的模块。
恶意行为触发:前端代码通过
Object.getPrototypeOf或直接访问污染属性执行攻击。
完整攻击链示例:
攻击者构造恶意请求:
POST /api/config HTTP/1.1
Content-Type: application/json
{
"__proto__": {
"__proto__": {
"eval": "console.log('RCE')"
}
}
}后端使用 pydash 合并数据:
from pydash import mergeWith
config = mergeWith(default_config, request.json)前端通过 API 获取配置并执行:
const config = await fetch('/api/config').then(res => res.json());
config.eval(); // 输出: "RCE"
1.3 漏洞影响范围
高危场景:云服务配置、权限系统、动态模板渲染。
典型案例:
某云服务因未过滤
__proto__导致攻击者篡改权限系统,获得管理员权限。前端框架通过污染后的对象渲染模板,触发 XSS 或逻辑漏洞。
二、攻击链分析:从原型篡改到 RCE
2.1 原型链污染的核心机制
JavaScript 的原型链继承机制允许对象通过 [[Prototype]] 链访问属性。当攻击者注入 __proto__ 时,相当于在目标对象的原型链中插入恶意属性。例如:
const obj = {};
Object.setPrototypeOf(obj, { malicious: "function() { console.log('hacked') }" });
obj.malicious(); // 输出: "hacked"
pydash 的漏洞在于其 mergeWith 函数未过滤 __proto__,导致恶意属性被直接合并到原型链中。
2.2 攻击链的四个阶段
属性注入:通过
__proto__或constructor注入恶意属性。原型篡改:修改
Object.prototype或Array.prototype影响全局对象。逻辑滥用:利用污染后的属性触发业务逻辑漏洞(如权限绕过)。
代码执行:通过
eval、Function构造函数或 DOM 操作执行恶意代码。
示例攻击链:
注入
__proto__.toString = function() { return '恶意代码'; }。前端调用
JSON.stringify(config)时触发toString,执行恶意代码。恶意代码通过
fetch或XMLHttpRequest泄露数据。
2.3 绕过防御机制的技巧
双重污染:同时注入
__proto__和constructor提高成功率。属性名混淆:使用 Unicode 编码或特殊字符绕过过滤。
链式攻击:结合其他漏洞(如 XXE、SSRF)扩大攻击面。
绕过示例:
const payload = {
"__proto__": {
"constructor": {
"prototype": {
"malicious": "console.log('bypass')"
}
}
}
};
三、防御机制对比:从修复到纵深防御
3.1 pydash 的官方修复方案
pydash 的修复分为两步:
过滤
__proto__:在mergeWith和setWith中显式过滤__proto__属性。严格模式:默认启用
strict参数,禁止修改原型链。
修复代码示例:
def mergeWith(target, source, customizer=None, strict=False):
if strict:
if "__proto__" in source:
raise ValueError("Prototype pollution attempt")
# 原有合并逻辑
3.2 防御措施对比分析
防御措施 | 优点 | 缺点 |
|---|---|---|
过滤 | 直接阻断攻击向量 | 可能误杀合法数据(如元数据) |
严格模式 | 全局禁用原型修改 | 需重构代码以适应新逻辑 |
输入验证 | 符合最小权限原则 | 对复杂数据结构难以全覆盖 |
沙盒环境 | 隔离恶意代码 | 性能开销大,不适用于实时系统 |
3.3 最佳实践:纵深防御体系
输入验证:
使用白名单验证输入字段(如仅允许
theme、layout等业务字段)。示例:
ALLOWED_FIELDS = {"theme", "layout", "timeout"}
def validate_input(data):
for key in data:
if key not in ALLOWED_FIELDS:
raise ValueError(f"Invalid field: {key}")深度防御:
结合过滤、严格模式、沙盒等多层防护。
示例:
from pydash import mergeWith
from pydash import setWith
def safe_merge(target, source):
validate_input(source)
return mergeWith(target, source, strict=True)安全编码规范:
禁止直接使用
Object.setPrototypeOf或__proto__。使用
Object.create创建新对象。
3.4 云服务的防御实践
AWS 的安全措施:
使用 AWS Shield 防御 DDoS 攻击。
通过 AWS WAF 过滤恶意请求(如包含
__proto__的 payload)。示例规则:
{
"Rule": {
"Name": "BlockProtoPollution",
"Priority": 1,
"Statement": {
"SqlInjectionMatchStatement": {
"FieldToMatch": {
"Data": "__proto__"
}
}
}
}
}Azure 的解决方案:
部署 Azure DDoS Protection 和 Web Application Firewall。
监控异常请求(如大量含
__proto__的 API 调用)。
四、总结:从漏洞到防御的闭环
pydash 的原型链污染漏洞揭示了现代 Web 应用安全的核心挑战:攻击者通过篡改数据模型控制应用程序行为。防御此类漏洞需采取以下措施:
库升级:及时更新 pydash 至修复版本。
输入验证:严格过滤用户输入,禁止非业务字段。
纵深防御:结合过滤、严格模式、沙盒等多层机制。
安全监控:实时检测异常请求(如大量
__proto__注入尝试)。
通过技术加固与安全意识提升,我们可将原型链污染的风险降至最低,构建更安全的 Web 应用生态。