读完p牛的 深入理解 JavaScript Prototype 污染攻击 还有点意犹未尽,看了一下参考链接里第一个,觉得还有翻译的价值,于是乎在这里与大家一同学习啦~
原文见:https://github.com/HoLyVieR/prototype-pollution-nsec18/
原型链污染攻击
BY OLIVIER ARTEAU
我是谁?
- 渗透测试人员&安全研究员
- 4年前我开始从事信息安全的工作,在此之前,我从事web开发的工作
计划(议程)
- 介绍JavaScript
- 什么允许了原型链污染?
- 怎样使它被利用?
- 缓解
介绍JavaScript
JavaScript中如何声明一个类
|
|
对象的基本原型
|
|
[1]: 就是function Dog(){ }
这一部分
[2]: 包括三个部分,1是function Dog(){ }
,2是Dog.prototype.talk = function(){return 42;}
,3是从Object
原型链继承的部分(JS是一个基于原型的语言,每个对象都有一个原型,并从原型继承方法和属性,最顶层的对象的原型为null)。
原型访问
|
|
(JS中可以通过数组下标的方式实现对对象属性的访问。)
原型链污染?
- “Object”和其他基本类型的扩展方法
- 如”prototype.js”等库
考虑下面这个糟糕的实现:
1234567Object.prototype.containsTheAnswer = function () {return this.hasOwnProperty("42");}var a = {"42":true};a.containsTheAnswer(); // true
原型链污染攻击
如果攻击者能够向对象的原型添加属性,那么会发生什么?
什么允许了原型链污染?
合并(merge)操作
|
|
(merge操作的一个简单实现:)
|
|
|
|
(
- 在JS中,查找对象的属性,如果在当前对象的全部属性中未找到,会继续向上,在对象的原型中寻找该属性,如果在对象的原型中,还未找到该属性,会继续寻找该对象原型的原型,直到找到,或是原型的原型为null。
- 如果某一个对象的原型A,被改变了,那么将会影响到所有原型链中存在A的对象。
var d = {};
但是d.polluted
值却为1,说明d对象的原型链中含有了polluted
属性,也就是:通过merge()函数,成功地在b的原型(Object)的属性中增加了名为polluted
的属性。- 为什么
var b = JSON.parse(xxx)
?见p牛文章
)
- 包括非常流行的库都受该漏洞影响,如lodash何Hoek
- 详细信息见paper。
克隆(clone)操作
|
|
|
|
- 只有一个库被发现受此问题影响。
- 详细内容见paper
路径分配操作
|
|
|
|
- By design
- 详细见paper中。
利用时间到了!
- 该研究的案例是Ghost CMS v1.19.2
- 这是一个大应用
- 它使用了在用户输入下易受攻击的库。
鉴别身份
- 第一步是找到受影响的库在哪里与用户输入一同被使用。
文件:/core/server/api/authentication.js
1234567function doReset(options){var data = options.data.passwordreset[0],resetToken = data.token,oldPassword = data.oldPassword,newPassword = data.newPassword;return settingsAPI.read(_.merge({key: 'db_hash'}, options))[...]
思考这一点,我们可以构建出payload的大致框架:
1234567891011121314PUT /ghost/api/v0.1/authentication/passwordreset HTTP/1.1Host: localhost:2368Content-Type: application/json; charset=UTF-8Connection: close{"passwordreset": [{"token": "MHx0ZXN0QHRlc3QuY29tfHRlc3RzZXRlc3Q=","email": "test1321321@test.com","newPassword": "kdsflaksldk930209","ne2Password": "kdsflaksldk930209","__proto__": {}}]}
不要崩溃!
添加任何一个属性,几乎都会造成所有端点在执行完成之前崩溃。
第一个目标是确定哪个属性需要被添加,以便至少有一个端点能够到达有趣的那点(漏洞点)。
该漏洞利用的目标端点是主页面。
我们想要到达的漏洞点是渲染所有模板的地方。
第一策略:添加上造成”undefined”异常处的、缺少的值。
文件:/core/server/controllers/channel.js
12345678// 调用 fetchData 来得到我们需要从该API中获得的一切信息return fetchData(res.locals.channel).then(function handleResult(result) {// If page is greater than number of pages we [...]// 如果 page 参数大于我们[...]页面的总数if (pageParam > result.meta.paination.page) {[...]}
为了修复该点,我们将污染这个值:
1"meta": {"pagination": {"pages": "100"}}第二策略:避免”dead-end”
有时,应用程序会在注入额外无效值的地方崩溃。
确定属性,避免使用会到达”dead-end”的代码路径。
第三策略:修复递归问题
12Object.prototype.foo = {};({}).foo.foo.foo.foo.foo === ({}).foo;
修复完的版本:
12Object.prototype.foo = {"foo": ""};({}).foo.foo === "";
利用!
- 使用属性注入。
渲染模板是懒加载的。
12345678910111213module.exports.setTemplate =function setTemplate(req, res, data) {var routeConfig = res._route || {};if (res._template && !req.err) {return;}if (req.err) {res._template = _private.getTemplateForError(res.statusCode;);return;}[...]在最终选择
_template
之前,我将它指向了一个受我控制的本地文件。我对这个文件做了一些fuzz,来确定哪些内容能够帮助我们注入任意代码。
有一部分的调用可能已经被破坏,无法执行我们选择的代码。
包含我们任意代码的属性是”blockParams”。
我们必须找到一个包含部分调用的模板。
包含部分调用的该应用的所有模板都会在渲染期间崩溃。
测试用例模板在”express-hbs”中提供。
最终目标:”../../../current/node_modules/express-hbs/test/issues/23/emptyComment.hbs”
|
|
缓解
- Object.freeze(Object.prototype)
- 使用像ajv这样的库来进行JSON格式的数据解析
- 使用 Map 代替 Object