パート 2

クロスサイトスクリプティング (XSS)

クロスサイトスクリプティング (XSS) とは、攻撃者が対象となる Web サイトのコンテンツ内に、 対象とは異なる Web サイトのスクリプト (HTML や Javascript) を混入可能にしてしまう脆弱性です。 被害者がそのページを表示すると、混入されたコードは被害者のブラウザ内で実行されます。 したがって、攻撃者はブラウザの同一生成元ポリシー ( Mozilla による解説ページ ) を回避し、 当該 Web サイトに関連した被害者の個人情報を盗むことができます。

折り返し型 XSS (Reflected XSS) 攻撃では、 攻撃はリクエスト内 (多くの場合は URL) にあり、 受け取ったサーバが攻撃内容とまったく同じもの、 またはエスケープやサニタイジング (無害化) に失敗したものを レスポンスとして返す際に脆弱性が発生します。 被害者にとって、攻撃者によって作成された悪意のある URL を表示することがその攻撃の引き金となります。 蓄積型 XSS (Stored XSS) 攻撃では、 攻撃はアプリケーション内 (例えばスニペットとして) に保存され、 サーバーが攻撃として保存されたデータをエスケープやサニタイジングせずにブラウザに表示すると、 被害者への攻撃の引き金となります。

ノート

詳細

これがどのように発生するかを理解しましょう: http://www.google.com/search?q=flowers は 以下のような HTML を含むページをレンダリングすると想定します

<p>Your search for 'flowers'
returned the following results:</p>

Google が返すページにクエリーパラメータ q の値をそのまま挿入しています。 もし、 www.google.comq のバリデーションやエスケープをしていない場合、 攻撃者は以下のような悪意あるリンクを作成することができます。

http://www.google.com/search?q=flowers+%3Cscript%3Eevil_script()%3C/script%3E

そして、このリンクを巧妙に被害者にクリックさせます。 被害者がこのリンクをロードすると、 被害者のブラウザには以下のようなページがレンダリングされます。

<p>Your search for 'flowers<script>evil_script()</script>'
returned the following results:</p>

そして、ブラウザは evil_script() を実行します。 ページ自体は www.google.com から来ており、 evil_script()www.google.com の一部として実行されるため、 被害者のブラウザにある、このドメインのステートや Cookie すべてにアクセスできます。

被害者に、悪意があると一目で分かるようなリンクをクリックさせる必要はありません。 攻撃者が www.evil.example.com を所有しており、 悪意のあるリンクを含む <iframe> を作成して [ www.google.com 等のページに埋め込んで (Kitahara 追加) ] いると想定しましょう。 もし被害者が www.evil.example.com を表示すると、攻撃はひそかに実行されます。

XSS にチャレンジ

通常、ページ上で Javascript を実行できる場合、別のユーザが閲覧すると XSS の脆弱性があります。 ハッキングする際に利用する単純な Javascript 関数は、 alert() 関数で、 引数で指定した文字列のポップアップボックスを作成します。

警告メッセージを表示するだけでは危険とは思えないかも知れませんが、 それを混入できるということは、より悪質なスクリプトも混入できるということです。 それは攻撃するために特定の特殊文字を混入する必要はありません。 もし、 alert(1) を混入できた場合は、 eval(String.fromCharCode(...)) を使って任意のスクリプトを混入できるようになります。

課題は Gruyere 内にある XSS の脆弱性を見つけ出すことです。 URL や 保存したデータの両方から脆弱性を探し出す必要があります。

通常、XSS 脆弱性はユーザの信頼できないデータを適切に処理していないアプリケーションに含まれるため、 一般的な攻撃方法は、入力フィールドに任意の文字列を入れ、 それがどのようにレスポンスページの HTML ソースへレンダリングされるかを確認します。 しかし、その前により簡単なもので試して見ましょう。

ファイルアップロードによる XSS

black box google-gruyere.appspot.com ドメインに任意のスクリプトを実行するためのファイルをアップロードできますか ?

ヒント

ヒント

スクリプトを含む HTML ファイルをアップロードできます。

ノート

攻撃方法と解決方法

