WebアプリケーションでJWTをセッションに使う際の保存先は(自分なりに説明できれば)どちらでもよいと思います

以下のツイートを読んで気持ちが昂ったので。

というのも、JWTをセッションに使うときに保存先含めて一時期悩んでいたので、その時の自分の解。

ただ、考えるたびに変化しているので、変わるのかもしれない。

要約

タイトル。

あとは優秀な方々が既に色々考えておられるのでそちらを読むとよいでしょう。

SPAセキュリティ入門~PHP Conference Japan 2021

JWT カテゴリーの記事一覧 - r-weblife

どうしてリスクアセスメントせずに JWT をセッションに使っちゃうわけ? - co3k.org

JWT形式を採用したChatWorkのアクセストークンについて - Chatwork Creator's Note

以降は読む価値ないです。あとは自分からの社会への不平不満や嫉妬とか想いとか。


私の解

JWTをセッションとして使う

色々課題があります。課題の答えを出した上でJWTを採用するのは、良いと思います。

ただし「JWTでセッション管理がステートレスになるぜ!スケールするぜ!!」と思っているなら、それは思い違いです。 JWT自体は「ある発行者が作った情報のためのフォーマット」にすぎません。JWTにはログアウトやセッションを無効化する話はありません。(セッションの有効期間は期限(exp)に持たせて表現するとかありますが) それに署名に使う鍵をどう管理したらよいでしょうか?アプリケーション単位でしょうか?ユーザ単位でしょうか?色々考えることはあるはずです。

まぁ、頑張って考えてもユーザや会社からは何の評価もされないと思いますけど。

JWT格納先としての Cookie vs localstorage(web storage) への自分の解

どちらでもよいと思います。CookieだろうがlocalstorageだろうがどっちもXSSの前には無力です

  • CookieはJWTが漏れないだけで攻撃ができないわけではない
    • リクエストを投げればブラウザが勝手につけるので、XSSで目的のリクエストを投げればいいので無力
  • localstorageはJWTも漏れるし、Cookieと同様にリクエストを投げるロジックを呼べばよい
    • こっちはブラウザが勝手につけるわけではないが、XSSできるなら取得してリクエストにつけて投げるのも簡単なので無力

とはいえ、Cookie(httponly)ならJavaScriptからどうあがいても読み取ることはできません。 そういった意味ではlocalstorageよりもCookieのほうがセキュアかもしれません。ただしhttponlyなCookieだろうがXSSで攻撃に繋げられるのであまり意味はないこともあります。

クロスサイトスクリプティング(XSS)対策としてCookieのHttpOnly属性でどこまで安全になるのか - YouTube

つまり、XSS無いことを前提にすればどっちも安全です。XSSが無いことを踏まえて自分にメリットのある方法を選べば良いかと思います。

  • Q1.機密情報をlocalstorageに入れるべきではないのでは?徳丸本でもそう言ってた
  • A1.はい。見られて困るものは入れてはいけないですね
  • Q2.じゃあセッション情報(セッションIDとかセッションとしてのJWT)を入れるべきではないのでは?
  • A2.いいえ。XSSが無いなら、見られても無意味ですし、改ざんされても無意味なので入れてもいいと思いますよ。
  • Q3.いや、セッション情報って見られたらそれを使って悪用できてしまうから、見られたら困るものでしょ?
  • A3.結論出ましたね。じゃあそういうことならCookieにしておけばいいんじゃないですかね。はい、さようなら。まぁ、悪意を持って見るためにはXSSが必要で、XSSの前にはどっちも一緒だと思いますよ。
  • Q4.どっちだよ?XSS無ければ機密情報を入れてもいいの?
  • A4.私はそう思ってます。今は。

追記:A1とA4で矛盾してんじゃんという感じですが、A4の機密情報はセッションIDとかJWTを指してるつもりでした。クレジットカードとかの即座に使える機密情報は入れてはいけないのは当然です。それはlocalstorage自体のライフタイムの長さとその間にXSSが起きるリスクを考えて、私は「入れたらまずいよね」という感覚だからです。また、Cookieなら機密情報を入れてもいいというわけでもないです。

