XSSになりたい

Webセキュリティとカメラと写真と

結局XSSって何なの?

タイトルのような疑問にうまーく答えてくれる資料がないよなぁって思って書いてみるかーと思い立ったはいいものの徳丸さんのいい感じのスライドがすでにあった。
まぁ、そんなこともある。

XSSとは

XSS(Cross Site Scripting)は、あるWebページにアクセスした標的のブラウザ上で、攻撃者が任意のコードを実行し得るバグ、あるいはそれを用いた攻撃手法のことで、大きく分けて反射型XSS(Reflected XSS)・持続型/蓄積型XSS(Persistent/Stored XSS)・DOM Based XSSの3種類がある。

例えばPHPで記述された以下のようなWebページはXSSが可能であるといえる。

<?php
  header("Content-Type: text/html; charset=UTF-8");
  $input = filter_input(INPUT_GET, "q");
?>
<!DOCTYPE html>

<html>
  <head>
    <title>XSSサンプル</title>
  </head>
  
  <body>
    <form>
      <input type="text" name="q" value="<?= $input ?>">
      <input type="submit" value="search">
    </form>
<?php
  if($input):
?>
    <span><strong><?= $input ?>の検索結果</strong></span>
<?php
  endif;
?>
  </body>
</html>

このスクリプトが「http://localhost/xss.sample.php」で動いていると仮定して、「http://localhost/xss.sample.php?q=<script>alert('XSS')</script>」といったURLにアクセスすることで、上記PHPスクリプトが出力するHTMLは以下のようになり、結果としてscriptタグ中のalert('XSS')が開発者・運営者の意図しない形でページ上で実行されることになる。

<!DOCTYPE html>

<html>
  <head>
    <title>XSSサンプル</title>
  </head>

  <body>
    <form>
      <input type="text" name="q" value="<script>alert('XSS')</script>">
      <input type="submit" value="search">
    </form>
    <span><strong><script>alert('XSS')</script>の検索結果</strong></span>
  </body>
</html>

最もシンプルなXSSのサンプルを示したが、これによって何ができるのか、何が問題になるのかについては後述。


XSSの種類

大きく分けて3種類あるXSS、それぞれを簡単に説明する。

反射型XSS

HTTPリクエスト中に含まれる攻撃コードがそのままWebページ上で動作するXSS
典型的には検索フォームなど、GETリクエストのパラメータ中に含まれるscriptタグがWebページ上で動作するものなどが挙げられる。(先に紹介したサンプルもこの反射型XSSに分類される)
特定のクエリに対してのみ動作するXSSなので、攻撃者は何らかの手段を用いて標的を特定のURLに誘導しなければならない。
"特定のURL"というのは、例えば先のサンプルの「http://localhost/xss.sample.php?q=<script>alert('XSS')</script>」であったり、アクセスすることで脆弱なページにPOSTリクエストを自動的に送信するページのURLであったりする。
特定のURLへの誘導が必要という観点において、反射型XSSでは持続型XSSに比して攻撃成功までのハードルが低いといえる。

持続型/蓄積型XSS

HTTPリクエスト中に攻撃コードが含まれるか否かに関わらず、あるWebページ上で持続的に動作するXSS
例として掲示板などの投稿中に含まれるscriptタグがそのまま動作する場合などを考える。
その掲示板上では攻撃者が投稿したコードが持続的に動作しているので、攻撃者は標的がそのページにアクセスするのをじっと待てばいいだけとなり、反射型XSSに比して攻撃のハードルがぐっと下がることになる。
ただしこれは"標的"がそのページに頻繁に、あるいはしばしばアクセスすることが前提となり、標的がそのページに普段アクセスしない場合などは反射型XSSと同様に何らかの手段を用いて標的がそのページにアクセスするよう誘導する必要がある。

DOM Based XSS

反射型・持続型XSSがサーバサイドコードのバグに起因するのに対し、DOM Based XSSはクライアントサイドスクリプトのバグに起因するXSS
例えばJavaScript中に以下のような処理がある場合は、DOM Based XSSが可能であるといえる。(ただしここまで典型的なものはまず存在しないと思う)

document.write(location.hash.substr(1));

上記処理が「http://localhost/xss.sample.html」上で実行されるとき、「http://localhost/xss.sample.html#<img src=x onerror=alert('XSS')>」といったURLにアクセスすることで、xss.sample.html上で外部から指定された任意のコードが実行されることになる。

DOM Based XSSは他にも様々なバリエーションが存在し得るが、ここでは省略。


XSSでできること

ここまで紹介してきたサンプルでは、Webページ上でダイアログボックスを表示するだけで、実害があることを示せていない。
実際にXSSでできるのは、JavaScript(あるいはVBScriptなど)でできる全てのことと言い換えて差し支えない(と思う)。
例えば以下のような例がある。

  • セッションIDの窃取、およびそれを用いたセッションハイジャック
  • フォームへの入力内容の窃取(ユーザの住所などが含まれるかもしれません)
  • ユーザを外部の悪質なページにリダイレクトさせる
  • あるWebページを全く別物のWebページに見せる
  • 標的のユーザに特定の操作をさせる(物品の購入とか)
  • あるユーザにしか見えていない情報を窃取する