攻撃方法 は、以下のようなスクリプトを含む .html ファイルのアップロードです:

<script>
alert(document.cookie);
</script>

解決方法 は、コンテンツを別々のドメインに置くことにより、 スクリプトがあなたのドメインにはアクセスしないようにします。 つまり、 example.com/ username 上にユーザのコンテンツを置くのではなく、 username .usercontent.example.comusername .example-usercontent.com 上にコンテンツを置きます (ドメイン名に “usercontent” 等を含めることで、 wwww のような一見無害に見えるユーザ名を登録し、 攻撃者がそれをフィッシング攻撃に利用するのを回避できます)。

折り返し型 XSS (Reflected XSS)

ここで興味深い問題があります。 いくつかのブラウザは折り返し型 XSS への攻撃を防御策が組み込まれています。 NoScript のようなブラウザ拡張も多少の防御策となります。 もし、そのようなブラウザ拡張を利用している場合は、 この攻撃を実行するために違うブラウザを利用するか、 一時的にその拡張を無効にする必要があるかも知れません。

この codelab が書かれた時点で、この防御策が組み込まれているブラウザは IE と Chrome でした。 この問題を解決するためには、 Gruyere のレスポンスは自動的に X-XSS-Protection: 0 という HTTP ヘッダーを含んでおり、 これは IE や Chrome の将来のバージョン (現在ディベロッパーチャンネルで利用可能) で評価されます。 もし Chrome を利用している場合は、 起動時に以下のように --disable-xss-auditor オプションを指定してください。

  • Windows: "C:\Documents and Settings\USERNAME\Local Settings\Application Data\Google\Chrome\Application\chrome.exe" --disable-xss-auditor
  • Mac: /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --disable-xss-auditor
  • GNU/Linux: /opt/google/chrome/google-chrome --disable-xss-auditor

もし Firefox に NoScript アドオン を入れている場合は、 google-gruyere.appspot.com を許可リストに追加してください。 もしそれでも XSS への攻撃ができない場合は違うブラウザで試してください。

ブラウザが保護するのであれば、XSS を心配する必要はないのではと思うかも知れません。 実は、ブラウザの保護機能はあなたが利用するアプリケーションを完璧に把握しているわけではありませんし、 天才ハッカーにはその保護の回避方法があるかもしれません。 アプリケーションの最初の場所は XSS 脆弱性の対策が十分ではありません。

black box 折り返し型 XSS への攻撃を見つけてください。 見つけたいものは URL のクリック時に実行されてしまうスクリプトです。

ヒント

ヒント 1

この URL はどうでしょうか ?

http://google-gruyere.appspot.com/123/invalid

ヒント

ヒント 2

URL 内で最も危険な文字は < と > です。 もし、アプリケーションのページ内にこの文字を直接混入できる場合は、 おそらくスクリプトも混入できるでしょう。 以下も試してください:

http://google-gruyere.appspot.com/123/%3e%3c
http://google-gruyere.appspot.com/123/%253e%253c
http://google-gruyere.appspot.com/123/%c0%be%c0%bc
http://google-gruyere.appspot.com/123/%26gt;%26lt;
http://google-gruyere.appspot.com/123/%26amp;gt;%26amp;lt;
http://google-gruyere.appspot.com/123/\074\x3c\u003c\x3C\u003C\X3C\U003C
http://google-gruyere.appspot.com/123/+ADw-+AD4-

上記では、様々な方法を利用し > と < をレンダリングするための URL を作成しています: そのまま ( % による URL エンコーディング)、 ダブル % エンコーディング、 不正 UTF-8 エンコーディング、 HTML の & エンコーディング、 ダブル & エンコーディング、 いくつかの C 言語形式の符号化など。 ソースを確認し、それが動作しているかを確認してください。 (注意: ブラウザは自動的に文字を % エンコードするため、 URL 内の >< リテラルは %3e%3c となります。 もし >< のようなリテラルを利用したい場合は、 その文字を URL に埋め込むために curl のようなツールを利用する必要があります)

ノート

攻撃方法と解決方法

