Address
304 North Cardinal St.
Dorchester Center, MA 02124
Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM
Ovo je prvi post u seriji blog postova o web ranjivostima. Svrha ovih postova je da uputi developere, security testere, QA inženjere, software/sistem inženjere u različite tipove sigurnosnih propusta koji mogu nastati u web aplikaciji, kao i način na koji se kod može pisati kako bi se izbeglo pravljenje tih propusta.
Prototype pollution je tip ranjivosti u JavaScript aplikacijama koji napadaču omogućava da direktno ili indirektno utiče na menjanje sadržaja objekta na proizvoljan način. Na taj način, napadač može uticati na tok izvršavanja aplikacije.
Prototype pollution ranjivost može postojati kako na frontend delu aplikacije, tako i na backend delu. U ovom postu, opisaćemo ranjivosti na frontend delu i kako ova ranjivost može dovesti do XSS napada. U jednom od sledećih postova, opisaćemo testiranje Prototype Pollution ranjivosti u okviru Node.js aplikacije. Princip ranjivosti je identičan na backendu, samo je način testiranja ranjivosti drugačiji.
Pre nego što nastavimo sa daljim opisom, objasnićemo tehničke pojmove koje ćemo koristiti, kako bi ceo tekst bio razumljiviji.
U JavaScriptu, objekat je struktura podataka koja se koristi za organizaciju i skladištenje podataka. Može se zamisliti kao kolekcija para ključ/vrednost, gde ključ predstavlja jedinstveni identifikator za vrednost. Ova struktura podataka omogućava developerima da grupišu srodne vrednosti i funkcije na jednom mestu, čime se olakšava manipulacija i upravljanje podacima. Objekti su posebno korisni kada radimo sa velikom količinom podataka, jer nam omogućavaju da struktuiramo te podatke na logičan i pregledan način. Vrednosti u snimljene u objektu možemo dobiti koristeći ključ pod kojim su vrednosti snimljena.
[[Prototype]]
. Prototype predstavlja šablon od koga se pravi objekat. U našem slučaju, mi koristimo podrazumevani šablon koji nam pruža JavaScript runtime.
JavaScript koristi “prototype” način nasleđivanja objekata. Ovaj način nasleđivanja je malo drugačiji od toga kako radi nasleđivanje u OOP.
Ako ste koristili JavaScript, znate da je string primitivan tip podataka koji ima neke specifične osobine (UTF-16 enkodovani karakteri, nepromenljiv…). Ali znamo da string tip ima određene metode, kao što je toLowerCase. Ova metoda omogućava konverziju stringa u mala slova. Ali postavlja se pitanje odakle dolazi ova metoda ako string nije objekat već primitivan tip podataka.
Object
. JavaScript engine će automatski enkapsulirati naš primitivni string tip u ovaj objekat, izvršiti metodu, i potom “uništiti” taj privremeni objekat. Taj proces se zove autoboxing i omogućava JavaScript engine-u da u kodu koristi primitivne tipove podataka koji zauzimaju malo memorije i koji imaju vrlo brze nativne funkcije za njihovu obradu. Dok u slučaju da nam je potrebna metoda za neku transformaciju datog tipa podataka, stvara se objekat koji:
let obj1 = {}
obj1['ime']='Petar'
obj1['prezime']='Petrovic'
obj1['prikazi_ime_i_prezime']= function() {
return this.ime + ' ' + this.prezime;
}
Ovo je objekat koji je nasledio metode i atribute od glavnog objekta, Object.
Da bismo videli šta nasleđuje ovaj objekat, možemo da koristimo ugrađenu funkciju:
Object.getPrototypeOf(naziv_naseg_objekta);
Istu ovu informaciju možemo da dobijemo koristeći specijalni atribut koji postoji u objektu obj1, a naziva se ‘proto‘. Ovaj atribut je pokazivač koji ukazuje na objekat od kog je dati objekat nasleđen.
Hierarhija nasleđivanja objekata, koji su već definisani u JavaScript engine-u, izgleda ovako:
U javascript-u se možemo referisati na bazni objekat koristeci sledeću komandu
Object.prototype
let client_object = {}
client_object.__proto__ === Object.prototype
> true
Ovo znači da __proto__
parametar u client_object
-u pokazuje direktno na Object.prototype
.
Ako dodamo atribut ili metodu u okviru Object.prototype
-a, svi objekti u JavaScriptu će dobiti pristup datom atributu ili metodi.
// Kreiramo objekat test_object
let test_object = {}
// pokušamo da pristupimo atibutu info
// i dobijemo odgovor undefine, sto znaci da dati atribut ne postoji u objektu
test_object.info
> undefined
// Sada dodamo dati info atribut direktno u naš bazni objekat
Object.prototype.info = 'this is info'
// ponovo pokušamo da pristupimo info atributu u našem test_object-u
// i ovog puta dobija podatke
test_object.info
> 'this is info'
Sada, kada imamo malo više informacija o tome kako radi Prototype nasleđivanje u JavaScriptu i šta je __proto__ svojstvo u objektu, možemo da se vratimo na pitanje kako nastaje i šta je to Prototype Pollution.
Prototype Pollution nastaje kada napadač ima mogućnost da direktno ili indirektno utiče na kreiranje naziva ključa u objektu i njegove vrednosti. Napadač to može iskoristiti da doda ključ pod nazivom __proto__ i da doda vrednost direktno u Object.prototype. Time, napadač dodaje atribut ili metodu u sve objekte koji su kreirani u okviru datog konteksta. Napadač zatim mora da nađe način da iskoristi dati atribut tako da ta vrednost bude na neki način pozvana u samom kodu aplikacije. Ali, na to ćemo se vratiti kasnije.
U slučaju da je to frontend aplikacija, takva ranjivost uglavnom uzrokuje XSS ranjivost, dok u slučaju da je to Node.js aplikacija, ranjivost najčešće uzrokuje RCE (Remote Code Execution), Access Control Bypass i slične tipove ranjivosti.
Par primera prototype pollution–a
let p_pollution = {}
p_pollution['__proto__']['injected_value'] = 'podaci koji su injectovani u prototype'
p_pollution.__proto__['injected_value'] ='podaci koji su injectovani u prototype'
Primer iz chrome konzole
Object.prototype
Postoji više opcija kako bi se sprečila ova ranjivost:
Unosi se koriste za generisanje ključeva u objektu.
Ovde treba pomenuti da nije dovoljno samo zabraniti __proto__
ključ, pošto napadač može da koristi još jedan trik.
Svaki objekat ima pokazivač na svoj konstruktor. Objekat u JavaScriptu se može kreirati na dva načina:
let obj_literal = {};
new Object()
let obj_explicit = new Object()
Object.prototype
let obj1 = {}
obj1.constructor.prototype === Object.prototype
To znači da napadač može koristiti dati pokazivač da doda vrednost u Object.prototype.
Kada kreiramo novi objekat, možemo definisati od koga tipa objekta se nasleđuje.
> let null_inheritence_object = Object.create(null);
> console.log(null_inheritence_object.__proto__)
undefined
>
Ovo sprečava mogućnost dodavanja vrednosti u Object.prototype
, jer objekat ne nasleđuje Object.prototype
i time nema referencu na isti.
Ovo se u JavaScriptu postiže korišćenjem
Object.freeze(Object.prototype)
U JavaScriptu (ES6) postoji struktura podataka po imenu Map. Ova struktura se može koristiti za čuvanje podataka kojima se pristupa pomoću ključa.
Evo kako izgleda i kako se koristi.
> let my_map = new Map();
> my_map.set('key1', 'value1');
// Preuzeti vrednost iz mape
> my_map.get('key1');
value1
>
Pre nego što pređemo na deo o testiranju ranjivosti aplikacije, trebalo bi da se upoznamo s faktorima koji moraju biti zadovoljeni kako bi napadač mogao da iskoristi ovu ranjivost.
Prva stvar je pronalaženje delova koda koji dozvoljavaju injektovanje prototype pollution unosa, kao što su __proto__
ili constructor.prototype
.
Ovo se najčešće postiže kroz JSON unos, URL query parametre ili Web Socket poruke. Nakon toga, neophodno je rekurzivno spajanje objekata.
Ovo je jednostavna funkcija koja spaja dva objekta i vraća obj1 sa prepravljenim ili dodatim atributima iz objekta2. Ova funkcija je ranjiva na prototype pollution. Naime, ako obj2 dobijamo parsiranjem JSON string-a, tada možemo napraviti objekat koji u sebi sadrži “proto” atribut kao string, i prilikom spajanja, takav objekat će biti deo Object.prototype-a u okviru datog konteksta. Ali, da bi ovo bilo jasnije, pogledajmo šta to znači u praksi.
Prvo, primer zašto postoji ranjivost isključivo ako koristimo JSON.parse(), a ne direktno objekat.
function mergeObjects(obj1, obj2) {
for (key in obj2) {
if (!(key in obj1)) {
obj1[key]= {};
}
if (obj2[key] instanceof Object) {
mergeObjects(obj1[key], obj2[key]);
} else {
obj1[key] = obj2[key];
}
}
return obj1;
}
Sad je pitanje, zašto ovo nije uslovilo ubacivanje info atributa u Object.prototype?
Ajde da vidim kako izgleda obj1
__proto__
pointer na objekat {info:”this is injected value”}
. Kako smo ranije naveli, JavaScript pri kreiranju objekta nasleđuje bazni objekat koji se referencira sa Object.prototype
. U našem slučaju objekat (ob1) je napravljen tako da njegov __proto__
pointer ukazuje na objakt {”info”:”this is injected value”}
a dati objekat ima __proto__
koji ukazuje na Object.prototype
. Isto ponašanje možemo postići sledećom komandom Pošto smo ustanovili da ovim ne možemo uticati na Object.prototype, pogledajmo kako izgleda kada JSON.parse izvrši parsiranje stringa koji izgleda kao prethodni objekat.
__proto__
atribut, ali taj atribut nije pointer na Object.prototype već predstavlja običan ključ u datom objektu. Šta je onda tu problem? Problem nastaje ako dati objekat pokušamo da iskopiramo u drugi objekat, primer toga je korišćenje naše jednostavne merge funkcije. Pogledajmo kako to izgleda Šta je ovde urađeno?
Simulirano je da napadač ima mogućnost da pošalje JSON objekat koji aplikacije konvertuje u javascript object koristeći builtin funkciju JSON.parse()
. Zatim smo koristili ranjivu funkciju mergeObjects koja spaja dva objekta. Rezultat tog spajanja je da je u Object.prototype
dodat atribut info sa vrednošću “this is injected value”. Ovaj deo ranjivosti se zove Prototype Pollution sink i to je način kako napadač može da upiše vrednost u Object.prototype. To samo po sebi nije dovoljno i potreban je način da se data vrednost iz Object.prototype na neki način učita u kod aplikacije tako da aplikacija izvši kod koji je napadač poslao.
Object.prototype
to je mesto u kodu odakle se učitavaju parametri u sink. U našem slučaju to je src_obj koji je dobijen parsiranjem JSON-a koji je pod kontrolom napadača.
predstavlja treću komponentu potrebnu da bi postojala data ranjivost. Gadget predstavlja mesto u kodu koje gde se input atribut učitan kroz PrototypePollution source koristi u aplikaciji. Primer gadget-a koji se može iskoristi za aktiviranje ranjivosti.
let defaults = {
analytics_script: 'cdn_url_for_analytics_script'
}
let analytics_script = config.analytics_script || defaults.analytics_script
let script = document.createElement('script');
script.src= analytics_script;
document.body.appendChild(script);
Ako config objekat nema analytics_script property definisan logika u aplikaciji omoguća da se data vrednost pročita iz default objekta. Gde je neka vrednost upisana.
Ako napadač ima mogućnost da doda analytics_script atribut u Object.prototype, napadač može da omogući učitavanje svoje skripte sa proizvoljnog URL-a.
U slučaju da postoj CSP (content security policy) definisan na sajtu i nije dozvoljeno učitavanje eksterne skripte sa url-ova koji nisu predefinisani, napadač u tom slučaju može da direktno doda JavaScript kod koristeci keyword data:
__proto__['analytics_script']['data:,alert(1);']
U sledećem primeru dat je kod koji koristi našu ranjivu funkciju merge.
Prototype pollution by pulsarpoint
Prototype pollution by pulsarpoint
Merged object
Kako napadač može iskoristi bug da dobije XSS u okviru aplikacije.
Kako izgleda testiranje naše aplikacije
U source object polje
{"__proto__": {"test": "injected value"}, "test":"test"}
U destination object
{}
{ “test”: “test”}
, deo source objekta {”__proto__”:{”test”:”injected value”}}
je importovano u Object.prototype Ako sada u source polje postavimo vrednost koja će zameniti analytics_script atribut.
{"__proto__":{"analytics_script": "data:,alert('xss')"}}
nakon što korisnik klikne na dugme Submit. XSS je aktiviran
Prvo je potrebno da pronađemo prototype pollution sink. Postoje tri opcije kako možemo naći Prototype Pollution sink
Zatim je potrebno pronaći gadget. Ovo nam omogućava da kod koji je importovan u Object.prototype bude korišćen u aplikaciji. I ovaj deo analize može biti automatizan korišćenjem BurpSuite dom-invidera.
U sledećem blog postu naučićemo kako otkriti ranjivost u različitim tipovima aplikacija.