サマリ
ISO-2022-JPエンコーディングの誤判定を悪用したXSSの技法としてEncoding Differentials: Why Charset Matters | SonarのTechnique 2を紹介する。これは、HTMLの属性を囲むダブルクォート等をISO-2022-JPの2バイト文字と誤認させることによって引用符の効果を無効化させることによるXSSである。本稿で紹介する攻撃は、従来からのセキュリティベストプラクティスである「文字エンコーディングの明示」に従っていれば影響を受けることはない。
ここで紹介する攻撃は、はせがわようすけ氏のブログ記事ISO-2022-JPによるXSSの話 - 葉っぱ日記で紹介されている「2. 属性値やテキストノードのエスケープのバイパス」と同じ原理であり、本記事の方が条件が複雑であるが、エスケープ対象の文字に関する制約は緩やかである。
はじめに
昨年末のブログ投稿「ISO-2022-JPの自動判定によるクロスサイト・スクリプティング(XSS)」で、ISO-2022-JPの悪用により、バックスラッシュを円記号に誤認させることにより、バックスラッシュによるエスケープを無効化するXSSについて紹介しました。この攻撃は昨年(2024年)7月にSonarSourceブログで投稿された方法のTechnique 1に該当しますが、本稿では同じ記事から「Technique 2 HTMLコンテキストの破壊」を紹介したいと思います。
ISO-2022-JP文字エンコーディングそのものや、その自動判定については説明を繰り返さないため、前回のブログ記事を参照ください。
ダブルクォートを無効化できればXSS攻撃が可能になる
安全なウェブサイトの作り方や私の本では、クロスサイト・スクリプティング脆弱性の必須対策として、「属性値は」ダブルクォートで囲むように要求しています。
HTMLタグを出力する場合は、その属性値を必ず「"」(ダブルクォート)で括るようにします。そして、「"」で括られた属性値に含まれる「"」を、HTMLエンティティ「"」にエスケープします。
安全なウェブサイトの作り方 - 1.5 クロスサイト・スクリプティングより引用
具体例で見てみましょう。Webアプリケーションにおいて以下のようなinput要素を生成しているとします。
<input name=mail value=外部の値>
外部の値はHTMLエスケープを行って表示されているとします。エケスープされている場合でも、外部の値として以下を指定することでXSS攻撃ができます。
1 onmouseover=alert(1)
生成されるinput要素は以下となります。
<input name=mail value=1 onmouseover=alert(1)>
外部からの入力はすべてvalue属性になるはずが、空白により属性値が終わってしまい、新たなonmouseover属性(イベント)ができてしまいます。この攻撃は、属性値をダブルクォート等で囲むことで防ぐことができます。
過去の攻撃手法: Shift_JISの先行バイトによるXSS
過去に有効だった文字コードXSSとして、Shift_JISの先行バイトによりダブルクォート等を無効化するテクニックが発見されています。はせがわようすけさんの記事を参照します。
詳しくは上記記事を読んでいただくとして、外部入力として0x82等のバイト値を入力することで、属性値を囲っているダブルクォート(0x22)と合わせて1文字と認識させようというテクニックです。これは過去にIEやFirefoxで成立していましたが、これらブラウザでもかなり前から対策されていて、現在この攻撃はできません。ブラウザ側の対策は、Shift_JISの2バイト目としてはダブルクォート(0x22)やシングルクォート(0x27)に該当するバイトは出現しえないため、\x82\x22等の並びはShift_JISとしては不正な文字であることを利用していると思います。
ISO-2022-JPの自動認識によりダブルクォート等を無効化する
これに対して、SonarSourceブログで紹介されたテクニックは、引用符の前にISO-2022-JPのエスケープシーケンスによりJIS X 0208の2バイト文字の開始を指定することにより、引用符を2バイト文字と認識させる方法です。当該記事では、マークダウンをHTMLに変換するプログラムにより、img要素を生成するシナリオを用いて説明しています。
マークダウンでのimg要素の一般的な記法は以下の通りです。
![ALT文字列](https://example.jp/a.png)
これに対して、下記のHTMLが生成されます。
<img src="https://example.jp/a.png" alt="ALT文字列"/>
SonarSourceブログでは、以下のように2つの画像とその間の文字列の構成を利用しています。
![AAA](BBB) CCC ![DDD](EEE)
これは以下のように変換されます。
<img src="BBB" alt="AAA"/> CCC <img src="EEE" alt="DDD"/>
攻撃は、まずAAAの箇所に ESC$@(ESCは0x1b)を指定します。これはISO-2022-JPにおいて、日本語(JIS X 0208)の開始を意味します。すると、AAA直後の「"」以降が2バイト文字と解釈されます(下図)。
このままだと攻撃はできないので、CCCの位置にESC(Bを指定します。これはASCIIの開始を意味するため、これ以降がASCIIとして解釈されます(下図)。
上記の赤い網掛けの箇所に注目ください。alt=を閉じるダブルクォートが文字化けしたため、<img src=までが属性値として扱われ、EEEの部分は属性値からはみ出しています。ここに攻撃文字列を指定できます。なので、EEEの箇所に onerror=alert(1) を指定することでXSS攻撃ができます。
検証用PHPスクリプト
ここで、検証用のPHPスクリプトを用意しました。これは画像のみを解釈できるマークダウンパーサーです。正規表現で![xxx](yyy)を探索してimg要素に変換するだけの単純なものです。元のテキストは全てHTMLエスケープされるので、簡単にはXSSはできないはずです。
<body>
<?php
$md = $_GET['md'];
$html = preg_replace('/!\[(.*?)\]\((.*?)\)/',
'<img src="\2" alt="\1">',
htmlspecialchars($md));
echo $html;
?></body>
このスクリプトに以下のクエリ文字列を指定してみましょう。
md=![%1b$@](BBB)%1b(B![DDD](+onerror=alert(1))
攻撃は失敗します。生成されたHTMLソースを見てみましょう。
alert(1" となっています。これは、![]()に対して、alert(1)の右括弧が反応してしまったものです。これを避ける方法はあるでしょうか。
一つの方法はalert(1)の代わりにaler`1` を指定するものです。これはタグ付きテンプレートと呼ばれるものです。詳しくはMDNの解説を参照ください。
これにより攻撃が成功します。onerror=alert`1`の前後に空白を挟んでいます。
md=![%1b$@](BBB)%1b(B![DDD](+onerror=alert`1`+)
↓
<img src="BBB" alt="⊂<img src=" onerror=alert`1` " alt="DDD">
ところで、Qiitaを含む一般的なマークダウンパーサーでは、括弧の対応をきちんとチェックしてくれるようです。なので、先のPHPスクリプトを括弧の対応を見るように変更してみました。PHPのPCRE拡張正規表現を使っています。
$html = preg_replace('/!\[(.*?)\]\((((?>[^()]+)|\((?2)\))*)\)/',
'<img src="\2" alt="\1" />',
htmlspecialchars($md));
これだと、onerror=alert(1) も期待通り攻撃が成功します!
対策
ISO-2022-JPの自動判定によるXSSの対策は以下の通りです。
- ウェブコンテンツの文字エンコーディングを適切に行う
これは従来からのセキュリティベストプラクティスであり、元々セキュアなアプリケーションが、この攻撃により突然危険になるわけではありません。
まとめ
HTMLの属性を囲むダブルクォート等をISO-2022-JPの2バイト文字と誤認させることによって、引用符の効果を無効化させることによるXSSについて説明しました。この攻撃が成立するために必要な条件は複雑で、かなりわざとらしいCTFのような状況でしか再現しなさそうではありますが、それはともかくとして、ウェブコンテンツの文字エンコーディングを適切に設定することをお勧めします。
0 件のコメント:
コメントを投稿