攻撃方法 は、以下のような URL を作成し、それを被害者にクリックさせます:

http://google-gruyere.appspot.com/123/<script>alert(1)</script>

解決方法 は、ユーザが入力したデータをエラーメッセージ内に表示する際に必ずエスケープします。 エラーメッセージは error.jtl を利用して表示されていますが、 それはテンプレート内でエスケープ処理されていません。 テンプレートの {{_message}} という箇所でメッセージをレンダリングしており、 ユーザが入力したデータをエスケープするための修飾子が抜けています。 ユーザが入力したデータをエスケープするために :text 修飾子を追加してください:

<div class="message">{{_message:text}}</div>

この欠陥を最も軽減する方法は、 すべての出力をデフォルトでエスケープ処理するように設計し、 そのまま HTML として出力したい時だけ明示的にタグ付けするようにします。 これが多くのテンプレートシステムに実装されている自動エスケープ機能です。

蓄積型 XSS (Stored XSS)

black box 次に蓄積型 XSS を探しましょう。 他の Gruyere ユーザが利用する場所にそのスクリプトを仕掛けるのが目的です。

最も分かりやすい場所は、ユーザが Gruyere に投稿したスニペットのデータを返している場所です (ファイルアップロードについては言及済みのため対象外とします)。

ヒント

ヒント 1

以下のスニペットを投稿し、何が起こるか調べてください:

<script>alert(1)</script>

これにはドキュメント内にスクリプトを埋め込む方法がたくさんあります。

ヒント

ヒント 2

ハッカーは適切な HTML 構文だけにとどまりません。 いくつか無効な HTML で試して、何が起こるか調べてください。 これを動作させるために何通りかの方法で試して見る必要があるかもしれません。 これらの動作にはいくつかの方法があります。

ノート

攻撃方法と解決方法

攻撃方法 は、以下のようなスニペット (他にも多くの方法があります) を投稿します:

(1) <a onmouseover="alert(1)" href="#">read this!</a>

(2) <p <script>alert(1)</script>hello

(3) </td <script>alert(1)</script>hello

いくつか HTML のサニタイジング (無害化) に失敗していることに気がついたでしょう。 スニペット (1) は、 onmouseoverjsanitize.py 内の許可されない属性から うっかり (どうでもいいですが Google さん typo です:: inadvertantly -> inadvertently) 漏れてしまったために動作してしまいました。 スニペット (2) と (3) は、 ブラウザが HTML シンタックスの誤った開始タグと終了タグを許す傾向があるために動作してしまいました。

解決方法 は、スニペットを調べ、サニタイジングします。 スニペットは jsanitize.py_SanitizeTag を使ってサニタイジングされています。 “onmouseover” を disallowed_attributes に追加し、スニペット (1) を防ぎましょう。

おっと! これだけでは完全な問題の解決にはなりません。 コード自体は解決したように見えますが、この解決策の回避策を見つけることができますか ?

ヒント

ヒント

_SanitizeTag のコードで、どの HTML 属性を許可、もしくは許可しないかを決定している箇所を注意深く読んでください。

ノート

攻撃方法と解決方法

このコードへの解決策では、許可されない属性を調べる際に、HTML とは違い大文字と小文字が区別されてしまうため不十分です。 よって、以下は動作してしまいます:

