2025年1月6日月曜日

ISO-2022-JP自動判定を用いたHTMLコンテキスト破壊によるXSS

サマリ

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のような状況でしか再現しなさそうではありますが、それはともかくとして、ウェブコンテンツの文字エンコーディングを適切に設定することをお勧めします。

2024年12月31日火曜日

ISO-2022-JPの自動判定によるクロスサイト・スクリプティング(XSS)

サマリ

ISO-2022-JPという文字エンコーディングの自動判定を悪用したクロスサイト・スクリプティング(XSS)攻撃について説明する。これは、文字エンコーディングを適切に指定していないウェブコンテンツに対して、文字エンコーディングをISO-2022-JPと誤認させることでバックスラッシュが円記号と解釈されることによりエスケープ処理を回避する攻撃である。本稿で紹介する攻撃は、従来からのセキュリティベストプラクティスである「文字エンコーディングの明示」に従っていれば影響を受けることはない。

はじめに

クロスサイト・スクリプティング対策として、記号文字のエスケープ処理に加えて、コンテンツの文字エンコーディングをレスポンスヘッダやmetaタグで明示しましょうと言われてきました(参照)。その背景として、UTF-7という文字エンコーディングを悪用したXSSの存在がありました。この攻撃については以下の記事を参照ください。

はせがわようすけさんの記事にもあるように、UTF-7によるXSSは、UTF-7に特徴的な文字列を注入することで、ブラウザに文字エンコーディングをUTF-7と誤認させることによるものです。
しかし、上記で「ありました」と表現している理由は、この問題がInternet Explorer(IE)限定の問題であること、IEでもかなり前に対策が進められて、現実的な攻撃は困難になっていたことによります。このため、徳丸本初版(2011年3月)でもUTF-7によるXSSは取り上げていません。
そのため、以下の記事に見られるように、「もうウェブコンテンツに文字エンコーディングが明示されていなくても脆弱性として指摘しなくてもよいのでは?」という意見も見られます。

令和になった今、Content-TypeヘッダのCharset付与によるクロスサイトスクリプティングについて考えてみる - 僕と技術とセキュリティ

ところが、今年(2024年)の7月に、SonarSourceのブログに以下の記事が投稿され、早々にCTFの作問等に応用されました。

Encoding Differentials: Why Charset Matters | Sonar

この記事ではISO-2022-JP文字エンコーディングの文字列を注入することにより、ブラウザにコンテンツの文字エンコーディングをISO-2022-JPと誤認させるテクニックです。すなわち、「文字エンコーディングを誤認させる」という点で、この記事の手法はUTF-7によるXSSの後継と考えられます。以下、上記ブログ記事のTechnique 1のパターンについて説明します。

ISO-2022-JP とはなにか

まずは、ISO-2022-JPについて説明しましょう。徳丸本2版から、ISO-2022-JPの説明を引用します。