ランダムなセッションID vs JWT への自分の解

どちらでもよいです。

一応「ランダムなセッションID」とは従来のセッション管理の仕組みで使う方法です。サーバサイドでログイン時にセッションID払い出しユーザ情報に紐づけ、ブラウザにセッションIDを返して次回のリクエストにセッションIDを付けてリクエストしてもらうことでセッションの仕組みを成り立たせています。

「ランダムなセッションID」はセッションIDだけ見ても情報はわかりません。しかし、そこからサーバサイドに問い合わせてどういうユーザか等の情報を集めるということはできます。

一方、JWTはペイロードの内容から情報を抜き取ることができます。JWTをみれば誰がいつログインしたかとかの情報はわかってしまうかもしれません(持たせている情報によります) では暗号化しましょう、とJWE を採用するとか、独自にペイロード部分を暗号化するのもよいでしょうが、そこまでする価値はあるかというと、私は無いと思います。 ランダムなセッションIDを使った場合と同じで、結局サーバサイドに問い合わせてどういうユーザか等の情報を集めるということはできるからです。

もし、奪取されてから次の攻撃までに無効化する運用が整っていれば暗号化の価値はあるかもしれませんが、私が知る限り「奪取された」という事を知る術は難しいと思うので価値が薄いと思っています(アクセスログからその兆候が見られるかもしれません。ほらAI案件ですよ。よかったですね。)

その他

Cookieに格納する」 は 「サーバーサイドセッションに格納する」というわけではない

リプライをみて「うーん?」という感じがしたので。

まず、「Cookieの値をサーバサイドでユーザと紐づけることでセッションを保持する仕組み」が王道パターンです。これが「サーバーサイドセッションに格納する」という意味であれば正しいです。

しかし、別にCookieに「ユーザID」と「署名」を付けたものを与えれば、「サーバーサイドセッションに格納する」ことなくユーザを一意に特定できます。 サーバ側はCookieのユーザIDをみれば誰かわかります。もちろん署名もちゃんと検証しましょう。 悪い人がユーザIDを成り済まそうとしても、署名を作るのは困難なので大丈夫、という理屈です。 セッションのための情報をサーバサイドに持たなくても良くなるのでスケールもします。ワオ!いいことづくめですね!オレ天災なのでは?こんな簡単なことなんでみんなやらないんだろう???

という感じで独自フォーマットを考えてもいいですが、JWTはなんとこの特性を揃えています。JWTもただの文字列です。というわけでJWTをCookieに使うことができます。(Cookieのサイズ制限には注意するぐらいか。"alg":"none"を認めちゃだめとかもあります。)

しかし、JWT自体はただのフォーマットなので、ログアウトの概念とか強制ログアウトさせたいとかの運用周りの問題があったり色々厄介なのです(上記の方法も同様です)。

ではログアウトのことを考えましょう、となると、結局何かしらサーバサイドで状態を持たないといけません。 JWTをlocalstorageで持とうがCookieで持とうが「サーバーサイドセッションに格納する」という点は変わらないことになるのかもしれません。

Chatworkさんの事例

徳丸先生のスライド中にChatworkさんの事例があったので一応。 JWTをCookieにいれるかlocalstorageに入れるか、という話とはずれますが「アクセストークン(JWT)をどう管理するか」という点は変わらないです。

Chatworkさんの事例ではアクセストークンをステートレスで扱います。その代わり、期限を30分と短くしてその期限内の悪用は許容しています。 で、使い続けたい場合はリフレッシュトークンでアクセストークンを作り直してくれ、というやり方です。そこにログアウトはないということです。 その代わり再発行するためのリフレッシュトークンは結局ユーザに紐づけて管理しています。まぁ、ここを管理しないといくらでも再発行できちゃうor都度アクセストークンを発行させるような作業をユーザに強いることになりますからね。