(1') <a ONMOUSEOVER="alert(1)" href="#">read this!</a>

HTML を正しくサニタイジングすることは非常に難しい問題です。 _SanitizeTag 関数には設計上の重大な欠点がいくつかあります:

  • それは HTML の整形 (Well-Formed) を確認していません。 上記で確認したように不正に整形した HTML は、 変更を加えられないままサニタイジングを通過してしまいます。 ブラウザは一般的に非常に甘く解釈するため、 そのフォーマットを厳格にコントロールしない限り、 ブラウザでの HTML の解釈を予想するのは非常に困難です。
  • 属性のブラックリストを利用していますが、これは技術的に誤っています。 上記で確認したように、単純に属性を大文字にするだけでブラックリストを通過してしまいました。 リストから他の属性が漏れているかも知れず、大変危険です。 それよりも、許可する値をホワイトリストに追加していく方が良いでしょう。
  • サニタイザーは属性値以外をサニタイジングしていません。 これにより hrefsrcstyle 等の URI 属性値へ javascript が混入できるため危険です。

HTML をサニタイジングするための正しいアプローチは:

  • 入力データを DOM の中間構造として解析し、 body を再整形して出力する。
  • 許可するタグや属性のみを含む厳しいホワイトリストを作成する。
  • URL と CSS 属性を許可する場合は、より厳しいサニタイジングを適用する。

可能な限り、広く知られており、実績がある HTML サニタイザーを利用しましょう。

HTML 属性による蓄積型 XSS

black box HTML 属性に対して値を混入することによる XSS も可能です。 プロフィールでカラーを設定する際にスクリプトを混入します。

ヒント

ヒント 1

カラーは style='color:color' とレンダリングされます。 カラー名にシングルクォーテーションを含めてみてください。

ヒント

ヒント 2

実行可能なスクリプトを HTML 属性に挿入することもできます。

ノート

攻撃方法と解決方法

攻撃方法 は、カラーの設定を以下のようにします:

red' onload='alert(1)' onmouseover='alert(2)

スニペット上にマウスを動かすと攻撃が発動します。 最初のクォートにより style 属性が終了し、 次のクォートで onload 属性が開始されることにより、この攻撃は動作します。

しかし、この攻撃は動作しない場合があります。 カラーをレンダリングする home.jtl を見てください。 それには style='{{color:text}}' と書いてあり、 :text 部分でテキストをエスケープするよう指定しています。 ではなぜエスケープされていないのでしょうか ? それは jtl.py 内で、``cgi.escape(str(value))`` を呼び出しており、 それは第二引数に値 (value) が HTML 属性に使われることを示すオプションを指定できます。 よって、これを cgi.escape(str(value),True) に置き換えましょう。 しかしそれだけでは解決になりません。 cgi.escape は、HTML 属性がダブルクォート内に書かれることを想定していますが、 Gruyere ではシングルクォートを使っているという問題があります。 (これは、利用するライブラリーのドキュメントを常に慎重に読み、 望み通りに動作するかを徹底的にテストしなければならないことを示しています)

この攻撃が onloadonmouseover の両方を利用している点に注目してください。 なぜならば onload イベントは bodyframeset 要素のみでサポートするよう W3C に規定されていますが、 いくつかのブラウザではそれ以外の要素でも動作をサポートしています。 これにより、被害者がそのようなブラウザを使っている場合、この攻撃は常に成功します。 そうでない場合は、ユーザがマウスを動かした際に成功します。 攻撃者が同時に複数の攻撃方法を利用することは、めずらくありません。

解決方法 は、シングルクォーテーションもダブルクォーテーションもエスケープする、 適切なテキストエスケープを利用します。 以下の関数を jtl.py に追加し、 text のエスケープを cgi.escape からこの関数を呼び出すように置き換えてください。

def _EscapeTextToHtml(var):
  """Escape HTML metacharacters.
  This function escapes characters that are dangerous to insert into
  HTML. It prevents XSS via quotes or script injected in attribute values.
  It is safer than cgi.escape, which escapes only <, >, & by default.
  cgi.escape can be told to escape double quotes, but it will never
  escape single quotes.
  """

  meta_chars = {
      '"': '&quot;',
      '\'': '&#39;',  # Not &apos;
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      }
  escaped_var = ""
  for i in var:
    if i in meta_chars:
      escaped_var = escaped_var + meta_chars[i]
    else:
      escaped_var = escaped_var + i
  return escaped_var

おっと! これだけでは完全な問題の解決にはなりません。 適所にこのフィックスを適用しても、まだカラー値は攻撃に対して弱いままです。

ヒント

ヒント 1

いくつかのブラウザはスタイルシートにスクリプトを含めることができます。

ヒント

ヒント 2

この攻撃が最も簡単なブラウザは、CSS の動的プロパティをサポートしている Internet Explorer です。

ノート

その他の攻撃方法と解決方法

とくに Internet Explorer の動的 CSS プロパティ (CSS Expression とも呼ばれている) はこの攻撃を簡単にします。

攻撃方法 は、カラーの設定を以下のようにしてください:

expression(alert(1))

他のブラウザは CSS Expression をサポートしていませんが、 他にも Mozilla の -moz-binding のような危険な CSS プロパティもあります。

解決方法 は、カラーをカラーとしてサニタイジングします。 最善の解決策は、jtl からの (typo: “form to jtl” -> “from to jtl”) 出力用の新しいサニタイジングを追加することです。 カラーとして安全に利用できることを確実にするために {{foo:color}} と書くようにします。 この関数はサニタイジングに利用できます。

ユーザに設定を許可する値はカラーだけとは限りません。 ユーザが設定したフォントやサイズ、URL も同様に無害化しなければなりません。 入力値のバリデーションは、ユーザが無効な値を入力しても、その時点で拒否するのに役立ちます。 しかし、入力時のバリデーションだけを実施するのは間違いです: もしバリデーションコード内にエラーを見つけた場合や新しいブラウザに新しい攻撃方法が見つかった場合、 前に挿入された値をさかのぼって、全て無害化する必要があります。 そうではなく、最初に実施した出力バリデーションを追加することができます。

AJAX による蓄積型 XSS

black box Gruyere に AJAXコードのバグを利用する XSS 攻撃を見つけてください。 ページの “refresh” リンクをクリックすると、それが攻撃の引き金となります。

ヒント

ヒント 1

http://google-gruyere.appspot.com/123/feed.jtlcurl で接続し、結果を確認してください。 (もしくはブラウザで閲覧しソースを表示してください) レスポンス内に各ユーザの最初のスニペットが含まれているのを確認できます。 ドキュメントにスニペットを挿入された際に、このレスポンスの全体がクライアント側で評価されます。 期待と異なる解析が行われるスニペットを追加できますか ?

ヒント

ヒント 2

スニペット内にいくつかクォーテーション (”) を含めてみてください。

ノート

攻撃方法と解決方法

攻撃方法 は、以下をスニペットに追加してください:

all <span style=display:none>"
+ (alert(1),"")
+ "</span>your base

JSON は以下のようでなければなりません:

_feed(({..., "Mallory": "snippet", ...}))

しかしそうではなく、以下のようになります:

_feed({..., "Mallory": "all <span style=display:none>"
+ (alert(1),"")
+ "</span>your base", ...})

"all <span style=display:none>""(alert(1),"")""</span>your base" は別々の意味があります。 この混入されたコードは、 (<span style=display:none> により) 最初にレンダリングされたページでも (空文字のみを挿入するので) リフレッシュ後でも目には見えないことに注意してください。 画面上に表示されるのは All Your Base です。 この攻撃を可能にするバグはサーバとクライアントの両方にあります。

解決方法 は、最初に、サーバ側で JSON レスポンスをレンダリングするさいにエスケープに失敗しています。 テンプレートには {{snippet.0:html}} と書いてありますが、それだけでは十分ではありません。 このテキストは DOM ノードの innerHTML に挿入されるので、HTML はサニタイジングしなければなりません。 しかし、そのテキストは Javascript に挿入されるので、 シングルクォーテーションやダブルクォーテーションはエスケープしなければなりません。 それは JTL に {{...:js}} を追加するだけでは十分ではありません; {{...:html:js}} のようなものが必要でしょう。

シングルクォーテーションとダブルクォーテーションを、それぞれ \x27\x22 を使ってエスケープします。 &#27;&quot; に置き換えるのは、 それが Javascript の文字として認識されず、HTML 属性の引用が崩れるため間違いです。

次に、ブラウザ側では、Gruyere は Javascript の eval を利用して JSON をコンバートしています。 一般的に eval は大変危険であり、あまり使うべきではありません。 利用する場合はとても慎重に利用する必要があり、この例がそれを物語っています。 安全でない文字列を完全に取り除くことができる JSON パーサーを利用する必要があります。 JSON パーサーは json.org で入手可能です。

AJAX による折り返し型 XSS

black box Gruyere の AJAX 機能内に URL をクリックした際に、スクリプトが実行されてしまうものを見つけてください

ヒント

ヒント 1

以下で Gruyere がユーザスニペットをリフレッシュする時

http://google-gruyere.appspot.com/123/feed.jtl?uid=value

スクリプトの結果は以下のようになります。

_feed((["user", "snippet1", ... ]))

ヒント

ヒント 2

混入方法は前述の折り返し型 XSS と非常に似ていますが、異なる脆弱性を利用します。

ノート

攻撃方法と解決方法

攻撃方法 は、以下のような URL を作成し、被害者にクリックさせます:

http://google-gruyere.appspot.com/123/feed.jtl?uid=<script>alert(1)</script>
http://google-gruyere.appspot.com/123/feed.jtl?uid=%3Cscript%3Ealert(1)%3C/script%3E

これは以下のようにレンダリングされ、

_feed((["<script>alert(1)</script>"]))

以外にもスクリプトが実行されてしまいます。 ブラウザが許可する HTML ファイルはとても寛容であり、 Gruyere が jtl ファイルのすべてのコンテンツタイプを text/html として返しているのが問題です。

解決方法 は、JSON コンテンツが決して HTML として解釈されないようにします。 Javascript で <> リテラルは文字列として許可されていますが、 ブラウザが誤解しないようにそのリテラルが現れないようにしてください。 このように、Javascript が \x3c\x3e とエスケープするように {{...:js}} を置き換える必要があります。 これにより常に Javascript の文字列 ‘<>’ は安全な ‘x3cx3e’ に置き換えられます。 (上述したように HTML のエスケープを利用してはいけません) (この辺 typo ?!)

レスポンスには常にコンテンツタイプをセットする必要があり、 この場合は JSON の結果は application/javascript として返します。 ブラウザが常にコンテンツタイプを考慮するのではないため、これだけでは問題を解決できません: 時々ブラウザは、正しいコンテンツタイプを提供していないサーバーから結果を “fix” するために “sniffing” (拡張子から逆引きなど ここが詳しい ) します。

しかしちょっとまってください。もっと重要なことがあります! Gruyere はコンテンツのエンコードもセットされていません。 そして、いくつかのブラウザはドキュメントのエンコードが何かの推測を試み、 攻撃者はコンテンツタイプを定めたドキュメントにコンテンツを混入できるかもしれません。 例えば、攻撃者がブラウザをだましてドキュメントを UTF-7 として認識させ、 <> をエンコードした +ADw-+AD4- から +ADw-script+AD4- のようなスクリプトタグを埋め込むことができます。 このように、レスポンスにはコンテンツタイプとエンコードの両方を常にセットしてください。例えば、 HTML では:

Content-Type: text/html; charset=utf-8

XSS についてさらに詳しく

上記で解説した XSS 攻撃に加え、Gruyere へ XSS 攻撃をする方法はたくさんあります。 そのすべてを調査してください。

XSS は難題です。 一方では、たいていの XSS 脆弱性の解決は簡単であり、 ユーザーが入力したコンテキストを表示する際に、正確にサニタイジング関数を適用します。 他方では、歴史が物語るように、これを正確に実施するのはとても難しいです。

米 CERT 社 は、複数の会社が被害を受けた、多数の XSS 脆弱性を報告しています。 XSS 脆弱性をなんとかしてくれる魔法の防御策はありませんが、 この種のバグが製品に発生するのを防ぐためにいくつかのステップがあります:

  1. 最初に 問題 を確実に理解してください。
  2. 可能な場合は、ソースコードでエスケープ関数を呼び出すのではなく、テンプレート機能でサニタイジングしてください。 これにより、エスケープ処理が一か所 (テンプレートファイルだけ) で完結し、 適切なデータの認証やエスケープ自体などテンプレートシステムのセキュリティ工学設計による恩恵を受けることができます。 同様に、テンプレートシステムの他のセキュリティ機能についても精通するようにしてください。
  3. 常に XSS に関するテストを実施してください。
  4. テンプレートライブラリーを自作しないようにしましょう :)