Wasmは「聖域」ではない:ブラウザのメモリ安全性を逆手に取ったXSSの現実
現場でコードを叩いていると、「WebAssembly(Wasm)を使っているから、うちはJavaScriptのXSSとは無縁だ」という、あまりにも危うい誤解を耳にすることがある。
確かにWasmはバイナリ形式であり、メモリ安全性を考慮した設計になっている。しかし、Wasmは孤立した宇宙ではない。ブラウザのDOMを操作しようとした瞬間、その「安全なはずの領域」から、JavaScriptの脆弱性という現実世界へ橋を架けることになる。今日は、Wasmを導入したシステムで起きるインジェクションの盲点と、どう防御すべきかという泥臭い話をしよう。
—
Wasmにおける「境界」の正体
Wasmがブラウザ上で動くとき、それは「線形メモリ(Linear Memory)」という閉じた領域で処理される。CやRustで書かれたコードはここで爆速で動くが、DOMを操作する際は、必ずJavaScriptの接着剤(Glue Code)を経由してブラウザのAPIを叩く必要がある。
攻撃者が狙うのは、まさにこの「接着剤」の部分だ。
Wasmモジュールが生成した出力結果が、サニタイズされないまま innerHTML や document.write に渡されたらどうなるか。たとえ内部ロジックが完璧でも、最終的な描画エンジンに渡る段階で、XSSのトリガーが引かれる。Wasmは「計算処理」には強いが、「コンテキストに応じた出力エスケープ」の責任は、結局のところJS側に残るというわけだ。
—
実践的PoC:Wasmから仕込むXSSの構図
例えば、ユーザー入力をWasmで加工して表示する機能を想像してほしい。
1. Wasm側: ユーザー入力を受け取り、文字列を加工してJS側に返す。
2. JS側: Wasmから受け取った文字列を、何の疑いもなくDOMに挿入する。
// 【脆弱な実装例】JS側の接着コード
async function renderWasmOutput(userInput) {
const wasm = await import(‘./processor.wasm’);
const result = wasm.process_data(userInput);
// 致命的な脆弱性: Wasmが返した値をそのままDOMに流し込んでいる
document.getElementById(‘display’).innerHTML = result;
}
もし、Wasmの process_data が入力値を適切に処理せず、 のようなペイロードをそのまま返してしまったら? Wasmモジュールがどれほど堅牢な言語で書かれていようが、ブラウザにとってはただの「悪意あるHTML文字列」でしかない。
—
完全防御のためのセキュア実装:WasmとDOMの間に「壁」を築く
防御の鉄則は「Wasm側で信頼せず、JS側で必ずエスケープする」ことだ。WasmとJSの境界線で、データの性質を厳格に定義する。
1. JS側での徹底したエスケープ(推奨)
Wasmが何を返そうとも、DOM操作の直前で安全なメソッドを使用する。innerHTML は禁じ手だ。
// 【セキュアな実装例】JS側でDOM挿入を制御する
function secureRender(wasmResult) {
const displayElement = document.getElementById(‘display’);
// 危険: displayElement.innerHTML = wasmResult;
// 安全: textContentを使用すれば、ブラウザは文字列としてのみ解釈する
displayElement.textContent = wasmResult;
}
2. CSP(Content Security Policy)による多重防御
万が一、DOM操作のコードに脆弱性が潜んでいたとしても、CSPで実行をブロックする。Wasmの実行には unsafe-eval が必要になるケースがあるが、可能な限り制限をかけよう。
Nginxの設定例: CSPヘッダーでインラインスクリプトを封じ込める
add_header Content-Security-Policy “default-src ‘self’; script-src ‘self’ ‘wasm-unsafe-eval’; object-src ‘none’;”;
※ wasm-unsafe-eval を使用する際は、インラインスクリプトや eval() が混入しないよう、厳格なポリシー運用が求められる。
—
現場で生き残るための「セキュリティ・マインドセット」
多くのエンジニアが陥る罠は、「技術の先進性がセキュリティを担保してくれる」という幻想だ。Wasmはパフォーマンスの革命だが、セキュリティの免罪符ではない。
もし君が開発リーダーなら、チームに以下のルールを徹底させてほしい。
1. 境界分離の原則: Wasmモジュールは「データ処理機」と割り切り、HTML生成の責任は負わせない。
2. 型安全の徹底: WasmからJSへデータを渡す際は、プリミティブな型(数値や、エスケープ済みの文字列)のみを許可する。
3. 自動テストにXSSパターンを含める: 「Wasmを使っているから大丈夫」というテストコードは今すぐ破棄し、あえて悪意あるHTML断片をWasmに注入し、JS側で正しくエスケープされているか確認するE2Eテストを実装すること。
セキュリティとは、最新技術を追いかけることではなく、「データが流れる場所には必ず落とし穴がある」という疑念を常に持ち続けることだ。Wasmを使おうが何を使おうが、ブラウザのDOMがHTMLを解釈する以上、我々の戦い方は変わらない。堅牢な設計で、泥臭く守り抜こう。
コメント