Attacco a Node.js attraverso la manipolazione di prototipi di oggetti JavaScript

I ricercatori del Centro Helmholtz per la sicurezza informatica (CISPA) e del Royal Institute of Technology (Svezia) hanno analizzato l'applicabilità della tecnica di inquinamento del prototipo JavaScript per creare attacchi alla piattaforma Node.js e alle popolari applicazioni basate su di essa, portando all'esecuzione di codice.

Il metodo di inquinamento del prototipo utilizza una funzionalità del linguaggio JavaScript che consente di aggiungere nuove proprietà al prototipo root di qualsiasi oggetto. Le applicazioni possono contenere blocchi di codice (gadget) il cui funzionamento è influenzato da una proprietà sostituita; ad esempio, il codice può contenere un costrutto come 'const cmd = options.cmd || "/bin/sh"', la cui logica verrà modificata se l'aggressore riesce a sostituire la proprietà "cmd" nel prototipo root.

Un attacco riuscito richiede che l'applicazione possa utilizzare dati esterni per creare una nuova proprietà nel prototipo radice dell'oggetto e che l'esecuzione incontri un gadget che dipende dalla proprietà modificata. La modifica del prototipo viene eseguita elaborando le proprietà del servizio "__proto__" e "costruttore" in Node.js. La proprietà "__proto__" restituisce il prototipo della classe dell'oggetto e la proprietà "costruttore" restituisce la funzione utilizzata per creare l'oggetto.

Se il codice dell'applicazione contiene l'assegnazione “obj[a][b] = valore” e i valori sono impostati da dati esterni, un utente malintenzionato può impostare “a” sul valore “__proto__” e ottenere l'installazione di una propria proprietà con il nome “b” e il valore “valore” nel prototipo radice dell'oggetto (obj.__proto__.b = valore;), e la proprietà impostata nel prototipo sarà visibile in tutti gli oggetti. Allo stesso modo, se il codice contiene espressioni come “obj[a][b][c] = valore”, impostando “a” sul valore “costruttore” e “b” su “prototipo” in tutti gli oggetti esistenti, è possibile definire una nuova proprietà con il nome "c" e il valore "value".

Esempio di modifica del prototipo: const o1 = {}; const o2 = nuovo Oggetto(); o1.__proto__.x = 42; // crea la proprietà “x” nel prototipo root console.log (o2.x); // accedo alla proprietà “x” da un altro oggetto // l'output sarà 42, poiché il prototipo root è stato modificato tramite l'oggetto o1, che è utilizzato anche nell'oggetto o2

Esempio di codice vulnerabile: function entryPoint (arg1, arg2, arg3){ const obj = {}; const p = oggetto[arg1]; p[arg2] = arg3; restituire p; }

Se gli argomenti della funzione entryPoint sono formati da dati di input, un utente malintenzionato può passare il valore "__proto__" ad arg1 e creare una proprietà con qualsiasi nome nel prototipo root. Se passi ad arg2 il valore "toString" e ad arg3 il valore 1, puoi definire la proprietà "toString" (Object.prototype.toString=1) e mandare in crash l'applicazione durante la chiamata a toString().

Esempi di situazioni che potrebbero portare all'esecuzione di codice da parte di un utente malintenzionato includono la creazione delle proprietà "main", "shell", "exports", "contextExtensions" e "env". Ad esempio, un utente malintenzionato può creare una proprietà “main” nel prototipo root di un oggetto, scrivendo in esso il percorso del suo script (Object.prototype.main = “./../../pwned.js”) e questa proprietà verrà chiamata al momento dell'esecuzione nel codice del costrutto require("my-package"), se il pacchetto incluso non definisce esplicitamente la proprietà "main" in package.json (se la proprietà non è definita, sarà ottenuto dal prototipo root). Le proprietà “shell”, “exports” e “env” possono essere sostituite in modo simile: let rootProto = Object.prototype; rootProto["esporta"] = {".":"./changelog.js"}; rootProto["1"] = "/percorso/a/npm/scripts/"; // attiva la chiamata require("./target.js"); Object.prototype.main = "/path/to/npm/scripts/changelog.js"; Object.prototype.shell = "nodo"; Object.prototype.env = {}; Object.prototype.env.NODE_OPTIONS = "—inspect-brk=0.0.0.0:1337"; // attiva la chiamata require("bytes");

I ricercatori hanno analizzato 10 pacchetti NPM con il maggior numero di dipendenze e hanno scoperto che 1958 di essi non hanno una proprietà principale in package.json, 4420 utilizzano percorsi relativi nelle loro istruzioni require e 355 utilizzano direttamente l'API di sostituzione dei comandi.

Un esempio funzionante è un exploit per attaccare il backend Parse Server che sovrascrive la proprietà evalFunctions. Per semplificare l’identificazione di tali vulnerabilità è stato sviluppato un toolkit che combina metodi di analisi statici e dinamici. Durante il test di Node.js sono stati identificati 11 gadget che possono essere utilizzati per organizzare attacchi che portano all’esecuzione del codice dell’aggressore. Oltre a Parse Server, sono state identificate due vulnerabilità sfruttabili anche nella CLI NPM.

Fonte: opennet.ru

Aggiungi un commento