JWTをlocalStorageに保存するのは脆弱?Cookieの方が良い?と言う話題について
Xで見かけたテーマ
最近、XでJWTトークンをlocalStorageに保存するのは危ないからCookieに保存した方が良い、という話を中心に議論が広がっていましたね。
この辺り疎いのですが話題になっていたので取り上げてみました。
JWTとは、そもそもサーバー側でセッションIDを発行し、DBやRedisに保存するようなセッションCookieの方式(ステートフル)とは異なり、サーバー側で発行したJWTをクライアント側で保存する方式(ステートレス)です。
なので、クライアント側マシンのlocalStorageに置くのかCookieに置くのかどっちが安全なんだという話です。
私が最初に思ったのは、JWTはlocalStorageに入れるよりも、Cookieに入れてHttpOnlyを指定する方がJavaSriptからのアクセスを遮断できる、だからそっちの方がいいんじゃないか、とシンプルに思っていました。
ですが、今回話題になっていた本質はそこではないようです。
以下のポストで気づきました。
どうゆうことなのかというと、この例で言うと「裏口が空いている時点で、もう終わっている」と言うのがポイントで、すでに家に泥棒が侵入できているなら、もう家の中にいるのだから鍵を盗む必要がないと言うことです。
つまり、XSSで侵入され任意のJavaScriptが実行できる時点で、攻撃者はすでにユーザーになりすましAPIを叩き放題になってしまいます。
例えば、
// HttpOnly Cookieは自動で付くので、攻撃者はただfetchするだけ
fetch('/api/transfer', {
method: 'POST',
body: JSON.stringify({ to: 'attacker', amount: 1000000 })
})
XSSが成立しているなら攻撃者は、
- Cookie(HttpOnly)が読めなくても、ブラウザ上で直接APIを叩ける。
- ユーザーの操作画面を全部監視・改ざんできる
- キーロガーを仕込んでパスワードも盗める
- 偽のログイン画面を被せて認証情報も取れる
といった感じで、トークンが盗まれるかどうかはもはや本質的な問題ではないわけです。
じゃあ、そもそもHttpOnly Cookieには意味がなくなってしまうのでは?と言う次の疑問が浮かんでくるのですが、多少の意味はあります。
例えば、攻撃者がlocalStorageからJWTを盗むことができたとすると、それを持ち帰ってそのトークンの期限が切れるまで持続的に使い続けることができてしまいます。 一方、HttpOnly Cookieの場合は、攻撃者のXSSが成立したとしてもJWTを盗むことができず被害想定はXSSが実行されているそのセッションに留まります。
家の比喩で言い直すと、
- localStorage = 鍵を盗まれてコピーされ、後日また来られる
- HttpOnly Cookie = 泥棒が今いる間しか悪さをできない(鍵を持ち帰ることができない)
というイメージです。 (関連ポスト)
まとめると、JWTをlocalStorageに置くのがいいかCookieに置くのが良いかといった議論は、XSSが成立している時点でそれはすでに意味がない。 もっと重要なのは、破られた時にどれだけ被害を抑えられるか、多層防御にするといったことだと思われます。
このあたりのセキュリティの事情を全然知らなかったので大変勉強になる良い例でした。 何か間違ってたらご指摘もらえると嬉しいです。
追記#
最近、この件について時系列でわかりやすく解説された記事が出されていました。ありがたいです!