Address
304 North Cardinal St.
Dorchester Center, MA 02124

Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM

Prototype Pollution 1. deo

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.

Šta je to prototype pollution?

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.

Uvod u JavaScript

Šta je to objekat?

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.

Kao što vidimo, objekat sadrži dve vrednosti koje smo mi uneli, a to su ime i prezime sa odgovarajućim vrednostima. Pored toga, postoji i atribut koji se zove [[Prototype]].

Šta je to prototype i zašto nam je bitan?

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.

Ova metoda je definisana u specijalnom String objektu, koji je izveden od baznog objekta 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:
  1. Enkapsulira dati primitivni tip podatka,
  2. Izvršava metodu nad tim podatkom, i nakon izvršene metode automatski uništi dati privremeni objekat.
Sada, kada znamo par stvari o objektima u JavaScriptu, vratimo se na Prototype, pošto nam je on bitan za razumevanje date ranjivosti. JavaScript koristi Prototype tip nasleđivanja objekata. Da bi ovo bilo jasnije, pogledajmo primer:
				
					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
				
			
Ova komanda nam pokazuje kako izgleda bazni objekat koji gotovo svi objekti nasleđuju. Da bismo se uverili u to, možemo da probamo sledeće:
				
					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'
				
			

Šta je to Prototype pollution

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

ovo pokazuje uspešno importovan atribut “injected_value” u Object.prototype

Kako sprečiti nastajanje date ranjivosti?

Postoji više opcija kako bi se sprečila ova ranjivost:

Sanitizacija korisničkih unosa

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:

  • Kroz direktno kreiranje objekta (object literal)
				
					let obj_literal = {};
				
			
Ili eksplicitno pozivanjem konstruktora objekta new Object()
				
					let obj_explicit = new Object()
				
			
Svaki kreirani objekat ima interni atribut constructor koji pokazuje na Object konstruktor.Object kontrutor je funkcija u JavaScript engine-u koja kreira objekat. Ova funkcija takođe ima prototype koje ukazuje na 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.

Definišite sve objekte tako da direktno nasleđuju null, a ne 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.

Zabranite promenu prototype objekta ako za tim ne postoji potreba

Ovo se u JavaScriptu postiže korišćenjem

				
					Object.freeze(Object.prototype)
				
			

Koristi Mapu umesto objekta

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
>
				
			

Koje komponente čine da dati bug može postati ranjivost?

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.

Prototype Pollution Sink

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

obj1 izgleda tako što poseduje __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.

Naš objekat obj1 sada ima __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.

Prototype pollution sink je mesto u kodu koje učitava prototype pollution source (prethodnom slucaju obj2) i bez validitranja radi mergovanje atributa tako da može da se promeni Object.prototype

Prototype pollution source

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.

Prototype pollution gadgets

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. 

				
					<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Prototype pollution by pulsarpoint</title>
    
    <style>
    body {
        font-family: Arial, Helvetica, sans-serif;
    }
    h1 {
        text-align: center;
    }
    div {
        display: flex;
        justify-content: center;
        align-items: center;
    }
    textarea {
        width: 400px;
        height: 200px;
        border: 1px solid #ccc;
        border-radius: 5px;
        padding: 10px;
        margin: 10px;
    }
    button {
        width: 100px;
        height: 50px;
        border: 1px solid #ccc;
        border-radius: 5px;
        padding: 10px;
        margin: 10px;
        cursor: pointer;
    }
    pre {
        width: 400px;
        height: 200px;
        border: 1px solid #ccc;
        border-radius: 5px;
        padding: 10px;
        margin: 10px;
        overflow: scroll;
    }
    footer {
        text-align: center;
    }
    footer a {
        text-decoration: none;
        color: #000;
    }
    .json_object {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
    }
</style>
</head>

<body>
    <h1>Prototype pollution by pulsarpoint</h1>
    <div>
        <div class="json_object">
            <label for="destination">Destination object</label>
            <textarea id="dst" name="destination">
            </textarea>
        </div>
        <div class="json_object">
            <label for="source">Source object</label>
            <textarea id="src" name="source">
            </textarea>
        </div>
    </div>
    <div>
        <div>
            <h3>Merged object</h3>
            <pre id="merged_object"> </pre>
        </div>
    </div>
    <div>
        <button id="btn">Submit</button>
    </div>
    <footer>
    <p>Prototype pollution by <a href="https://pulsarpoint.rs">pulsarpoint</a></p>
</footer>
</body>
</html>
<script>

    let defaults = {
        analytics_script: 'cdn_url_for_analytics_script'
    }
    
    function mergeObjects(dst, src) {
        for (key in src) {
            if (!(key in dst)) {
                dst[key]= {};
            }
            if (src[key] instanceof Object) {
                mergeObjects(dst[key], src[key]);
            } else {
               dst[key] = src[key];
            }
        }
        return dst;
    }

    var btn = document.getElementById('btn');
   
    btn.addEventListener('click', function(){
        let config = {};
        var dst = document.getElementById('dst').value;
        var src = document.getElementById('src').value;
        var dstObj = JSON.parse(dst);
        var srcObj = JSON.parse(src);
        dstObject = mergeObjects(dstObj, srcObj);
        document.getElementById('merged_object').innerHTML = JSON.stringify(dstObject, null, 2);
        let analytics_script = config.analytics_script || defaults.analytics_script
        let script = document.createElement('script');
        script.src= analytics_script;
        document.body.appendChild(script);
    });
</script>
				
			

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

				
					{}
				
			
Merge objekat sadrži samo { “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

Kako testirati kod na prototype pollution?

Prvo je potrebno da pronađemo prototype pollution sink. Postoje tri opcije kako možemo naći Prototype Pollution sink

  1. source code aplikacije i da vidimo koje input parametre aplikacija parsira i kako i da li ih integriše u neki objekat gde je naziv ključa pod direktnom ili indirektnom kontrolom od strane napadača.
  2. druga opcija ja da se testiraju inputi nad kojima imamo kontrolu i da se proverimo da li web aplikacija date inpute na neki način učini vidljivim u Object.prototype-u. Da bi povećali uspešnost ovog testa potrebno je da razumemo kako aplikacije radi i koje opcije postoje za unos podataka u aplikaciju.
  3. korišćenje alata kao što je dom-invider od portswigger-a. Alat dolazi instaliran kao plugin u okviru web browsera u sklopu sa BurpSuite skenerom.

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.