安全设置未知属性(缓解方括号对象注入攻击)实用程序功能

问题描述 投票:2回答:1

[设置了eslint-plugin-security之后,我尝试解决我们的javascript代码库中有近400种使用方括号的问题(由security / detect-object-injection标记)。尽管此插件可能更加智能,但是方括号的任何使用都可能成为恶意代理注入自己的代码的机会。

要了解我的问题的方式以及整个问题的内容,您需要阅读以下文档:https://github.com/nodesecurity/eslint-plugin-security/blob/master/docs/the-dangers-of-square-bracket-notation.md

我通常尝试使用Object.prototype.hasOwnProperty.call(someObject,someProperty),在这里我可以减轻someProperty被恶意设置为constructor的机会。许多情况只是在for循环(for (let i=0;i<arr.length;i++) { arr[i] })中解引用数组索引。如果i始终为int,则这显然总是安全的。

一种情况,我认为我做得不好,是这样的方括号分配:

someObject[somePropertyPotentiallyDefinedFromBackend] = someStringPotentiallyMaliciouslyDefinedString

StackOverflow的现状是“向我展示所有代码”-当您遍历一个代码库并在成百上千个实例中修复这些代码时,花很长时间才能阅读其中的一个代码。这些作业。此外,我们希望确保此代码在将来进行修改时保持安全。

我们如何确保要设置的属性在香草对象上基本上尚未定义? (即constructor

试图自己解决这个问题,但是缺少一些内容-看看是否可以通过其余的单元测试:

因此,我认为解决此问题的最简单方法是使用一个简单的工具safeKey定义如下:

// use window.safeKey = for easy tinkering in the console.
const safeKey = (() => {
  // Safely allocate plainObject's inside iife
  // Since this function may get called very frequently -
  // I think it's important to have plainObject's
  // statically defined
  const obj = {};
  const arr = [];
  // ...if for some reason you ever use square brackets on these types...
  // const fun = function() {}
  // const bol = true;
  // const num = 0;
  // const str = '';
  return key => {
    // eslint-disable-next-line security/detect-object-injection
    if (obj[key] !== undefined || arr[key] !== undefined
      // ||
      // fun[key] !== undefined ||
      // bol[key] !== undefined ||
      // num[key] !== undefined ||
      // str[key] !== undefined
    ) {
      return 'SAFE_'+key;
    } else {
      return key;
    }
  };
})();

我们也可以编写一个util safeSet-代替这个:

obj[key] = value;

您将执行此操作:

safeSet(obj, key, value)

safeKey测试(失败):

console.log(safeKey('toString'));
//     Good: => SAFE_toString

console.log(safeKey('__proto__'));
//     Good: => SAFE___proto__

console.log(safeKey('constructor'));
//     Good: => SAFE_constructor

console.log(safeKey('prototype'));
//     Fail: =>      prototype

console.log(safeKey('toJSON'));
//     Fail: =>      toJSON

然后您将像这样使用它:

someObject[safeKey(somePropertyPotentiallyDefinedFromBackend)] = someStringPotentiallyMaliciouslyDefinedString

[这意味着,如果后端在constructor的某个地方偶然发送了带有密钥的json,我们就不会对此cho之以鼻,而只需使用密钥SAFE_constructor(lol)。也适用于任何其他预定义的方法/属性,因此现在后端不必担心json键与本地定义的js属性/方法发生冲突。

如果没有一系列通过的单元测试,此util函数将一无所获。正如我所评论的,并非所有测试都通过了。我不确定哪个对象本机定义为JSON-这意味着它可能需要成为必须列入黑名单的方法/属性名称的硬编码列表的一部分。但是我不确定如何找出every这些需要列入黑名单的属性方法之一。因此,我们需要知道任何人可以生成此列表并保持更新的最佳方式。

我确实发现使用Object.freeze(Object.prototype)会有所帮助,但是我认为原型中不存在像toJSON这样的方法。

这里是另一个小测试用例:

const prop = 'toString';
someData[safeKey(prop)] = () => {
    alert('hacked');
    return 'foo';
};
console.log('someProp.toString()', someData + '');
javascript security code-injection square-bracket
1个回答
0
投票

[您可以考虑采用一些策略来缓解与Object.prototype中的内置字符串冲突的任意字符串键的问题。它们不是互斥的,但是为了保持一致,最好只保留一个。

  • [Just transform all keys:这可能是(从概念上来说)最简单的选项,甚至可以移植到ECMAScript 3时代起的引擎上,并且即使将来添加到Object.prototype时也很健壮(因为它们不太可能)。只需在所有不受信任的密钥之前添加一个(最好是非标识符)字符即可;这样可以安全地命名所有合理的JavaScript内置组件(大概应该具有有效标识符的名称)中不受信任的键的名称空间。遍历对象时,请检查该字符并将其适当地剥离。

  • 在严格受控的情况下限制使用括号表示法:仅将其用于数组或将任意字符串键映射到其他值的对象。

    此外,对于“地图”对象:

    • 通过Object.create(null)创建它们,而不是仅由Object.create(null)创建;这样可以确保它们具有空的原型链,因此根本不会继承{}的属性描述符。假设Object.prototype也是constructor的属性描述符(__proto__;同一部分,则这将防止通过不受信任的输入(例如__proto__示例)泄露不需要的属性,并通过Object.prototype覆盖对象的原型。最多10版)。后者至少在现代引擎中是正确的–较早的,预先标准化的引擎可能已与原型链分开实施了ECMA-262 6th Ed. § B.2.2.1,我现在不记得了;
    • 确保它们不直接包含函数值,以防止__proto__出现问题;
    • 避免通过强制toJSONtoString构造函数将它们直接转换为字符串,以防止名为String的键出现问题;鉴于缺少原型,他们将始终没有toString的默认实现,这似乎相对容易解决。

    对于数组,只需确保方括号内的表达式始终是整数。

    [遗憾的是,默认情况下,toString创建的对象仍将在其原型链中包含JSON.parse(尽管在现代引擎中,类似JSON.parse的键将直接添加到对象本身,而绕过了原型的设置器)描述)。您可以将此类对象视为不可变的,并使用Object.prototype读取其属性,或者通过编写调用__proto__的齐磊函数来剥离其原型。

    您如何执行此纪律取决于您。作为一个示例,要对Object.prototype.hasOwnProperty.call(obj, key) ? obj[key] : void 0中的对象强制执行不变性规则,您可以编写一个reviver函数,以对解析JSON产生的任何对象调用Object.setPrototypeOf,然后启用Object.setPrototypeOf;这样,写入此类对象将抛出JSON.parse。通过足够的测试,您应该能够捕获所有这些情况。

  • 使用Object.seal代替普通对象存储键-值对

  • :使用Object.seal允许您使用字符串以外的键,而普通对象则无法使用;但是,即使仅使用字符串键,您也可以从地图对象本身的原型链中完全隔离地图的条目。通过strict modeTypeError方法访问Maps中的项目,而不用括号表示法访问,并且根本不能与属性发生冲突:键位于单独的命名空间中。

    但是存在无法将Map直接序列化为JSON的问题。您可以通过编写Map的替换函数将Map转换为无原型的普通对象,以及为.get的将原始对象转换回.set的齐磊函数来解决此问题。再一次,将每个JSON对象简单地恢复为Map也将涉及“类”,即,您可能希望保留为纯JS对象的相对固定结构的对象。为了区分这两种情况,您可能需要在JSON解析函数中添加某种架构参数。

© www.soinside.com 2019 - 2024. All rights reserved.