はじめに
XSSへの対策を考えたとき、認証トークン(JWTを想定)をどこに保存すべきかの検討メモです。
なおXSS攻撃の場面は以下を想定しています。
攻撃者が仕掛けた任意のスクリプトが、他の第3者のブラウザで実行されその人の認証情報が奪われるケース。
なお私は元々、こう思ってました
- Cookie??ダサくね?よくわからんけど危なくない?
- Web Storageって最近の仕様なんでしょ?同一オリジンポリシーあるし大丈夫でしょ
ですが、そうでもないことが分かり、自戒を込めてメモを残す次第です。
結論
先に結論を示します。
以下の順でXSS攻撃に対してセキュアと結論づけました。
ただし、あくまでXSS攻撃に対するセキュア度を述べただけで、必ず1.を採用すべきという訳ではないと思っています。
- HttpOnly, Secure属性をつけたcookieに保持する(場合によってdomain, path属性もつける)
- in-memoryで保持する
- Web Storageに保持する
HttpOnly, Secure属性をつけ、同一オリジンポリシーを適用したcookieに保持する
以下の理由から、仮にXSSを仕掛けられていても認証トークンが盗まれるリスクがほとんどありません。
- HttpOnly属性でJavaScriptからアクセスができなくなるためXSSが存在しても読み取られない。
- サーバー側でセットされたCookieはそのサーバーにしか送信できない。(異なるサーバーに送信したい場合はdomain属性を使う)
- Secure属性で通信路上で盗まれるリスクも低い
一方で、XSS以外の観点では以下の課題や制約があります。
- 認証サーバー側でSet-Cookieしてもらう必要がある(が、外部認証サービスを使っていると必ずしもそのような仕様とはなっていない)
- Authorizationヘッダの仕様 (RFC6750) に従えなくなる
この方法は確かにセキュアですが、認証APIの実装やCookieの取り扱いの正しい実装などコストの高い方法となるでしょう
Web Storageに保持する
window.localStorage
, windos.sessionStorage
から情報を抜き出して、別のサーバーに送信するJavaScriptコードを書くのは比較的簡単です。
おそらく数行で書けるでしょう。
様々なサイトをクロールして、XSSできそうなフォームを見つけ、数行のコードをSubmitしてみるというのは簡単に自動化できそうなタスクです。
もし、Web Storageに認証トークンを保持していればこのような無差別的な攻撃の対象になるリスクがあります。
in-memoryで保持する
Web Storageには保持せずメモリ展開(JavaScript上の変数など)のみに格納する方法です。
原理的には、XSSによってメモリ上の認証トークンを盗まれる可能性はありますが、
WebStorageと違い、攻撃者からみるとどこに認証トークンが存在するかは不明であり困難性が大幅に向上しているといえるでしょう。
(もしかすると、変数スコープの関係で読み取ることが不可能な可能性もあります)
ただし、in-memoryに保持された認証トークンは、ページ再読み込みの際には消えてしまうでしょう。
これがユーザービリティにどの程度の影響を与えるかは判断が必要です。
ちなみに、Auth0ではこの方法が推奨されています。
参考: https://auth0.com/docs/security/store-tokens#regular-web-apps
終わりに
現実的に、1.の方法を取れない場合は多くあると思いますし、そもそもサイト自体にXSS対策を十分にするから大丈夫だ!という判断もあると思います。
2,3はどちらも極端な話、ログインページでgetByElement, innerHTMLなどの利用をXSSで許してしまえば、
攻撃者がログインフローを任意の処理でハックして認証トークンを盗むことが原理的に可能です。
そうすると、認証トークンが盗まれた場合を想定した対策を別途行うことで、3の方法を取るという選択肢もあると思います。