如何防止 JavaScript 中的原型污染

Ben*_*ler 1 javascript security node.js

最近我偶然发现了doT.js 中的一个漏洞。该漏洞的存在是因为攻击者可以使用原型污染来修改传递给 doT 的选项的值。

例子:

var doT = require("dot");
var tempFn = doT.template("<h1>Here is a sample template " +
    "{{=console.log(23)}}</h1>");
tempFn({})
Run Code Online (Sandbox Code Playgroud)
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();
Run Code Online (Sandbox Code Playgroud)

然后我开始思考:这是否意味着几乎所有JavaScript 库的 API 选项都可能因原型污染而受到损害?

例如,hereexpress.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))
Run Code Online (Sandbox Code Playgroud)

攻击者不能设置Object.prototype.redirect = true,如果用户未指定,会发生重定向吗?肯定还有更多恶意用例。

作为库作者,可以做些什么来允许传递选项但防止原型污染?

编辑:我特别关注与 NPM 一起分发的包。例如,doT.js 的作者可以做些什么来解决这个漏洞?

Yve*_* M. 5

Olivier Arteau发布了一份完整的白皮书 PDF,称为NodeJS应用程序中的Prototype 污染攻击,涵盖了攻击的识别和缓解。


攻击的一般概念

原型污染背后的一般思想始于这样一个事实:攻击者至少可以控制参数avalue以下形式的任何表达式:

obj[a][b] = value;
Run Code Online (Sandbox Code Playgroud)

攻击者可以设置a__proto__并且名称定义为 的属性bobj在应用程序的所有现有对象(属于 的类)上定义为 值value

当攻击者至少控制a,b和时,同样的事情可以附加以下形式value

obj[a][b][c] = value;
Run Code Online (Sandbox Code Playgroud)

攻击者可以设置a constructor btoprototype和定义名称的属性c将在应用程序的所有现有对象上定义为值value

然而,因为这需要更复杂的对象分配,所以第一种形式更容易使用。

虽然您很少会偶然发现在文本上看起来像提供的示例的代码,但一些操作可以为攻击者提供类似的控制。

减轻

使用 Map 而不是 Object

它本质上作为 HashMap 工作,但没有所有的安全警告Object。当需要键/值结构时,Map应该优先于Object.

Object.create(null)

可以在 JavaScript 中创建没有任何原型的对象。它需要使用该Object.create函数。通过此 API 创建的对象将不具有__proto__ constructor 属性。以这种方式创建对象有助于减轻原型污染攻击。

let obj = Object.create(null);
obj.__proto__ // undefined
obj.constructor // undefined
Run Code Online (Sandbox Code Playgroud)

JSON 输入的模式验证

npm 上的多个库(例如:ajv)为 JSON 数据提供模式验证。架构验证确保 JSON 数据包含具有适当类型的所有预期属性。当使用这种方法来减轻“原型污染”攻击时,重要的是拒绝不需要的属性。在ajv 中,这可以通过在架构上设置additionalProperties为来完成false

冻结原型

使用Object.freeze将减轻几乎所有可利用的情况。

请注意,虽然向基础对象的原型添加函数在实践中是令人不悦的,但它仍可能在您的 Node.js 应用程序或其依赖项中使用。强烈建议在沿着这条路线前进之前检查您的 Node.js 应用程序及其对此类使用的依赖。由于冻结对象的行为是在属性分配时静默失败,因此可能会引入难以识别的错误。

Object.freeze(Object.prototype);
Object.freeze(Object);
({}).__proto__.test = 123;
({}).test // this will be undefined
Run Code Online (Sandbox Code Playgroud)


jfr*_*d00 2

作为库作者,可以做些什么来允许传递选项但防止原型污染?

您可以使用 来检测某个属性是在您的实际对象上还是通过原型继承.hasOwnProperty()。但是,攻击者.hasOwnProperty()也可以覆盖并改变其行为。

正如我在评论中所说,在 Javascript 程序中使用您的库的人可以访问您的代码的完整源代码。因此,他们甚至不必使用原型污染来修改东西——他们可以随心所欲地修改你的代码。

为了完全保护您的代码,您必须仅分发在不同进程中运行并具有进程间 API(例如 http 服务器)的已编译可执行文件,或者您必须将代码放入服务中并仅提供以这种方式访问​​。如果您要分发 Javascript 库,就其本质而言,您必须分发源代码,以便使用您的库的程序员可以真正对其进行任何操作。他们甚至不必诉诸原型技巧。

  • @BenGubler - 正如我已经说过的,任何可以在您的服务器上进行 doT.js 攻击的攻击者都可以在您的服务器上做更糟糕的事情。您必须仅在服务器上运行受信任的库。我不明白这有什么好担心的。您提供的链接仅显示该报告已“关闭”,但没有任何关于关闭原因的讨论。我认为它已被关闭,因为它实际上并不是一个有效的漏洞。 (3认同)