最近我偶然发现了一个vulnerability in doT.js 。该漏洞的存在是因为攻击者可以利用原型(prototype)污染来修改传入 doT 的选项值。
示例:
var doT = require("dot");
var tempFn = doT.template("<h1>Here is a sample template " +
"{{=console.log(23)}}</h1>");
tempFn({})
var doT = require("dot"); // prototype pollution attack vector
Object.prototype.templateSettings = {varname:"a,b,c,d,x=console.log(25)"};
// benign looking template compilation + application
var dots = require("dot").process({path: "./resources"});
dots.mytemplate();
然后我开始思考:这是否意味着几乎任何 JavaScript 库的 API 选项都可能因原型(prototype)污染而受到损害?
例如,这里的 express.static
与选项一起使用。
var options = {
dotfiles: 'ignore',
etag: false,
extensions: ['htm', 'html'],
index: false,
maxAge: '1d',
redirect: false,
setHeaders: function (res, path, stat) {
res.set('x-timestamp', Date.now())
}
}
app.use(express.static('public', options))
攻击者难道不能设置Object.prototype.redirect = true
,并且如果用户未指定,就会发生重定向吗?而且肯定还有更多的恶意用例。
作为库作者,可以做些什么来允许传递选项但防止原型(prototype)污染?
编辑:我特别关注使用 NPM 分发的包。例如,doT.js 的作者可以采取什么措施来解决该漏洞?
最佳答案
Olivier Arteau发布了完整的 PDF 白皮书 Prototype pollution attack in NodeJS application其中包括识别和缓解攻击。
<小时/>攻击的一般概念
原型(prototype)污染背后的一般思想始于攻击者至少可以控制以下形式的任何表达式的参数a
和value
:
obj[a][b] = value;
攻击者可以将a
设置为__proto__
,并且具有由b
定义的名称的属性将在所有现有对象(该对象的应用程序的 obj
的类),其值为 value
。
当攻击者至少控制a
、b
和value
时,同样的东西可以附加以下形式。
obj[a][b][c] = value;
攻击者可以将a
设置为constructor
,将b
设置为prototype
,并将属性名称定义为c
将在应用程序的所有现有对象上定义,值为 value
。
但是,由于这需要更复杂的对象分配,因此第一种形式更容易使用。
虽然您很少会偶然发现与所提供的示例文本类似的代码,但一些操作可以为攻击者提供类似的控制。
缓解措施
使用映射而不是对象
它本质上作为一个 HashMap 工作,但没有 Object
所具有的所有安全警告。当需要键/值结构时,Map
应优先于 Object
。
Object.create(null)
可以在 JavaScript 中创建没有任何原型(prototype)的对象。它需要使用Object.create
函数。通过此 API 创建的对象不会具有 __proto__
和 constructor
属性。以这种方式创建对象可以帮助减轻
原型(prototype)污染攻击。
let obj = Object.create(null);
obj.__proto__ // undefined
obj.constructor // undefined
JSON 输入的架构验证
npm 上的多个库(例如: ajv )为 JSON 数据提供架构验证。架构验证确保 JSON 数据包含具有适当类型的所有预期属性。当使用这种方法来减轻“原型(prototype)污染”攻击时,拒绝不需要的属性非常重要。在 ajv ,这可以通过在架构上将 additionalProperties
设置为 false
来完成。
卡住原型(prototype)
使用Object.freeze
将缓解几乎所有可利用的情况。
请注意,虽然向基础对象的原型(prototype)添加函数是一种令人不悦的做法,但它仍然可以在 Node.js 应用程序或其依赖项中使用。强烈建议在走这条路线之前检查您的 Node.js 应用程序及其依赖关系以了解此类用法。由于卡住对象的行为是在属性分配上默默失败,因此可能会引入难以识别的错误。
Object.freeze(Object.prototype);
Object.freeze(Object);
({}).__proto__.test = 123;
({}).test // this will be undefined
关于javascript - 如何防止 JavaScript 中的原型(prototype)污染,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57780961/