これは頭が良くて、よく「セッション情報が流出したから強制ログアウトさせたい」みたいな運用を求めることがありますが、この運用自体が手遅れなんです。流出した事実はかわらないですからね。 パスワードのハッシュが流出したんでパスワードを再設定してください、というのも手遅れ。GitHubAWSのアクセスキー流しちゃったとかも手遅れ。流出した事実はかわらないですからね。

そこで、流出したものが無意味になればどうでしょうか。パスワードのハッシュならハッシュに使うsalt値を変えれば概ね無意味になりますし、AWSのアクセスキーとかは失効できます。

しかし、それもその操作を行うまでです。そこにリードタイムがあり、その間は悪用できてしまいます。

Chatworkさんのアクセストークンもこの考え方で、30分という期間の悪用は認めることでステートレスを手に入れています。 まぁ今度はリフレッシュトークンが漏れないように注意しないといけなかったり、そのリフレッシュトークンをWebアプリに適用する場合にどこに保存するか考えたり課題はあるのですが、アーキテクチャ的には大半はアクセストークンを使った操作になるのでそこがスケールするのはおいしいでしょう。

リスクを許容すれば、こういうやり方もできますよという事例ですね。

「みんな、もうSNSでいがみ合うのはやめよう」とは

流行りのテンプレートみたいです。

https://togetter.com/li/1843400

以下はお気持ち

セッションの管理・運用は簡単じゃない

  • ログアウトという一般的な要件を満たせる
  • セッションが悪用されているときに強制的に無効化する仕組みがあるか
  • 内部犯の脅威

多分きちんとやっているところのほうが少ないと思います。フレームワークのサンプルのGet Startから始めて数分でログイン機能付きのウェブサイトができた!となり、そこに肉付けしていってるだけだと思います。私もそうです。それは別に恥ずべきことではないです。

ステートフルなセッション管理を採用する、つまりセッションIDはランダムに発行してその時々で紐づけて管理するというやり方は、先駆者らが色々考えた上でその仕組みになっており、色々と理にかなっています。

内部犯のために定期的に鍵のローテーションとか本当にやってる所あるんでしょうか?建前では言ってるけど本当にやってますか?やらなくても表面化しませんよね? そもそも中の人が退職されたりしたら発行済みのセッションIDを全部リセットしましょうとか本当はやったほうがいいですよね?なんでやらないんですか?悪用のリスクとユーザ影響を天秤にかけたうえでの判断ですか?ならいいでしょう。私の責任じゃないですし。

あと、そんなところに工数をかけてもお客さんや会社は見てくれないですからね。 ちゃんとアピールしていかないといけないんですが、セッション管理一つとっても、こういう課題があるんですよ!っていっても「ログイン・ログアウトとか今どきできて当たり前でしょ」ってなりますし、そもそもこういう攻撃のケースはなかなかイメージしてくれません。悲しいなぁ。

サプライチェーン攻撃

JWTとかもはやほとんど関係ないですが、XSSするにはなにもアプリの脆弱性だけではなくなってきています。

人為的か悪意的かともかくとして、npmやCDNを経由として、悪いコードを仕込む余地があります。

www.itmedia.co.jp

news.mynavi.jp

scan.netsecurity.ne.jp

Wordpressならプラグイン脆弱性を使って攻撃を仕込まれるのもサプライチェーン攻撃でしょう。

ところで、あなたが使ってるVSCodeプラグイン、ブラウザのExtension、Ansibleのモジュール、Linuxで得体の知れないパッケージリポジトリにあるパッケージなどについて何があるでしょうか?それは安全でしょうか?

あとは人ですね。人。悪い人が全部悪い。悪い人類は全て滅ぼそう。HTTPと信頼だけの優しいWebを取り戻しましょう。

最近はウェブサイト作ったら作っただけ脆弱性になるんじゃないかとヒヤヒヤしながら作ってます。楽をしたい