逆説的に、JavaScriptでできないことはXSSでもできない。
いずれにせよ、XSSというのは、あるWebページ上で、本来そのページに存在しないJavaScriptコードを実行させる攻撃手法。


根本的対策

XSS対策として「メタキャラクタをエスケープする」という文言がよく用いられるが、この表現は非常に厄介で、"メタキャラクタ"と"エスケープ"という用語がそれぞれ曖昧なまま用いられることが非常に多い。
例えば、先のサンプルにおいては、ユーザ入力中に出現するHTMLにおけるメタキャラクタ「"'<>&」の5つをそれぞれ文字実体参照・数値文字参照に置き換えることでXSSを防ぐことができる。
しかしながら、例えば以下のようなページを想定すると、HTMLにおけるメタキャラクタを文字実体参照・数値文字参照に置き換えるというのがどんなときにも当てはまる対策ではないことがわかる。(以下の例では「javascript:alert('XSS')」などを入力した場合に任意のコードが実行できる) 特定のコンテキストにおいては「"'<>&」を使わずとも任意のJavaScriptコードは実行し得るからだ。

<?php
  function h($str) {
    # htmlspecialchars関数でHTMLにおけるメタキャラクタを実体参照・数値文字参照に変換できる
    return htmlspecialchars($str, ENT_QUOTES, "UTF-8");
  }
  
  header("Content-Type: text/html; charset=UTF-8");
  $input = filter_input(INPUT_GET, "q");
?>
<!DOCTYPE html>

<html>
  <head>
    <title>XSSサンプル</title>
  </head>
  
  <body>
    <form>
      <input type="text" name="q" value="<?= h($input) ?>">
      <input type="submit" value="search">
    </form>
<?php
  if($input):
?>
    <a href="<?= h($input) ?>"><?= h($input) ?></a>
<?php
  endif;
?>
  </body>
</html>

それを踏まえた根本的対策として…って書こうと思ったけど長くなっちゃうのでこれ読んで!
安全なウェブサイトの作り方(24ページ)

簡単に言うと、他の安全な文字/文字列に置き換えなければならない文字/文字列や、取り除かなければならない文字/文字列がケースバイケースで異なるため、一概に"こうすれば安全"という対策がない。
強いて言えば「メタキャラクタをエスケープする」なのだが、それでは説明になっていないのであまり意味がない。
また、Webアプリケーションの機能要件との噛み合いもある。一部のHTMLタグがユーザ入力として許可されるようなアプリケーションであればXSSへの対策も異なってくる。
基本的なアプローチとしては、HTML中の出力は全てHTMLエスケープし、それ以外のコンテキストでの出力は行わない、ということに尽きる。加えて先の例のようにURLの出力を行う場合はhttpスキーム、httpsスキームのみ許可するといった個別の条件に合わせた追加の対策が必要になる。


影響緩和策

根本的な対策を間違いなく実施することが大事だが、対策漏れがあったときの影響緩和策として、すぐ思いつくのはこの辺り。
他にもあるかも。

  • Cookieのhttponly属性
  • XSS Filter/XSS Auditor
  • CSP(Content Security Policy)

Cookieのhttponly属性

XSSのデモや解説などでもっともよく見られるのが、セッションIDを窃取し、盗み出したセッションIDを用いてセッションハイジャックをするというもの。
Cookieに設定されたPHPSESSIDなどが窃取の対称となるが、Cookieにhttponly属性を付与することでJavaScriptからそのCookieの値にアクセスすることができなくなる。
これによってXSSによるCookieの窃取を防ぐことができる。

XSS Filter/XSS Auditor

ブラウザによってはXSSを検知し、防止する機能が備わっている。
IEXSS Filter、ChromeSafariXSS Auditorがそれにあたる。
それらの機能は基本的にデフォルトで有効化されているが、X-XSS-Protectionヘッダで制御することができる。
特に問題がなければ3つ目のブロックモード指定が無難。

// XSS防止機能を無効化
X-XSS-Protection: 0

// XSS防止機能を有効化
X-XSS-Protection: 1

// XSS防止機能をブロックモードで有効化
X-XSS-Protection: 1; mode=block

これらXSS防止機能は、基本的にリクエスト中に出現するXSSの攻撃コードらしい文字列がエスケープされずにページ中に出力された場合にのみ動作するため、持続型XSSやDOM Based XSSに対しては効果がないことに留意する必要がある。

CSP(Content Security Policy)

多くのモダンブラウザではCSPを用いてXSSの影響を緩和することができる。
CSP (Content Security Policy) - Security | MDN


参考

安全なウェブサイトの作り方:IPA 独立行政法人 情報処理推進機構
体系的に学ぶ 安全なWebアプリケーションの作り方
CSP (Content Security Policy) - Security | MDN