Cookieとlocal storageの話でよく参照されてる記事にすごく疑問がある
(おことわり:セキュリティ専門家ではない人の私見です)
この記事。
以下の質問への回答からリンクされていたので読んだんだけど、内容がかなり疑わしいと思う……。
HttpOnlyフラグは、XSSの対策としてほとんど意味がありません。
つまり、熟練した攻撃者に対しては、HttpOnlyは攻撃を遅らせることすらできないのです。有効に働かないあまり攻撃者に気付かれもしないWebアプリケーションファイアウォールのようなものです。
この書き方だと、まるで「HttpOnlyフラグを付けてあるCookieに保存されたセッション情報(セッションIDやアクセストークン)をXSSで盗み取ることができる」かのように見えるけど、そうではない。
参考として挙げられているWhy HttpOnly Won’t Protect Youも読んだけど、HttpOnlyのCookieに保存した値をXSSで盗み取ることができるとは全く書いていない。
「HttpOnlyフラグを付けてCookie内のセッション情報だけを守っても、XSSがあったら結局はアプリを自動操作されてしまうから、XSSがあったら安全ではないよ」という、当たり前の話が書いてあるだけだった。
「Why HttpOnly Won’t Protect You」は2007年の記事で、その趣旨は「HttpOnlyを設定したからといってXSSがある限り安全ではないよ!」というものだ。これは推測だが、2007年当時は現在と異なり「XSSがある限り何も安全ではない」という認識が浸透しておらず、HttpOnlyさえ設定していればXSS対策を怠ってもいいと考えている人がいたのだろう。コメント欄を見ても、そういう文脈に見える。
でも「Web Storage: ~」の記事はそうではない。この記事はCookieとweb storageを比べているのだから、「XSSがある限り何も安全ではない」という両方に共通の話を、さもHttpOnlyが機能していないかのように書くのは、はっきり言って詭弁だろう。
私が思うに、現在の有能な攻撃者は カスタムCSRFペイロード を用いてXSSをエクスプロイトするか、 BeEFフック を利用するでしょう。それに比べ、セッショントークンを盗む攻撃では時間差が発生し、環境の違いも生じるため、実用的でなかったりエラーが生じがちだたったりします。
分かりにくいが、「セッショントークンを盗んで使う攻撃法は上手くいかないことが多いから、攻撃者はXSS脆弱性があったら他の攻撃をするよ」→「だからセッショントークンそのものを守ることに意味はないよ」という話をしたいのだろうか?
でも、コード1行で簡単に盗める状態だったら盗むんじゃないかな……? 攻撃者じゃないから知らないけど……。
最終的に言えることとして、Secureフラグと同様に、HttpOnlyフラグも「cookieをWeb Storageと同レベルのセキュリティに引き上げる」だけに必要とされているものなのです。
Secureフラグが「cookieをWeb Storageと同レベルのセキュリティに引き上げる」ものだという意見には同意するが、HttpOnlyフラグはそうではない。だってWeb StorageにはHttpOnlyフラグ相当の機能はまったくない(というか、そもそも存在し得ない)もの。なんの話をしているのかさっぱりわからない。
結局、
不適切な設定のCookie < Web Storage < 適切な設定のCookie
だと思うんですけど……
ここでいう不適切な設定のCookieとは……
- Secure属性を忘れている場合
- ユーザーが中間者攻撃されている場合かつユーザーが
http://
のURLにアクセスしてしまった場合:攻撃者にCookieを送信してしまう
- ユーザーが中間者攻撃されている場合かつユーザーが
- 不適切なDomain指定で意図しないドメインが対象になっている場合
- その意図しないドメインが攻撃者の管理下である場合:攻撃者にCookieを送信してしまう
- (元記事には指摘されてないけど)
- Path属性を使ってオリジンを分けようとしている場合
- 同一ドメイン内の別のPathが攻撃者の管理下である場合:攻撃者にCookieを送信してしまう
- 昔ながらの
https://example.com/~username/
形式でやってるレンタルサーバーとか……? - (Path属性なんか使ってるサイト現代には存在しないと思う……)
- HttpOnly属性を忘れている場合
- XSS攻撃が成立する場合:攻撃者がCookie内容にアクセス可能
このうち「1~3が成立せず4だけ成立している場合」が「Web Storageと同じ」状態。
1~3はCookieに特有の問題でWeb Storageには存在しないので、1~3をやってしまうくらいならWeb Storageの方が良いというのはその通り。
- 1については、デフォルトがSecureではないだから危ないという批判はその通りである。
- 2については、Domainが未指定の場合は同一ホストだけを指すので、デフォルトでは発生しない。
- 3については、Pathが未指定の場合はなにもしないので、デフォルトでは発生しない。
2つの選択肢の間の大きな相違点の一つとして、Web Storageはその内容がHTTPリクエストに自動的に付加されない点がcookieと異なる、ということが挙げられます。
これはその通り。
クロスドメインのリクエストに対してcookieを自動的に付加してしまうブラウザの動作は、CSRFや クロスオリジンの時間差攻撃 を可能にしてしまっています。現在この問題を解決するために開発中の 更に更に別のクッキー属性 の仕様が存在しますが、この属性が得られたところで、最善の賭けはWeb Storageでしょう。
「更に更に別のクッキー属性」として挙げられているのがSameSite属性だった。これがなかった時代の記事なのか。
SameSite属性はStrictおよびLaxを指定するとCSRFを防ぐ効果が得られる。また、未指定の場合のデフォルトはLaxに変わった。これにより、意図的にNoneを指定しない限りは、iframeやimgやJSの非同期通信ではCookieを送信しなくなった。
これがなかった過去の時代の批判。
SameSiteについて調べていたら出てきたんだけど、Chromeではクロススキーム(http
とhttps
が違う同一ドメインへのリクエスト)でCookieを送らない「Schemeful Same-Site」にしているらしい。
あくまでChromeではという話であって仕様というわけではないだろうけど、これがあると「Secure属性をつけ忘れたときの危険」からだいぶ救われる。(そもそも忘れちゃだめだけどね)
結論
Web Storageはcookieにとって代われるものを提供しています。もしデフォルトでセキュアでないとしても、少なくともデフォルトでcookieよりセキュアでないことはないのです。
この記事の当時の「SameSite=None+Secureなしがデフォルト」だったCookieなら、そうとも言えなくもない。
現在はSameSiteのデフォルトはLaxなので、当てはまらない。「SameSite=None+Secureなし」は警告対象になっているので(まともな運営者なら)すぐわかる。
また、この記事の文脈から読み取れる「XSSがあったらどうせ攻撃されるのだから、セッショントークンが盗まれることを気にしても仕方ない」みたいな雰囲気(?)は、あなたがそういうリスク判断をするのは別にいいけど、XSSがあってもなおセッショントークンが守れる方を使いたいという人に対する反論にはならない。
「万が一XSSがあってもCookie内のデータを盗まれない」ことの利点は確かに存在すると思う。
XSSが成立した瞬間にセッショントークン(セッションIDもしくはアクセストークンそのもの)にアクセスできるアプリとそうでないアプリでは、意味のある攻撃を成立させるまでの難易度が異なる。
前者では、攻撃の成立に必要なのは「取得したセッショントークンを攻撃者の元に送信するリクエストを1つ投げる」ことだけ。
後者では、攻撃のすべてを事前にXSSの攻撃スクリプトに含めておかないといけない。
XSSは成立するけど攻撃コードの長さに制限があり、なおかつ外部スクリプトは実行できない(CSPでインラインは許可されてるけど他ドメインおよびevalは許可されてない)状況なら、前者は成立するけど後者は成立しないという可能性は十分にあると思っているんだけど……どうなんだろ?
そもそも、HttpOnly CookieとWeb Storageには「HttpOnly Cookieの値を設定できるのはサーバーだけである」という違いがあるから、「どちらに保存するか」という話ではなく「Cookieが利用できないケースでローカルに永続化したかったらWeb Storageを使うしかないよね」という話だよね。
SPAとAPIサーバーが同一ドメインなら全部Cookieで済ませて問題ないけど、SPAとAPIサーバーがクロスドメインの場合はそうもいかない。
サードパーティーCookieは滅びの運命にあるから、SameSite=Noneで対処する方法はあまり将来性がないように思う。そうなると、APIサーバー側で適切なDomain属性を指定してSPAのドメインをファーストパーティーにする必要がある。また、認証APIとその他APIでドメインが異なっている場合も同様に、その他APIのドメインをDomain属性に指定してファーストパーティーにする必要がある。
(※ここ、自信がない。APIサーバーのドメインapi.example.com
の認証情報Cookieにおいて、Domain属性にSPAを配信しているサーバーのドメインapp.example.jp
が指定してあれば、SPAのJS非同期通信からAPIサーバーへリクエストを送信するときに認証情報Cookieを一緒に送信できる、という認識でいるんだけど、試したことがない。)
ただ、APIサーバー側が自分たちの管理下にない場合はDomain属性の設定はできないし、そもそもCookie自体も設定できないかもしれない。そういう場合は最初からCookieに保存するという手段は採れない。
(この場合、自前のAPIサーバーを立てて、外部APIサーバーからもらったトークンを自前APIサーバーが管理し、SPAと自前APIサーバーはCookieを使って通信する、ということはできなくはない)
元記事のURLで検索したら、似たような疑問を持ってる人がツイッターにいた。
そこで参照されてたOWASPの評価:
Do not store session identifiers in local storage as the data is always accessible by JavaScript. Cookies can mitigate this risk using the
httpOnly
flag.
結論出てんじゃん!!!!!
(雑記)
調査中に、MDN日本語版に結構重大な誤訳を発見した。どうやって報告するか調べておく。