ISO-2022-JPは7ビットの文字エンコーディングで、エスケープシーケンスという符号により文字集合(US-ASCIIとJIS X 0208)を切り替える方式です。「JISコード」と呼ばれる場合もあります。図6-15にエスケープシーケンスの例として、ISO-2022-JP符号化された「ABCと漢字!」という文字列を図示します。

 同図で、「ESC $ B」がJIS X 0208の始まり、「ESC ( B」がUS-ASCIIの始まりを示します。ISO-2022-JPは状態の切り替えを伴うため、コンピュータ上の内部処理や検索用のデータとしては適していません。歴史的な理由から、主に電子メールの伝送に用いられてきました。
 なお、「インターネットでは半角片仮名を使うな」という主張を目にする場合がありますが、これはISO-2022-JPが半角片仮名(JIS X 0201の片仮名)をサポートしないことに由来しています。
体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 P535より引用

上記には言及されていませんが、ISO-2022-JPは、US-ASCIIとJIS X 0208以外も扱えるようになっていまして、主なものとしては以下があります。表はとほほの文字コード入門から引用しました。

記号表記 16進表記 意味
ESC ( B 1B 28 42 ASCII。
ESC ( J 1B 28 4A JIS X 0201(旧称 JIS C 6220)-1976 ラテン文字集合
ESC $ @ 1B 24 40 JIS X 0208(旧称 JIS C 6226)-1978(通称:旧JIS)
ESC $ B 1B 24 42 JIS X 0208-1983(通称:新JIS) または JIS X 0208-1990

下の2種類はJIS X 0208の旧規格と新規格なので中身は少し違うもののセキュリティ上の違いはありませんが、問題はJIS X 0201です。これは8ビットの1バイトでASCIIと半角カナを使えるようにした文字集合です。以下、英語版のWikipediaより引用です。


https://en.wikipedia.org/wiki/JIS_X_0201 より引用

この赤枠で囲った箇所がASCIIとは異なっていて、\(バックスラッシュ)の代わりに¥(円記号)、~(チルダ)の代わりに ‾ (オーバーライン)が割り当てられています。ISO-2022-JPでは、片仮名の部分は割り当てられておらず、ローマ字と記号の部分(0x00~0x7F)のみが使えます。ISO-2022-JPで半角片仮名が使えないのは、この制約によるものです。

ISO-2022-JPでは、エスケープシーケンス ESC $ J によりJIS X 0201に切り替えができますが、使えるのはローマ字部分のみで、ASCIIとの違いは\ → ¥、~ → ‾ のみなので実用上はわざわざ用意する意味がないように思える一方、これがセキュリティ上問題になります。

ISO-2022-JPによるXSSの基本的な考え方

我々に馴染み深いShift_JIS等ですと、表示上は ¥ となっていても、ブログラミング等ではバックスラッシュとして扱われますが、JIS X 0201の ¥ はあくまで円記号でありバックスラッシュではありません。このため、JavaScript等でエスケープ処理のために使ったバックスラッシュが円記号に化けてしまうと、「エスケープ処理が無効になってしまう」というセキュリティ上の問題になってしまいます。

たとえば、PHP等でJavaScriptを以下のように動的生成する箇所があったとします。

const v = "ここを動的生成する";

動的生成の箇所に "; alert(1)// を入力するXSSを防ごうとすると、以下のようにエスケープ処理するはずです。

const v = "\"; alert(1)//";

ところが、\が¥に化けると以下のようになり、XSS攻撃ができてしまいます。

const v = "¥"; alert(1)//";

日本のプログラマだと¥がバックスラッシュに見えてしまう方が多いと思いますが、上記はバックスラッシュではない円記号です。なので、文字列リテラルは “¥” だけとなり、後続の ; alert(1)//が文字列リテラルからはみ出し、実行されてしまいます(//以下はJavaScriptのコメントとなり無視されます。JavaScriptは行末のセミコロンは省略可能です)。

文字エンコーディングの自動判定

そうは言っても、文字エンコーディングがISO-2022-JPと認識されていなければ問題はありません。具体的には、レスポンスヘッダやmeta要素でcharsetが明示されている場合は、ISO-2022-JPと誤認されることはありません。問題は、適切な文字エンコーディング指定がない場合です。具体的には、以下のケースです。

  • 文字エンコーディング指定がない
  • 文字エンコーディング指定はあるが間違っている(charset=EUC_JP等)

これらのケースでは、ブラウザはテキストの中身から文字エンコーディングを「推測」します。このため、ISO-2022-JPに特徴的なエスケープシーケンスがある場合、ISO-2022-JPのコンテンツと「誤認」する場合があります。これを以下のスクリプトにより試してみましょう(実験する場合は日本語のコメントは削除してください)。

<?php
  header('Content-Type: text/html; charset=EUC_JP');  // 正しくはEUC-JP
?><body>
<?php echo htmlspecialchars($_GET['p']); ?>
<script>
  document.write("\\".codePointAt(0).toString(16)) // \のコードポイントを取得する
</script>
</body>

このスクリプトを p=%1b(J というクエリ文字列を指定して実行すると a5 と表示されます。バックスラッシュのコードポイントはU+005C、円記号のそれはU+00A5ですから、ESC ( J の指定によりJIS X 0201と解釈され、バックスラッシュが円記号に化けていることが分かります。

XSSを試す

ISO-2022-JPによるXSSの肝は、バックスラッシュが円記号に誤認されるところですから、バックスラッシュによりエスケープ処理を行っているスクリプトが脆弱性の対象になります。その具体例として、以下のPHPスクリプトを用います。

<?php
  header('Content-Type: text/html; charset=EUC_JP');
  function escapeJS($str) {  // Escape function for JavaScript String
    $replacements = [
        '\\' => '\\\\',
        '"'  => '\\"',
        "'"  => "\\'",
        "\n" => '\\n',
        "\r" => '\\r',
        "\t" => '\\t',
        '<'  => '\\u003C',
        '>'  => '\\u003E'
    ];
    return strtr($str, $replacements);
  }
?><body>
<script>
  const v = "<?php echo escapeJS($_GET['p']); ?>"
  console.log(v)
</script>
</body>

このスクリプトは、クエリー文字列 p をJavaScriptの変数 v に代入していて、その際に関数escapeJSを呼んでJavaScript文字列リテラルのエスケープ処理を行っています。小なり・大なり記号のエスケープはJavaScriptの文法上は必要ありませんが、これがないと、</script><script>alert(1)</script> のような形で、いったんscriptタグを終端するという攻撃ができるための処理です。
これに対する攻撃文字列は以下の通りです。

p=%1b(J";alert(1)//

実行結果は以下となります。

この際に生成される該当箇所を示します。まずは16進数ダンプ。

赤枠で囲った部分が ESC (J であり、JIS X 0201への切り替えのエスケープシーケンスです。
その結果、当該箇所は以下のようにデコードされます。

const v = "¥";alert(1)//"

¥はバックスラッシュではない円記号なので、直後のダブルクォート「"」で文字列リテラルが閉じられ、後続のalertが実行されていることが分かります。

コンテンツにマルチバイト文字があればどうなるか?

この攻撃は、文字エンコーディングの自動判定の結果ISO-2022-JPと判定されることが条件なので、元々コンテンツにShift_JISやEUC-JP等の日本語があれば1、ISO-2022-JPと本来の文字エンコーディングのどちらが「勝つ」かが問題になります。私の調べた範囲では、文字列を注入できる箇所(外部入力の表示など)の前にマルチバイトの文字があると、ISO-2022-JPとは判定されないようです。かと言ってUTF-8やShift_JIS、EUC-JP等と判定されるわけでもなく、windows-1252(ISO-8859-1に類似のMS独自文字エンコーディング)と判定されることが多いようです(下図)。

<head>
<title>日本語のタイトル</title>
</head>
<body>
ESC ( J 等としてもISO-2022-JPとは判定されない
</body>

一方、コンテンツが英字のみで構成されていると、ISO-2022-JPと判定させることは容易になります。
それでは、外部入力を注入できる箇所より「後に」マルチバイト文字列がある場合はどうでしょうか。下図のようなケースです。

<head>
<title>English Title</title>
</head>
<body>
<div>ここに外部入力を注入できる</div>
<div>日本語コンテンツ(Shift_JISあるいはEUC-JP)がある</div>
</body>

このようなケースでは、ISO-2022-JPの文字列を長くすることで、後ろにUTF-8等のマルチバイトの文字があっても、ISO-2022-JPと判定されることが分かりました。ただし、ブラウザにより挙動が異なります。まず、実験に用いたPHPスクリプトを以下に示します。

<?php
  function escapeJS($str) {  // Escape function for JavaScript String
    /* Same as before. Omitted. */
  }
?><body>
<?php
  echo htmlspecialchars($_GET['txt']);
?>
<script>
  const v = "<?php echo escapeJS($_GET['js']); ?>"
  console.log(v)
</script>
<!-- 雀の往来(EUC-JPコンテンツのための魔法の文字列) -->
<p>こんにちは こんにちは!!</p>
</body>

このスクリプトは2つのクエリ文字列txtとjsを取ります。txtは通常のテキスト(要素内容)としてHTMLエスケープして表示します。jsはJavaScriptエスケープしてJavaScriptの文字列リテラルとして埋め込まれます。txtの方は、元コンテンツの日本語が出現する前に置かれている必要があります。jsの方は場所はどこでも問題ありません。「雀の往来」はEUC-JP文字エンコーディングのコンテンツを文字化けしにくくする「魔法の文字列」です(参照)。「雀の往来」は実験には必要ありませんが、ノリで入れてみました。

Google Chrome

Google Chrome用の攻撃文字列は以下となります(バージョン131.0.6778.205で確認)。

http://example.jp/iso-xss-m.php?js=%1B(J";alert(1)//&txt=%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA

クエリー文字列 js は先のものと同じです。txtの方は、以下の文字列「誓A」を8個繰り返したものです。見やすくするために空白を挟んでいます。

ESC $@ @@ ESC (B A

jsの方(JavaScrpit文字列)で文字エンコーディングの誤認がさせられないか調べましたが、私が試した範囲ではできませんでした。なので、「文字エンコーディングの誤判定(txt)」と「XSS攻撃(js)」は別に指定する必要があるようです。

Firefox

Firefox用の攻撃文字列は以下です(バージョン133.03で確認)。

http://example.jp/iso-xss-m.php?js=%1B(J";alert(1)//&txt=%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA

js側はGoogle Chromeと同じですが、txtの方は先の「誓A」を120個続けた形となっています。パーセントデコード後のバイト数は1080バイトなので、文字数制限をしているフィールドだと攻撃は難しいかもしれません。
Firefox特有の性質が2つ見つかりました。
まず、Firefoxは文字エンコーディングの自動判定する際に、コンテンツがある程度の長さがあると、レンダリングを2回行う場合があります。1回目は最初に判定した文字エンコーディングでレンダリングして、コンテンツ全体から判定した文字エンコーディングが1回目と異なる場合、もう一度レンダリングを用います。そうすると、先の「誓A」でいったんISO-2022-JPによりレンダリングした後、後続のEUC-JP等のデータも合わせて他のエンコーディング(典型的にはwindows-1252)でレンダリングし直します。これに合わせてJavaScriptも2回実行されます。
この場合、1回目のJavaScript実行の際に文字エンコーディングがISO-2022-JPと判定されているので、XSS攻撃は成功します。2回目は攻撃に失敗するのですが、1回成功すれば攻撃には十分です。
次に、Firefoxの場合だと、js側に「文字エンコーディング誤判定用の文字列」を入れても攻撃は成功します。なので、攻撃対象フィールドが1つしかない場合でも攻撃できる可能性があります。

Safari

Safari(macOS 15.2上のSafari 18.2)で確認したところでは、Safariは文字エンコーディングの自動判定の結果ISO-2022-JPを選択することはないようです。明示的にISO-2022-JPを指定することはできますが、本稿で説明するISO-2022-JPの自動判定による攻撃のパターンはできないようです。

影響を受けるアプリケーション

この攻撃の影響を受ける条件は下記の両方を満たす場合です。

  • 文字エンコーディングを正しく指定していない
  • バックスラッシュによるエスケープ処理でXSS対策をしている

ただし、日本語のコンテンツだと通常title要素として日本語が記載されているわけで、その前に「外部から文字列を注入できる箇所がある」というのはレアケースかなと思います。しかし、多国語化対応されているサイトであれば、英語版のページを狙うことで攻撃成功の可能性は高まると思います。

対策

ISO-2022-JPの自動判定によるXSSの対策は以下の通りです。

  • ウェブコンテンツの文字エンコーディングを適切に行う

また、JavaScriptの動的生成は元々XSS対策が難しいので、HTML上のカスタムデータ属性に動的な値を設定して、その値をJavaScriptから読み込むという方法を推奨します。詳しくは拙著「体系的に学ぶ 安全なWebアプリケーションの作り方 第2版」を参照ください。

まとめ

ISO-2022-JPの自動判定によるXSSについて説明しました。ウェブコンテンツの文字エンコーディングの設定漏れは脆弱性と言えるのかという意見も見られるところでしたが、やはり文字エンコーディングは適切に設定しましょう。文字エンコーディングの指定は元々必要な処理なので、「既知の攻撃手法ができなくなったから、もう対応しなくてよい」というものではありません。


  1. 文字エンコーディングを指定しない場合、UTF-8は自動判定されないようなので、Shift_JISあるいはEUC-JPを使っている場合が問題になると思います。 ↩︎

2024年4月1日月曜日

「はじめて学ぶ最新サイバーセキュリティ講義」の監訳を担当しました

 日経BPから4月4日発売予定の『はじめて学ぶ最新サイバーセキュリティ講義 「都市伝説」と「誤解」を乗り越え、正しい知識と対策を身につける』の監訳を担当したので紹介させていただきます。

本書の原書は、ユージーン・H・スパフォード、レイ・メトカーフ、ジョサイヤ・ダイクストラの3名の共著として書かれた「Cybersecurity Myths and Misconceptions」で、米国Amazonのレビューでは4.6の高評価を得ています。また、「インターネットの父」ことヴィントン・サーフ氏が本書に前書きを寄せています(後述)。


はじめに

サイバーセキュリティは、その短い歴史にも関わらず、神話や都市伝説に満ちています。古典的なものとして、本書の冒頭では、「ウイルス対策企業が自社製品を売るためにマルウェアを作って拡散した」が紹介されています。

本書は、このようなセキュリティの都市伝説や神話をとりあげ、これらの「ウソを暴き」ながら、セキュリティの真髄を説明していくスタイルをとっています。

本書によると、「この本は、一般的な問題、人間の問題、文脈的な問題、データの問題という4つのパートに分かれ、175件以上の都市伝説、偏見、誤解を収めています。」とあります。175件! すごいですね。

割合とっつきやすい都市伝説から紹介しましょう。それは「パスワードはひんぱんに変更せよ」です。

かつて、セキュリティのベストプラクティスとして「パスワードを定期的に変更を強制する」がありましたが、さまざまな議論を経た後に、2017年6月アメリカ国立標準技術研究所(NIST)がNIST SP 800-63-3を発表し、その中でパスワードの定期的変更の強制を否定したことで、一応の「都市伝説の終結」をみました。

とはいえ、パスワードの定期的変更は永く信じられてきた「ベストプラクティス」なので、各種ガイドライン等には今でも残っていますし、セキュリティに携わるものでも、「パスワードは定期的に変更するべきもの」と思っている人は多いと思います。本書は、この種の神話や都市伝説を多数取り上げ、次々に否定していきます。


意外なところにも「神話」が

神話の中には、読者には意外なものも含まれます。たとえば、「ユーザーが最弱リンク」がそれです。

サイバースペースでは、サイバーセキュリティの最弱リンクはユーザーであるとの言説が不当に支持されていますが、それは誤りです。

意外に思いませんか? なぜこれが不当なのかはぜひ本書で確かめてください。ヒントとして、本書から下記を引用しておきます。

人々を力づけたいという思いやりにより、ユーザーは高いセキュリティ意識を持つパートナーになります。そのように意識を改めることで、強い信頼関係が生まれます。ミスを報告しやすくして、修正の手助けをする。それこそが、ユーザーを見下しミスを責めるよりもずっと望ましい態度と言えます。

もう一つは、「攻めるほうが守るよりも楽」です。よく攻撃側に比べて防御側は不利と言われます。本書でも、この節は以下の書き出しから始まっています。

サイバーセキュリティ分野の非常に多くの尊敬すべき人たちが口をそろえてこう言っていました。「攻撃側はたったひとつの突破口があればいいのに対して、防御側はあらゆる攻撃に備えなければならないため不利だ」と。アタックサーフェイス(攻撃対象領域)で考えれば、この主張は本当らしく聞こえます。たくさんのサーフェイスを防衛するとなると、リソースを薄く広く分散しなければなりません。攻撃側がサーフェイス点にリソースを集中し、攻撃方法もひとつに絞ってきたなら、たしかに相手が圧倒的に有利です。

ではあるのですが、現実の局面では、攻撃よりも守備がかんたんというケースもあることと、あまりに「防御側が不利」と強調することによるセキュリティ担当者へのメンタルへの影響に本書は言及しています。


徳丸は何をしたか

私は主に以下を担当しました。

  • 訳語として日本のセキュリティ分野の一般的な用語を選択する
  • 翻訳を読んで意味の通りにくいところの訳を手直しする
  • 米国と日本で違いがある箇所(法律など)などに監訳注をつける

原書は、凝った文体や、反語のようなレトリック、古典や著名な映画を踏まえた表現、韻を踏んでいる箇所などがあり、「米国のインテリはこういう文体が好きなのかなぁ」と感慨に耽っておりましたが、できるだけ日本の読者に読みやすい訳を心がけました。

その一つとして、原書の見出しは「正しいこと、望ましいこと」と「神話、都市伝説、誤解」が特に区別なく並べられています。これだと読者は混乱すると思い、編集部と相談した結果、「神話、都市伝説、誤解」に相当する見出しにはメガホンのアイコンをつけました。下図に例を示します。


本書の対象読者

本書の対象読者は、エンジニアやセキュリティ担当者だけでなく、もっと広範囲にセキュリティに関係する人たちです。本書の出版元のページでは以下のように紹介されています。
本書は、開発者、デザイナー、アナリスト、意思決定を行う人、学生など、プロ、アマを問わずサイバーセキュリティに関係する人たちに向けて書かれています。加えて、サイバーセキュリティに関わっていない人にも役立ちます。テクノロジーに依存しているなら、サイバー防衛と無縁ではいられないからです。あなたもきっと含まれるはずです。

本書の読み方と得られるもの

本書は技術書ではなく、エッセイ集のような形ですので、一つ一つの節は割合読みやすいと思いますが、神話が175件も集めてあるだけあって500ページ以上の大部となっています。いきなり通読する必要もないため、読みたいところから拾い読みのような形でも良いと思います。とはいえ、本書を読み進めることにより、ハウツー本からは得られないセキュリティの本質的な思考方法が習得できます。

これに関しては、「インターネットの父」ことヴィントン・サーフ氏が本書に前書きを寄せていますが、その中に以下の一節があります。

なかでも強力な防衛ツールに「クリティカルシンキング(懐疑的思考)」があります。この本が伝えようとしているのは、サイバー空間でのリスクに対してもっと懐疑的になる方法です。たやすく身につくものではないので、多少の訓練は必要になるでしょう。悪者は我々の人間としての弱みにつけ込んできます。悲しいかな、困っている人を助けずにはいられない私たちの善意すら狙われます。そのため、そうした人間性やポジティブな社会感情を狙った詐欺は数知れません。この本は、そんな策略を見破る方法を教えてくれます。

本書でクリティカルシンキングを身につければ、詐欺行為のみならず、ネットのセキュリティ記事に右往左往することなく、どっしりと構えつつ、本当に必要な備えは何か、自分で考えられるようになるでしょう。

2023年4月3日月曜日

2023年4月においてクリックジャッキング未対策のサイトはどの条件で被害を受けるか

サマリ

CookieやlocalStorage等でセッション管理しているウェブサイトがクリックジャッキング対策していない場合、どの条件で被害を受けるかを説明する。SameSite属性のないCookieでセッション管理しているウェブサイトは、主要ブラウザのデフォルト設定ではクリックジャッキングの影響を受けない。一方、loaclStorageにトークン類を格納するウェブサイトでは、Google Chrome等のブラウザでクリックジャッキングの影響がある。また、ブラウザの設定を変更した場合の影響についても説明する。

クリックジャッキングとは

クリックジャッキングとは、一言で説明すると「ウェブサイト利用者に意図しないクリック(タップ)をさせる」攻撃です。ウェブサイト上で意図しないクリックを勝手にさせられると、重大な結果になる場合があります。例えば、このURLを閲覧すると、以下のようにTwitterにて「犯行予告を投稿する」画面になり、「ツイートする」を押すと、押した人のアカウントで犯行予告をツイートする結果になります。Twitterのこの機能はウェブインテントと呼ばれます。

このような拙い「攻撃」だと、被害者は画面内容を見た結果ツイートボタンは押さないわけですが、これにiframeを用いた仕掛けを組み合わせて「知らない間にボタンを押させる攻撃」がクリックジャッキングです。具体的には、iframeを用いて、下図のようにボタンを押させるための罠と、Twitter等の掲示板の画面を重ねます。

この際、掲示板を手前に配置してCSS設定により透明にします。一方罠サイトは奥側に配置します。すると、見た目上は罠サイトだけが見えますが、ボタンをクリックすると手前側の掲示板のボタンを押したことになり、ログイン中の利用者のアカウントで意図しない投稿がされます。これがクリックジャッキングです。現実のTwitterはクリックジャッキング対策がされているので、この攻撃は成立しません。

この例では「意図しない投稿」でしたが、これ以外に、設定変更や退会など「クリックだけでできる操作」全てで影響がありえます。

クリックジャッキング対策には、以下のようなレスポンスヘッダを出力することで行われます。

Content-Security-Policy: frame-ancestors 'none';

上図はCSPを用いた対策ですが、伝統的なX-Frame-Optionsヘッダを用いた対策も有効です。

しかしながら、上記対策をしていない場合でも、モダンブラウザでは、iframe内のコンテンツからCookieおよびlocalStorageへのアクセスが制御されていて、クリックジャッキング攻撃ができないケースが多くなっています。
本稿では、モダンなブラウザの利用者がクリックジャッキング被害を受ける条件について説明します。

検証方法

今回は、クリックジャッキングの影響の有無を、iframe内のウェブページがCookieやlocalStorageの値を受け取れるかどうかで判断することにしました。もしもiframe内のページがCookieやlocalStorageにアクセスできない場合、ページはログイン状態にはならず、被害者のアカウントでの投稿や設定変更などはできないからです。
下図は検証環境の模式図です。サイトAがクリックジャッキング脆弱なサイト、サイトBは罠サイトです。

サイトAのログインを模して、CookieやlocalStorage、sessionStorageをセットします(図の左側)。この後サイトBに遷移しますが、サイトB内にiframeがあり、その中にサイトAのページがあります(図の右側)。このiframe内のサイトAのページでCookie、localStorage、sessionStorageの値を表示して、これらの値を受け取れているかを確認します。

CookieのSameSite属性は以下の3種類のものを用意しました。SameSite属性の意味についてはこちらの記事を参照ください。

  • SameSite属性なし
  • SameSite=None
  • SameSite=Lax

SameSite属性なしは伝統的なセッション管理を想定しています。SameSite=Laxの場合、iframe内のコンテンツにCookieは送信されないはずですが、比較のためテスト条件に加えました。

検証結果

検証結果を下表に示します。

結果の要約は下記の通りです。

  • SameSite属性のないCookieでセッション管理しているサイトは全てのブラウザにてクリックジャッキングの被害を受けない
  • localStorageでセッション管理しているサイトは、Google Chrome、Edge、Opera(デスクトップ、Android)のユーザが被害を受ける
  • Brave、Firefox、Safariの利用者はいずれのケースでもクリックジャッキングの被害を受けない

このように、ブラウザのセキュリティ機能の差により、クリックジャッキング被害の条件が変わっています。


各ブラウザのセキュリティ機能

次に、各ブラウザのセキュリティ機能がどのようにクリックジャッキングを防いでいるかを説明します。

Chromium系ブラウザの防御はデフォルトSameSite=Lax

CookieのSameSite属性にてLaxあるいはStrictを指定した場合、iframe内に置かれたページに対してはCookieは送信されません。これはすべてのブラウザに共通の仕様ですが、Chromium系のブラウザ(Google Chrome、Edge、Brave、Opera…)では、SameSite属性のないCookieはSameSite=Laxとして扱われます(参考)。
このため、SameSite=Noneを明示的に設定していないCookieは、iframe内のページには送信されないことになります。SameSite属性は、もともとはCSRF対策を意図した機能と思われますが、クリックジャッキング対策にも効果があります。
デフォルトSameSite=Laxの効果はCookieのみであるため、sessionStorageとlocalStorageには効果が及びません。

※注
SameSite属性のないCookieは、Cookieが生成されてから2分以内であれば、クロスサイトのPOSTリクエストに付与されます(参考)が、iframeの場合はこの2分間の猶予期間はなく、ただちにSameSite=Laxとして扱われます。

Firefoxは2022年1月11日にデフォルトSameSite=Laxを導入しましたが、非互換を理由に直後にキャンセルされました(参考)。現在でも、SameSiteのデフォルトは、SameSite=None相当です。
一方、Firefoxは2022年6月14日に包括的 Cookie 保護(Total Cookie Protection:TCP)と呼ばれる機能を投入しました。TCPはセキュリティ目的というよりはプライバシー保護を目的としたものですが、クリックジャッキング防御にも効果があります。

下図は包括的Cookie保護(TCP)のイメージ図です(引用元)。

従来は一つのCookieの入れ物(cookie jar)をすべてのサイトで共有していました(上図左)が、TCPが有効になるとサイト毎にcookie jarが独立するようになります(上図右)。これだけだと抽象的でわかりにくいので、サイトAがサイトB、サイトCのiframeにて表示されている様子を用いて説明します(下図)。

上図のように、サイトAのCookieは、単独で表示されている場合(左)、サイトBのiframe内で表示されている場合(中央)、サイトCのiframeで表示されている場合(右)で、それぞれ別の値となります。この機能はサードパーティCookieによるトラッキングを防ぐ目的で導入されたものですが、セッション管理のCookieもiframe内では分離・保護されるため、結果としてクリックジャッキングに対する防御として作用します。また、Cookieだけでなく、sessionStorageやlocalStorageもサイト毎に分離されます。

なお、実験による検証の結果、Braveブラウザにも包括的Cookie保護と同様の機能が実装されているようです(sessionStorageの挙動はFirefoxともSafariとも異なりますが、詳細は省略します)。BraveはChromiumベースのブラウザなので、デフォルトSameSite=Laxも実装されています。

Safariの防御機能はITP(Intelligent Tracking Prevention)

Safari(WebKit)にはITP(Intelligent Tracking Prevention)というトラッキング防止機能があります。これにより、結果としてクリックジャッキングも防御されています。
下図は、ITPによりiframe内のページのCookieがどうなるかを模式的に示しています。

サイトAでセットされたCookieは、サイトBにiframeで埋め込まれてもCookieは送信されず、またレスポンスのSet-Cookieも無視されます。すなわち、iframe内ではCookieの送信も受信もされないということになります。

一方、sessionStorageとlocalStorageについては、FirefoxのTCPのような挙動になります。すなわち、サイト毎に分離された形になります。この挙動はWebKitのドキュメントに記載されています。

Partitioned Third-Party Storage
Third-party LocalStorage and IndexedDB are partitioned per first-party website and also made ephemeral.
試訳: サードパーティのlocalStorageとindexedDBはファーストパーティのウェブサイト毎に分離され、またエフェメラル(一時的)なものになります。

すなわち、localStorage等については、iframe等に埋め込まれていた場合、ファーストパーティのウェブサイト毎に別の名前空間になります。
しかし、実験の結果はsessionStorageも上記の分離がなされていました。Safari 16.0まではsessionStorageは分離されておらず、Safari 16.1以降においてsessionStorageもlocalStorage同様のサイト毎の分離がされているようです。私はこの件についてのドキュメントを発見できていませんので、ドキュメントをご存知の方はぜひ教えてください。また、エフェメラルいうのは、ブラウザを終了すると削除されるという意味です。通常localStorageはブラウザを終了しても保持されますが、iframe等のサードパーティで保存されたlocalStorageはブラウザの終了と共に削除されます。一方、FirefoxのlocalStorageはサイト毎に分離されていますが、ブラウザを終了しても、localStorageの値は保持されます。


防御機能を無効化するとどうなるか

ここまで、ブラウザの提供する保護機能により、クリックジャッキング攻撃から防御される仕組みをご紹介しました。しかし、これらの機能は利用者が無効にすることができます。以下、利用者が保護機能を無効にした場合の影響を調べるため、まずはブラウザ毎にセキュリティ機能を無効化する方法を説明します。以下は、セキュリティを低下させる設定なので、検証以外では使用しないことをお勧めします。また試用した後は忘れずに戻しておいてください。

Google Chrome、Edgeの場合

Google Chrome等のChromium系ブラウザにて、デフォルトSameSite=Laxを無効化するには、Windowsの場合レジストリのLegacySameSiteCookieBehaviorEnabledForDomainListにて、無効化するドメインのリストを指定します。参考記事によると、この設定は暫定的なもので将来変更される見込みです。設定例を下図に示します。この設定では、すべてのドメインにてデフォルトSameSite=Laxを無効化しています。危険な設定なので、検証用途以外には使わないでください。


同じ方法でEdgeでも設定可能です。

Firefoxの場合

FirefoxのTCPを無効化するためには、設定|プライバシーとセキュリティから、強化型トラッキング防止機能から、カスタムを選択して、Cookieのチェックを外すか、その右のセレクトボックスで「クロスサイトトラッキングCookie(一番上)」を選択します。


Safari

SafariのITPを無効化するには、Safariの設定からプライバシーを選び、「サイト超えトラッキングを防ぐ」のチェックを外します。


結果

上記の設定変更をした結果を下表に示します。赤字で(*)付きで示した項は設定変更により条件が変わっていることを示します。


考察

前述の設定変更により、以下の状態となりました。

  • ブラウザ側の変更により、ほぼ全ての条件でクリックジャッキングの影響を受けるようになる
  • FirefoxのTCP、およびSafariのITPはプライバシー強化のための機能だが、セキュリティの効果もある
  • SameSite=Lax設定はクリックジャッキング対策としても効果が高い

各ブラウザの設定変更の中で、もっとも現実にありそうなものがSafariのITPの無効化です。「サイト超えトラッキングを防ぐ」でGoogle検索するとトップに表示されるのが、以下のYahoo! JAPANの記事です。

iOS 11以降では、「サイト越えトラッキングを防ぐ」がオン(有効)になっている場合があります。
「サイト越えトラッキングを防ぐ」がオンになっている場合、Cookie(クッキー)がSafari上に残らなくなります。
Yahoo! JAPANでは、複数のサービスでCookieを使用しているため、「サイト越えトラッキングを防ぐ」をオンにしていると、サービス内の機能が限定されるなど、一部のサービスを利用できません。
以下の手順を参考に、「サイト越えトラッキングを防ぐ」機能をオフにしてからYahoo! JAPANのサービスをご利用ください。
iPhone向けSafariで「サイト越えトラッキングを防ぐ」機能をオフにするより引用

しかし、当該の設定変更を行うと、Yahoo!以外のサイトでもセキュリティを弱くする結果になります。このページには以下のような記載もありますが、

注意
「サイト越えトラッキングを防ぐ」機能の設定変更は、お客様ご自身の責任において変更してください。

「お客様ご自身の責任において変更」と書いてありますが、変更による影響は何も説明されていないため、この書き方はいかがなものかという感想を持ちました。


まとめ

  • SameSite属性のないCookieによるセッション管理という伝統的なサイトはモダンなブラウザのデフォルト設定ではクリックジャッキング攻撃の影響はない
  • localStorageにトークン類を保存する実装の場合、Chromium系のブラウザ(Chrome、Edge、Opera)ではクリックジャッキング攻撃の影響がある
  • ブラウザのトラッキング機能を解除するとクリックジャッキング攻撃の影響を受けやすくなる
  • アプリケーション提供者は、ブラウザの設定によらずクリックジャッキングを防御するために、CSPやX-Frame-Optionsによりクリックジャッキング対策を実施すること
  • サイト運営者は、ブラウザの設定変更を促さないこと
  • サイト利用者は、ブラウザの設定を安易に変更しないこと

2023年3月27日月曜日

[書評] ハッキングAPI ―Web APIを攻撃から守るためのテスト技法

サマリ

ハッキングAPI―Web APIを攻撃から守るためのテスト技法(2023年3月27日発売)を読んだ。本書は、Web APIに対するセキュリティテストの全体像と具体的なテスト方法を記載している。ペンテスターは、APIの検出、APIエンドポイントの分析、攻撃(テスト)を行う必要があり、そのために必要な情報がすべて記載されている。また、実習のためのツールと「やられサイト」を複数紹介し、具体的なトレーニング方法を解説している。単にツールやサイトの使い方の説明にとどまらず、本格的なペネトレーションテストの考え方を説明している。
本書の想定読者はAPIのペネトレーションテストを実施するペンテスター及びペンテスターを目指す人であるが、API開発者やウェブアプリケーション脆弱性診断員にとっても有益な内容を多く含む。

重要事項説明

  • 本書の監修者の一人(洲崎俊氏)と評者は知人関係にある
  • 評者が読んだ書籍はご恵贈いただいたものである
  • この記事のリンクにはアフィリエイトが含まれる

APIセキュリティテストとは何か

まずは、本書の主題である「APIセキュリティテスト」を本書でどのように定義しているかを紹介しよう。以下は、本書p3からの引用である。

APIセキュリティテストは、一般的なペネトレーションテストともWebアプリケーション診断とも異なります。APIが持つ攻撃対象領域(Attack Surface)の幅広さと複雑さのため、多くのペンテスターはAPIセキュリティテストを独自サービスとして位置づけています。

通常のWebアプリケーション脆弱性診断でもAPIのテストは当然含まれるが、APIはウェブアプリケーションからだけ利用されるものではなく、IoT機器などからも呼び出される。この場合、IoTシステムとしてセキュリティテストあるいはAPI単体でのセキュリティテストが行われる。本書で説明されるAPIセキュリティテストは、脆弱性診断というよりはペネトレーションテストである。

APIのペネトレーションテストを業務で経験できるエンジニアは限られていると思うが、例えばバグバウンティプログラムに参加して、公開APIをテストするような機会は、意欲と技量さえあれば誰にでも与えらるし、本書はバグバウンティプログラムに参加するバグハンター向けの注意や心構えについても度々言及している。

本書の構成

ここで、本書の構成について紹介しよう。本書は4つの部、16の章で構成される。ここからも分かるように、本書は非常に広範囲のトピックについて扱っている。

第1部 API セキュリティの原理

「第1部 API セキュリティの原理」では、後述の章立てからもわかるように。Web APIの基礎知識と、0章としてAPIテストのスコープ(範囲)、顧客からテスト許可を取得することの必要性などを説明している。2章 Web APIの解剖学では、RESTful APIに加えてGraphQLについても簡単な説明をしている。JSON/XML/YAML等のデータ形式、APIの認証方式(BASIC認証、APIキー、JWT、HMAC、OAuth2.0…)について説明する。REST APIおよびGraphQLを前提とすることから、Cookieによるセッション管理は本書では取り扱われておらず、そのためかCSRF脆弱性やCookieのSameSite属性に関する説明は割愛されている。

  • 0章 セキュリティテストへの準備
  • 1章 Webアプリケーションの仕組み
  • 2章 Web APIの解剖学
  • 3章 一般的なAPI脆弱性

3章の「一般的なAPI脆弱性」としては以下が紹介されている。概ねOWASP API Security Top 10 2019と重なっているが、3.1 情報漏えいと3.11 ビジネスロジックの欠陥は独自に追加したものである。

  • 3.1 情報漏えい
  • 3.2 オブジェクトレベルの認可不備 (BOLA)
  • 3.3 ユーザ認証の不備
  • 3.4 過剰なデータ露出
  • 3.5 リソース不足とレート制限
  • 3.6 機能レベルの認可不備 (BFLA)
  • 3.7 マスアサインメント
  • 3.8 セキュリティ設定ミス
  • 3.9 インジェクション
  • 3.10 不適切な資産管理
  • 3.11 ビジネスロジックの欠陥

第2部 APIテストラボの構築

  • 4章 API ハッキングラボの構築
  • 5章 脆弱なAPIラボ環境の準備

第2部はAPIテストのための実習環境として、診断ツールと脆弱なAPI環境を準備する。診断ツールとしては、Burp Suite、Postman、Wfuzzが大活躍することになるが、これら以外にOWASP Amass、KiterunnerなどAPIを発見するためのツールも紹介されており興味深い。脆弱なAPI環境としてはcrAPIなど数種を紹介している。また、訳注の形で拙作のBad Todoも紹介頂いていた(APIではなく古典的なWebアプリケーションとして)。

第3部 APIへの攻撃

第3部は、いよいよAPIの実際のテスト(攻撃)を説明する。第3部の章立ては以下の通りである。

  • 6章 APIの検出
  • 7章 エンドポイント分析
  • 8章 認証への攻撃
  • 9章 ファジング
  • 10章 認可への攻撃
  • 11章 マスアサインメント
  • 12章 インジェクション攻撃

まず6章と7章でAPIの検出と分析を行うが、この2章は十分な紙面を使って詳しく説明されている。このあたりが、よくある「ハッカー入門」的な書籍とは異なり、本書が実務的な内容になっていることの表れのように思える。
6章 APIの検出では、OSINT的な手法を活用してAPIを検出する。通常の脆弱性診断ではAPIエンドポイントの情報は開示されるので、このステップは省略されるが、本書でいう「ブラックボックス型テスト」では、APIの検出から始まるのでこのステップが必要になる。

7章のエンドポイント分析は、APIの正常系の仕様を把握するステップであり、ウェブアプリケーション脆弱性診断のサイト巡回(クローリング)に相当する。APIの仕様はドキュメント化されている場合も多いが、そうでない場合、アプリケーション経由のAPI通信をキャプチャすることになる。このステップは本書ではリバースエンジニアリングと表現されている。通信のキャプチャはBurp Suite等の診断プロキシを用いることもできるが、本書では主にPostmanを使用する。Postmanのプロキシ機能(評者は本書を読むまで知らなかった)を用いて、API仕様を明確にする。7章の後半は情報漏洩や過剰なデータ露出等、ファジングを伴わない脆弱性検査を行う。

8章から12章はさまざまな攻撃によるセキュリティテストである。8章の前半はパスワード認証に対する辞書攻撃やパスワードスプレイなど古典的な攻撃だが、Hydraのようなパスワード解析専用ツールではなく、Burp Suite IntruderやWfuzz等のファジング用のツールを使っている。パスワードスプレイの場合、ユーザIDのリストが必要になるが、6章で解説した偵察や7章の過剰なデータ露出脆弱性を利用できるとする。8章の後半は、トークンやJWTに対する攻撃で、ブルートフォースを含む攻撃手法を解説している。

9章で説明されるファジングとは異常なデータをAPIに送信して意図しない結果を引き起こすことである。ファジングにはワイドファジング(広く浅く)とディープファジング(狭く深く)があり、前者にはPostmanのCollection Runnerを用い、後者にはBurp Suite IntruderやWfuzzを用いている。評者はPostmanを使ってはいたが、もっぱらAPIの機能的なテストの目的であり、ファジングに用いる発想がなかったので勉強になった。

10章は認可への攻撃である。認可脆弱性に関しては特に目新しいものではないが、テストの観点とツールの効果的な使い方は興味深い。ここでも、Postmanを効果的に使用している。

11章はマスアサインメントに対する攻撃である。マスアサインメント脆弱性とは、主に更新系のAPIで、「変更できてはいけない項目を変更できる」問題であり、過去のGithubの事例が有名である。例えば、利用者が自身のメールアドレスやパスワードを変更できるのは問題ないが、テーブル上のadmin列をtrueに勝手に変更できたら大問題である。API呼び出しにadmin=trueを追加するだけで意図しないadmin列の更新ができる場合がマスアサインメント脆弱性である。Ruby on Rails 4以降ではStrong Parametersと言う機能によりマスアサインメントに対策している。マスアサインメントのテスト自体は簡単なのだが、問題は「変更できてはいけないパラメータ(先の例ではadmin)」をどう発見するかである。本章では、マスアサインメント可能なパラメータの探索についていくつかの方法を説明している。

12章はインジェクションということで、クロスサイトスクリプティング(XSS)やSQLインジェクションなどが含まれる。有名な脆弱性であるからか、この章の説明は簡潔なものである。12.4.1項で説明されるSQLインジェクション攻撃文字列は、あまり凝ったものではないし、本番環境で試すには危険なものが含まれていて評者はぎょっとした(p304)。だが、直後に訳者による適切なコラム「SQLインジェクションのテストを実施する際の注意と補足(pp305-306)」があり、評者は安心した。もっとも、コラム内の文字列「’||’」や「’+’」もMySQLの場合は危険なので、SQLインジェクション検査は厳重な注意が必要である(参考:とある診断員とSQLインジェクション)。

第4部 実世界におけるAPI攻撃

第4部は、「実世界におけるAPI攻撃」というテーマで、13章はバイパス技術の応用とレート制限テストとして、バリデーションやWAF、APIの利用制限を回避する技術といういかにもハッカー的なテーマ、14章は皆さん大好きGraphQLへの応用、15章では、APIの脆弱性が情報漏洩やバグバウンティでどのように発見されたかを紹介している。

  • 13章 バイパス技術の応用とレート制限テスト
  • 14章 GraphQLへの攻撃
  • 15章 データ侵害とバグバウンティ

本書のすごいところ

本書の眼目は、APIセキュリティテストの方法論に徹底しているところである。それは、APIを検出するところから始まる。種々の検出手法は、APIそのものを探すだけではなく、マスアサインメント可能なパラメータを探す場合にも応用できるもので、この種の解説は非常に貴重なものだ。
また、テストに使うツールの説明も詳しい。筆者は主にBurp SuiteやPostmanなどの汎用的なツールを使い、Burp SuiteのIntruderやPostmanのCollection Runnerによりファジングだけでなく、パスワードに対する辞書攻撃やマスアサインメントのパラメータ探索にも用いる。その他にも多くのツールが紹介されているが、評者には、汎用ツールの使いこなしこそ勉強すべきだと思えた。
さらには、テストに対する心構えや、方針の立て方など、現役のペンテスターならではの知見が学べるところも、本書ならではの価値といえる。

本書で扱ってない内容

本書で扱っていない項目についても紹介しよう。
まず、現在APIの主流であるRESTfulAPIはステートレスだからCookieによるセッション管理は行わないという主張から、Cookie関連の記載は一切ない。これに関連して、以下の項目もない。

  • Cookieに関する説明(例えばSameSite属性を含め、Cookieの言及がない)
  • セッション管理(トークン類の保存場所はlocalStorageかCookieか等)
  • APIのCSRF(Content-Type、CORSとの関連を含むが、そもそもCSRFの言及がない)

また、Web APIの基礎としてJSONなどの説明はあるが、同一オリジンポリシーやCORSの説明はない。
XSSの話題は少し出ているが、Content-Typeの話題はない。例えば、以下のケースは、レスポンスボディの見かけ上はXSSだが、Content-Type: application/jsonなのでJavaScriptは実行されない。この種の話題は、他の本(例えば拙著)で学ぶ必要がある。

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 43

{"message": "<img src=x onerror=alert(1)>"}

その他、触れられてないテーマの例として下記がある。

  • JSONP(RESTfulAPIだから関係ない)
  • CSP(API側だから無関係というのは分かる)
  • X-Content-Type-OptionsやHSTSなどのレスポンスヘッダ(侵入とは直接関係がないからか?)
  • 脆弱性の対策方法

注意点

筆者は根っからのペンテスター気質という感じで、例えば、以下のような記載がある(p301)。

プロバイダのAPIがコンテンツを追加したり、そのWebアプリケーションに変更を加える場合、脆弱性が存在しないか探すべきかもしれません。

これに対しては、下記の訳注がついている。

一般に、許可なくサードパーティAPIをテストすることは許されていないケースが多いため、注意が必要です。

また、以下のような記載もある(p115)。

筆者は、Webアプリケーションの存在を発見すると、すぐにNiktoでスキャンを行います。

おそらく文脈的には「セキュリティテストの過程でWebアプリケーションを発見すると…」ということであろうが、Niktoも脆弱性診断ツールの一つではあるので、許可なくNiktoスキャンを行うことは問題がある。

本書の眼目は訳者の知見にある

本書は、本格的なAPIセキュリティテストの書籍の和訳というだけで価値が高いものであるが、次の点において、価値が倍加していると考える。

  • 原書の出版後に利用サイトやツールのバージョンアップ、サイトの閉鎖等に伴う利用法の変更や代替手段の説明があること
  • 豊富な訳注と訳者コラムにより、原書の説明不足や、日本の読者になじみのない一般知識(例えばAcmeの意味)が補足されていること
  • 訳注での参考情報の記載がとても豊富であり、追加の勉強のために役立つこと

したがって、読者が英語に堪能であったとしても、それでもこの和訳を選択する価値は十分にある。また、参考リンクが豊富であることから、買うなら電子版(PDF)が圧倒的にお勧めである。評者はレビューを紙版で行ったが、電子版も購入した。
翻訳はこなれていて、問題となるような箇所はなかった。

本書の使い方

本書のお勧めの活用法は以下のようなものであろう。

  • 4章「APIハッキングラボの構築」と5章「脆弱なAPIラボ環境の準備」に従い実習環境を準備する
  • 各章を読む
  • 章末に示された実習を行う
  • 本書を読むだけでは分からない箇所を訳注の参考文献に従って勉強する

かなり時間がかかるはずだが、それだけの価値は必ずある。

まとめ

  • 本書はAPIセキュリティテストに関する実践的なガイドである
  • ツールの紹介が多いが、読者はツールを能動的に活用する必要があり、そのための情報が本書にはある
  • 訳注および訳者コラムは価値が非常に高く、それだけで訳書を選択する理由になる
  • 買うなら絶対に電子版(PDF)をお勧めする

2022年12月20日火曜日

Ruby cgi gemのHTTPヘッダインジェクション脆弱性CVE-2021-33621の概要と発見の経緯

この記事はRuby Advent Calendar 2022の第20日の記事です。前日の記事は@ydahさんによる「RuboCopのバージョンを最新に保つ技術」でした。

2022年11月22日に、Ruby cgi gemのHTTPヘッダインジェクション脆弱性CVE-2021-33621が発表がされました。

私はHackerOneを通じてこの脆弱性を報告しました。この記事では、当該脆弱性の概要と発見の経緯などについて報告します。

概要

この脆弱性はRubyでCGIプログラムを記述際に用いられるcgi gem のheaderメソッドに内在するものです。PoCを以下に示します。このスクリプトは、クエリ文字列 num を受け取り、http://example.jp/?num=<指定された数値>にリダイレクトするものです。

#!/usr/bin/ruby
require 'cgi'

cgi = CGI.new
num = cgi.params['num'][0]
print cgi.header({'status' => '302 Found', 'Location' => "http://example.jp/?num=#{num}"})

ここで、numとして下記を指定した場合、

num=3%0d%0aSet-Cookie:+SESSID%3d1234567890

%0d%0aは改行なので、以下のレスポンスヘッダが出力されます。

Status: 302 Found
Location: http://example.jp/?num=3
Set-Cookie: SESSID=1234567890

すなわち、リダイレクトする「ついで」に任意のクッキーをセットすることができます。

クッキのセット以外に、任意URLへのリダイレクトもできます。

num=3%0d%0aLocation:+http://trap.example.com/

この結果のレスポンスヘッダは下記となりますが、

Status: 302 Found
Location: http://example.jp/?num=3
Location: http://trap.example.com/

Apacheの場合複数のLocationヘッダがあった場合、最後のLocationヘッダのみがブラウザに返されるので、結果として、trap.example.comにリダイレクトされます。これにより、偽ログイン画面に遷移してパスワードを盗むなどができます。

また、以下のようにHTTPレスポンスボディを出力することもできます。

num=3%0d%0a%0d%0a<script>alert(1)</script>

レスポンスは下記となります。空行の後にJavaScriptコードが出力されていることが分かります。

Status: 302 Found
Location: http://example.jp/?num=3

<script>alert(1)</script>

ただ、このままだとリダイレクトが優先されるのでレスポンスボディは表示されません。リダイレクトを避けるテクニックとして、ヘッダインジェクションによりStatus 500等を出力する方法があります。このテクニックははるぷさんに教えていただきました。

num=3%0d%0aStatus:+500%0d%0a%0d%0a<script>alert(2)</script>

このケースでの出力は下記となります。

Status: 302 Found
Location: http://example.jp/?num=3
Status: 500

<script>alert(1)</script>

Statusヘッダが2つありますが、前述のようにApacheは最後のStatusヘッダのみをブラウザに返すため、リダイレクトは無効となり、JavaScriptが実行されます。これはXSSと同等の脅威となります。

脆弱性発見の経緯

なぜ今どきRubyのcgi gemを調べたか、それにはちょっとした理由があります。 元々は、2020年1月にRuby on RailsのHTTPヘッダインジェクションに脆弱性を見つけて、HackerOneにて報告したところ、これはRailsの脆弱性ではなくて、ウェブサーバーのPumaの脆弱性だということになり、Puma側で修正された(CVE-2020-5247)ことがきっかけです。

この脆弱性について、適当な場で紹介したいと思っていたところ、2021年6月18日に銀座Rails#34@リンクアンドモチベーションがオンライン開催されることがわかりましたので、CfPに応募して採択されました。

この内容は私のYouTubeチャンネルにて公開しています。動画データを主催者様に提供いただきました。ありがとうございます。

この発表(動画)では、冒頭のHTTPヘッダインジェクション入門をPHP(の古いバージョン)にて行っていますが、元々はRubyのCGIで説明するアイデアがありました。そのサンプルプログラムを作成する過程でRubyのcgi gemに脆弱性があることに気づきました。ゼロデイの脆弱性を説明に使うわけにもいかず、本番ではPHPの旧版の脆弱性を用いた形になりました。

さて、cgi gemに脆弱性があることは疑いのないものでしたが、「RubyでCGIを書いているサイトはどれくらいあるのだろう」という疑いもあり、gem側で修正しなくても、注意喚起してアプリケーション側での改修でもよいのではないかと思いました。とはいえ、当方が勝手に「ゼロデイ脆弱性」を公開することもよくありません。

このためRubyコミッタ柴田さんに相談したところ、チームで検討するのでHackerOne経由で申告してほしい(日本語でOK)旨の連絡をいただきましたので、HackerOneに投稿した内容が冒頭で紹介したものです。こちらは脆弱性と認められ、改修されることになりました。 また、CGI::Cookieにもセキュリティ上好ましくない実装を見つけましたので、追加の報告として提出しました。

内容は、Cookieのname、path属性、domain属性設定時に外部由来の値を使うとインジェクションが可能というものでした。こちらも脆弱性と認められ、同時に修正されています。詳しくはHackerOneの報告を御覧ください。

影響を受けるアプリケーション

当脆弱性の影響を受けるアプリケーションは下記のとおりです。

  • cgi.headerメソッドに外部由来の値をセットしている場合
  • CGI::CookieによりCookieのname、path属性、domain属性設定時に外部由来の値を使っている場合

※ Cookieのpath属性およびdomain属性へのセミコロンを用いたインジェクションは、PHP-7.2以前にも存在していました(バグ#69948)。

影響

当脆弱性の影響は概要で示したとおりですが、まとめると以下のようになります。

  • 任意のレスポンスヘッダの追加・改変
    • (その結果)クッキーの追加、改変
    • (同上)任意URLのリダイレクト
  • レスポンスボディの追加・改変
    • (その結果)任意JavaScriptの実行(XSSと同等の脅威)
  • Cookieのname、path、domainを通じた属性等の改変

対策

cgi gemを最新版(0.3.5, 0.2.2, 0.1.0.2)にアップデートすることで対策されます。

Ruby本体のバージョンアップ

Rubyのバージョン 3.1.3 / 3.0.5 / 2.7.7 にて最新のcgi gemを含む形で対応されています。 現時点では、主要Linuxディストリビューションはアップデートを提供していないようです。

cgi gemのアップデート

前述のように、Linuxディストリビューション経由でのyumやaptによるアップデートでは本問題の対応はできませんが、gemコマンドによるアップデートは可能です。

$ sudo gem update cgi

ただし、Rubyのバージョンが2.7以降である必要があります(参考)。Ruby 2.7未満を使用している場合は、Ruby自体をバージョンアップする(推奨)か、以下のアプリケーション側の対策を実施してください。

アプリケーションでの対応

アプリケーション側で対策するには、以下を実施してください。

  • cgi.headerメソッドに渡すパラメータを検証して改行(\rや\n)が混入しないようにする
  • CGI::Cookieに渡すname属性やdomain属性を検証する

まとめ

Ruby cgi gemのHTTPヘッダインジェクション脆弱性CVE-2021-33621について、脆弱性の内容と経緯について説明しました。今時RubyでCGIプログラムを記述するケースもほとんどないとは思いますが、HTTPヘッダインジェクションという今となってはレアな脆弱性が発見され、きちんと修正されたことをご報告したいと思い本稿を書きました。

脆弱性そのものは拙著「安全なWebアプリケーションの作り方」に記載されている範囲のものですので、バグハンターを目指す皆さんは、「著名ソフトウェアだけどあまり使われていない機能」に着目すると、比較的容易に脆弱性が見つけられ、インターネットの安全にも貢献できるのではないでしょうか。

また、今回の脆弱性対応くださった柴田(@hsbt)さん、@mameさん、@nobuさんに感謝申し上げます。きちんと対応いただき、発見者としてうれしく思います。ありがとうございました。

2022年9月9日金曜日

PHPカンファレンス2022にてSPAセキュリティ超入門の話をします

今年もPHPカンファレンスにてトークさせていただくことになりまして、以下のようなお話をいたします。

日時:9月25日(日) 14:40〜15:40
場所:大田区産業プラザPiO  および YouTube
費用:無料
講演タイトル:SPAセキュリティ超入門
申し込み: connpass
アジェンダ:

SPA(Single Page Application)の普及が一層進んでおり、従来型のMPAを知らないウェブ開発者も生まれつつあるようです。SPA対応のフレームワークでは基本的な脆弱性については対策機能が用意されていますが、それにも関わらず、脆弱性診断等で基本的な脆弱性が指摘されるケースはむしろ増えつつあります。
本セッションでは、LaravelとReactで開発したアプリケーションをモデルとして、SQLインジェクション、クロスサイトスクリプティング、認可制御不備等の脆弱性の実例を紹介しながら、現実的な対策について紹介します。LaravelやReact以外のフレームワーク利用者にも役立つ説明を心がけます。


実は昨年も「SPAセキュリティ入門」というテーマでトークをしておりまして、今年はもう少し易しめということで「超入門」としました。

昨年の講演のスライドおよび動画は以下から参照ください。

昨年は主にSPAにまつわるセッション管理に焦点をあてて、JWTによるセッション管理は是か非か、JWT等トークン類の保存場所はCookieかlocalStorageかという基本的な考え方(しばしば論争になる)のお話をしました。

今年はもう少し現実的なテーマとして、アジェンダにもあるように、LaravelによるサーバーサイドAPI、Reactによるフロントエンドという構成のSPAを題材として、以下のような「よくある」脆弱性がいかにして混入するか、およびその対策について説明します。

  • 認可制御不備
  • SQLインジェクション
  • クロスサイトスクリプティング(XSS)

これら脆弱性は、LaravelおよびReactを普通に使っていれば混入しないはずのものです。しかし脆弱性診断等ではこれら脆弱性はしばしば目にします。普通に作れば混入しないはずの脆弱性がなぜ混入するのか。私はその原因を追い続けていましたが、どうも従来型のアプリケーション(マルチページアプリケーション=MPA)では常識だった知識が、SPAでは伝承されていないのではないかと考えるに至りました。この要因ファクターXについて紹介するとともに、なぜ今基本的な脆弱性が多いのかを説明しようと思います。

なお、前述のようにファクターXは従来の常識ですので、「なぁんだ、そんなことか」となること請け負いですが、「そんなこと」レベルのことが問題になっているように考えています。

それでは、よろしくお願いいたします。




無事終了しましたのでスライドと動画を添付いたします。


フォロワー

ブログ アーカイブ