はじめに
DOMベースのXSSチートシートは以下の通り。 参考文献 クライアント側のスクリプト・インジェクションの場所を特定し、防止し、自動化したい場合。要するに、ユーザーが制御する入力 ( ソース)は危険なAPI(a シンクを使用)、安全なパターンに置き換える。 テキストコンテンツ, createElementまたはサニタイザー)を使用し、ビルド、ランタイム、およびペンテストのワークフローにチェックを統合します。DOM XSSは完全にブラウザで発生し、従来の多くのサーバサイドのフィルタをバイパスするため、フロントエンドが最後の防衛ラインとなり、ほとんどのチームがまだ自動化を欠いている場所となります。
DOMベースのXSSとは何か?
PortSwiggerによると、DOMベースのクロスサイト・スクリプティングは以下のような場合に発生する。 「JavaScriptは、URLのような攻撃者が制御可能なソースからデータを受け取り、次のような動的コード実行をサポートするシンクに渡す。 eval() または インナーHTML." ポーツウィガー・ネット OWASP DOM-based XSS Prevention Cheat Sheet(OWASPのDOMベースのXSS対策チートシート)では、ストアド/リフレクテッドXSSとの主な違いは以下の通りであると強調している。 実行時クライアント側インジェクション. cheatsheetseries.owasp.org+1
シングルページアプリ(SPA)、サードパーティ製ウィジェットの多用、ダイナミックDOMの構築など、最新のアプリケーションではリスクが増大します。ペイロードがサーバーログに到達しない可能性があり、従来のWAFでは見逃してしまう可能性があります。 フラグメント識別子, ポストメッセージフローあるいは ウィンドウ名と、いずれも一般的な情報源である。この変化を認識することが、セキュリティの成熟度を測る第一歩となる。
脅威のマッピング:発生源→吸収源
セキュアコーディングは、次のようなメンタルマップから始まる。 ソース (攻撃者の入力が入る)と 流し台 (実行される)。あるセキュリティ・ブログはこうまとめている: "ソースとは、ウェブページ上でユーザー入力が追加されるあらゆる場所のことである......シンクとは、ソースに挿入されたデータが移動する場所のことである......サニタイズされていなければ、DOMベースのXSS脆弱性につながる可能性がある。" ミディアム
以下は、ワークステーションやレビュー・チェックリストに置いておくべきコンパクトな表である:
| ソース(エントリーポイント) | シンク(危険なAPI) |
|---|---|
| location.hash, location.search, URLSearchParams | innerHTML、insertAdjacentHTML、outerHTML |
| ポストメッセージ ウィンドウ名 | eval(), new Function(), setTimeout(string) |
| document.referrer, localStorage, sessionStorage | setAttribute('on...'), element.src = ... |
| 未検証のサードパーティ製ウィジェット・データ | DOMの挿入や暗黙的なコードの実行 |
OWASPが強調しているように、単一のテクニックでXSSを防ぐことはできない。適切なシンク、エンコーディング、サニタイズ、安全なAPIを組み合わせる必要がある。 cheatsheetseries.owasp.org
コードと修正による実世界の攻撃パターン
例A - URLフラグメントによるjQueryセレクタ・インジェクション
脆弱なスニペット:
js
$(window).on('hashchange', () => {.
const target = $(location.hash); // ユーザー制御
target[0].scrollIntoView();
});
攻撃者は https://site/page.html#<img src="x" onerror="alert(1)"> - jQueryはハッシュをHTML/セレクタとして扱い オンエラー トリガー
修正する:
js
const raw = location.hash.slice(1);
const safeId = /^[A-Za-z0-9_-]+$/.test(raw) ? raw : null;
if (safeId) { {.
コンスtarget = document.getElementById(safeId);
if (target) target.scrollIntoView();
}
キー:検証、ハッシュをHTMLではなく識別子として扱う。
例B - postMessage → eval チェーン
脆弱なスニペット:
js
window.addEventListener('message', e => {)
eval(e.data); // 危険
});
修正版:
window.addEventListener('message', event => {)
if (event.origin !== '') return;
を試す。
msg = JSON.parse(event.data);
handleMessage(msg);
をキャッチする。
console.warn('Invalid message format');
}
});
避ける 評価オリジンをチェックし、安全なパージングを使用する。
例C - マークダウン・コンテキストにおけるエディター/プレビューXSS
傷つきやすい:
js
preview.innerHTML = marked(userInput);
安全だ:
js
DOMPurify を 'dompurify' からインポートする;
const dirty = marked(userInput);
const clean = DOMPurify.sanitize(dirty);
preview.innerHTML = clean;
ユーザーが作成したHTMLを許可する場合は、サニタイズが必要です。
展開可能な実装:運用を可能にする
安全なDOMヘルパー safe-dom.js
js
DOMPurify を 'dompurify' からインポートする;
export function setSafeHTML(el, dirty) {.
const clean = DOMPurify.sanitize(dirty, {)
ALLOWED_TAGS:['b','i','a','p','ul','li','code','pre','img']、
ALLOWED_ATTR: ['href','src','alt','title','rel']、
FORBID_ATTR: ['onerror','onclick','style']。
});
el.innerHTML = clean;
}
エクスポート関数 setText(el, text) { {.
el.textContent = String(text ?? '');
}
export function safeSetAttribute(el, name, val) {.
if (/^on/i.test(name)) throw new Error('Event handler attribute not allowed');
el.setAttribute(name, String(val));
}
このライブラリを使用して、安全なDOM操作を一元化し、人的ミスを減らします。
静的エンフォースメント - ESLint サンプル
js
// .eslintrc.js
ルールに従います:{
'no-restricted-syntax':[
エラー」、
{ selector:"AssignmentExpression[left.property.name='innerHTML']", message:"safe-dom.setSafeHTMLまたはtextContentを使用してください。"},
{ selector:"CallExpression[callee.name='eval']", message:"Avoid eval()" }、
{ selector:"CallExpression[callee.property.name='write']", message:"document.write()を避ける" }。
]
}
コミット前フック (ハスキー, 前後期)で危険なパターンをブロックする。
CI / Puppeteerテスト - GitHub Actions
.github/workflows/dom-xss.yml はPuppeteerのテストを誘発する:
// tests/puppeteer/dom-xss-test.js
js
const puppeteer = require('puppeteer');
(非同期()=>{
const browser = await puppeteer.launch();
const page = await browser.newPage();
const logs = [];
page.on('console', msg => logs.push(msg.text()));
await page.goto(${URL}/page.html#)<img src="x" onerror="console.log("XSS")">);
await page.waitForTimeout(1000);
if (logs.some(l=>l.includes('XSS'))) process.exit(2);
browser.close() を待つ;
})();
検出で失敗。
ランタイム・モニタリング - MutationObserver
js
(function(){)
const obs = new MutationObserver(muts=>{)
muts.forEach(m=>{
m.addedNodes.forEach(n=>{)
if(n.nodeType==1){。
const html = n.innerHTML || '';
if(/on(error|click|load)|<scriptb/i.test(html)){。
navigator.sendBeacon('/_monitoring/xss', JSON.stringify({)
url: location.href, snippet: html.slice(0,200)
}));
}
}
});
});
});
obs.observe(document.documentElement, {childList:true, subtree:true});
})();
予期せぬDOM注入を警告するステージングに有用。
ブラウザのセキュリティ強化 - CSPとTrusted Types
CSPヘッダー:
pgsql
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-XYZ'; object-src 'none'; base-uri 'self';
トラステッド・タイプのスニペット
js
window.trustedTypes?.createPolicy('セーフポリシー', {)
createHTML: s => { throw new Error('Direct HTML assignment blocked'); }、
createScript: s => { throw new Error('Direct script creation blocked'); }.
});
これはデフォルトで信頼できないシンクを拒否する。
第三者によるスクリプトの安全性 - SRI
バッシュ
openssl dgst -sha384 -binary vendor.js | openssl base64 -A
用途 。 をピンして検証する。
自動化のためのPenligentとの統合
もしあなたのチームがPenligentを使っているなら、DOM XSS対策を継続的なパイプラインに昇格させることができます。Penligentの調査記事では、次のように指摘しています。 "ランタイム・テイント・トラッキングによってDOMベースのXSSを検出する......サーバーサイドのテクニックでは、クライアントサイド・インジェクションを確実に捉えることはできない。" 寡黙
ワークフローの例:
- CIでは、ルールセットでPenligentスキャンをトリガーする。
ドムエックスエスのようなペイロードを提供する。#<img src="x" onerror="alert(1)">. - Penligentはヘッドレスフローを実行し、PoCを生成し、Webhook経由で調査結果を返す。
- CIによる分析結果:重大度≧高であれば、ビルドを失敗させ、PRにペイロード+シンク+修正勧告の注釈をつける(例えば、「place」)。
インナーHTMLとsafe-dom.setSafeHTML). - 開発者が修正し、CIを再実行し、緑色のときだけマージする。
これにより、参照(このカンニングペーパー)→コードポリシー→自動検出→組織的修正というループが完成する。
結論
フロントエンドはもはや「単なるUI」ではない。それは 攻撃表面.このチートシートでは、クライアントサイドインジェクションの理解、危険なシンクの置き換え、安全なヘルパーライブラリの構築、静的/CI/実行時検出の導入、Penligentのようなプラットフォームによる自動化について説明します。次のステップ: 禁止されているシンク (インナーHTML, 評価)を採用する。 セーフドム ライブラリ、lint ルールの適用、ヘッドレス・テストと実際のペンテスト・ロジックの統合、本番/ステージングの監視、サードパーティ・リソースのピン留めなどです。DOM XSSを保護することは、DOM XSSを以下のようにすることです。 不可能 偶然に頼るのではなく、すり抜けるために。

