前言
本文为玲珑安全原创,未经授权不可转载。请进行合法的渗透测试,玲珑安全公众号、芳华绝代安全团队公众号及文章作者不承担任何因利用本文分享的内容而造成的任何后果。
漏洞原理
在 JavaScript 中,使用原型继承模型与许多其他语言所采用的基于类的继承模型有着明显的区别。
在 JavaScript 中,对象是由一组称为“属性”的键值对构成的集合。举例来说,可以定义一个包含用户名和用户标识的 user 对象:
const
user = {
username
:
"ice"
,
useridentify
:
1001
,
addOrder
:
function
(
)
{
// 执行添加订单操作的函数
}
};
在 JavaScript 中,每个对象(如 B)都链接到另一个对象(如 A),A 被称为 B 的原型。当对象 B 分配了原型之后,它会自动继承其指定原型的所有属性。
举个例子,当 myString 对象继承了 String.prototype 原型之后,由于该原型中包含 toLowerCase() 方法,因此 myString 对象也可以直接调用该方法:
let
myString =
"Ice"
;
Object
.getPrototypeOf(myString);
// String.prototype
myString.toLowerCase();
// ice
在 JavaScript 中,当引用对象的属性时,JavaScript 引擎首先尝试直接在对象本身上查找该属性。如果对象本身没有匹配的属性,JavaScript 引擎会在对象的原型链上继续查找。
在 JavaScript 中,由于对象 B 的原型是另一个对象 A,因此对象 A 也可能有自己的原型,以此类推。由于 JavaScript 中几乎所有的东西都是对象,所以原型链最终会回溯到顶层的 Object.prototype,而其原型则是 null。
总结来说:通过原型链的机制,JavaScript 实现了对象之间的继承关系,使得对象可以共享和继承其原型链上的属性和方法。这种原型继承模型为 JavaScript 带来了灵活性和强大的功能。
JavaScript 的原型链污染思路即为:指向全局对象原型添加任意属性,然后这些属性可能会被用户定义的对象继承,从而实现敏感操作。
而恰好,由于每个对象都有一个特殊的属性 __proto__,我们可以使用它来访问其原型及其属性,实现自定义或覆盖内置方法,甚至可以添加新方法。
举例如下:
// 使用
__proto__
属性回溯原型链
myString.
__proto__
// String.prototype
myString.
__proto__
.
__proto__
// Object.prototype
myString.
__proto__
.
__proto__
.
__proto__
// null
漏洞危害
原型链污染虽然通常无法作为独立漏洞被利用,但它能够让我们控制本来无法访问的对象属性。如果应用程序随后以不安全的方式处理用户控制的属性,则可能与其他漏洞相互影响。在客户端 JavaScript 中,这将导致 DOM XSS,而服务器端原型污染甚至有可能导致远程代码执行。
漏洞利用
我们通常在以下污染源中实现污染:
1、URL 中的查询字符串或片段字符串(哈希)
2、基于 JSON 的输入
3、Web消息
No.1
通过URL进行原链型污染
当 URL 中包含像 ?__proto__[evilProperty]=payload 这样的恶意构造的查询字符串时,URL 解析器可能会将 __proto__ 解释为字符串:
{
string1
:
'ice',
string2
:
'JavaScript',
__proto__
:
{
evilProperty
:
'payload'
}
}
但在执行递归合并操作时,URL解析器会使用类似于 targetObject.__proto__.evilProperty = 'payload' 的语句来分配 evilProperty 的值。
在这个赋值过程中,原型对象的evilProperty属性将被覆盖或添加(如果不存在)。假设目标对象使用默认的 Object.prototype,那么 JavaScript 运行时,所有对象都会继承这个 evilProperty 属性,除非它们已经拥有了相同名字的属性。
No.2
通过JSON输入造成原链型污染
假设通过网页消息注入了以下恶意的 JSON:
jsonCopy Code{
"__proto__"
: {
"evilProperty"
:
"payload"
}
}
如果这个 JSON 经过 JSON.parse() 方法转换为 JavaScript 对象,得到的对象实际上将具有一个键为 __proto__ 的属性:
const
objectLiteral = {
__proto__
: {
evilProperty
:
'payload'
}};
const
objectFromJson =
JSON
.parse(
'{"__proto__": {"evilProperty": "payload"}}'
);
objectLiteral.hasOwnProperty(
'__proto__'
);
// false
objectFromJson.hasOwnProperty(
'__proto__'
);
// true
通过 JSON.parse() 创建的对象随后被合并到现有对象时,可能导致原型污染。
漏洞实例
定义user对象,它从父对象 Object.Prototype 继承了所有属性:
let
user = {
'name'
:
'mayank'
,
'age'
:
25
}
user.name
user.name.toString()
覆盖上层对象的toString()属性:
user.name.__proto__.toString =
()
=>
{alert(
'ice'
)}
访问用户属性,实现XSS:
user
.name
.toString
()
缓解措施
1、确保用户输入不包含影响原型链的 __proto__、构造函数或原型。
2、使用 Object.freeze(Object.prototype) 冻结 Object.prototype(由于兼容性问题,不推荐使用)
原文始发于微信公众号(芳华绝代安全团队):JavaScript原链型污染详解
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论