こんにちは、 @okazu_dm です。 前回の記事 に引き続きCookie関連のセキュリティに関する記事となります。
今回は、Cookieの仕様を定めたRFC6265(https://datatracker.ietf.org/doc/html/rfc6265)自体に含まれるSecure属性の問題点と、その対策について紹介していきます。
CookieのSecure属性自体は前回紹介したSameSite属性と比較してわかりやすいのもあり、かなり知名度が高いと思われますが、Secure属性単体で守れる範囲というのは実は限定的である、という点を本記事では実験も交えて示していきます。
なお、本記事はセキュリティ以外の分野を主業務とするソフトウェアエンジニアを主な想定読者として書いています。
- 記事内の検証につかったブラウザのバージョン
- Cookieについて
- 中間者攻撃の仕組み
- 実際に中間者攻撃をしてみる
- 「httpsのリクエストだけ受けるようにする」だけでは対策としては不十分
- 対策としてのSecure属性
- Secure属性の仕様上の注意点
- HSTS(HTTP Strict Transport Security)
- Cookieを守るための追加策: __Host- prefix
- まとめ
- お知らせ
- Flatt Securityは外部パートナーと連携して技術記事を発信しています
記事内の検証につかったブラウザのバージョン
- Safari: 16.4 (18615.1.26.11.22)
- Google Chrome: 122.0.6261.112
- Firefox: 122.0
Cookieについて
まず、この記事の主なトピックとなるCookieについて簡単におさらいします。 (前回の記事と全く同じ内容になります)
Cookieは、本来状態を持たないプロトコルの中で、ブラウザにHTTPコネクションを越えて状態を保持させるための仕組みです。 HTTPのレスポンスヘッダ、 リクエストヘッダによりCookieに保存された文字列がやりとりされます。具体的にはサーバからSet-Cookieレスポンスヘッダを通じてブラウザにセットされ、逆にブラウザからはCookieリクエストヘッダを通じてサーバに対して送信されます。
上の画像ではSESSION_IDという名前のCookieにサーバサイドのアプリケーションが発行したランダムなセッションIDをセットしています。Cookieの上書きや削除、ブラウザ側の機構により判定される例外などいくつかのケースを除いて、これ以降の通信ではブラウザからサーバに対してCookieヘッダを通してこのCookieが渡されます。
このようにすることで、サーバサイドアプリケーションは一連の通信をするブラウザを見分けることができます。
この記事に関連する部分を簡単に紹介しましたが、詳細についてはMDNの解説 が分かりやすさと網羅性のバランスが良いため、おすすめします。
中間者攻撃の仕組み
以上のようにCookieは、サーバサイドアプリケーションがユーザを見分けるなどの目的で使われます。これは、例えば悪意ある攻撃者がユーザとサイトとの通信に介入し、Cookieを窃取、または任意の値を設定できる状況の場合、不正ログインなどにつながることを示します。
では、攻撃者はどのようにこれを実現するのでしょうか。以下の図をご覧ください。
このように、攻撃対象のユーザとサイトとの通信に割り込む、または傍受する形で通信の中身を不正に読み書きする攻撃を、中間者攻撃と呼びます。
実際に中間者攻撃をしてみる
では、実際に中間者攻撃をしてみましょう。 手法としてはいくつかの選択肢があるのですが、今回は説明の都合上、また単純化のためにhttpのみを傍受、改ざんするようにします。
まず、以下のような単純なサービスを用意します。このサービスは80番ポートでHTTPリクエストを受け付けて “Hello World!” と表示するだけの簡単なサービスです。
では、この通信に介入してみましょう。
今回はBurp Suiteというツールのproxy機能を使います。このツールが起動しているポートを通信時に経由させることで、通信の改ざんが可能になります。今回は端末のプロキシ設定で意図的に経由させました。
まず、正規のレスポンスを途中で止めている状態が以下です
これを以下のように書き換えることで攻撃を試みます。
すると、ブラウザに返ってくるレスポンスが以下のように書き換えられていることがわかり、これにより中間者攻撃が成功したことを確認できました。
「httpsのリクエストだけ受けるようにする」だけでは対策としては不十分
では、上記のような平文の通信に対する中間者攻撃に対してはどのように対策すれば良いのでしょうか。
最初に思いつくのは「そもそも平文通信をしないためにサイト上からhttpのページをなくす」ということですが、根本的な対策にはなりません。その理由としては、中間者攻撃を成立させるためには、ユーザが平文のリクエストを発行さえすれば良いためです。
例えば、以下の図のようにサービス側がhttpの通信を受け付けていなくてもブラウザがリクエストを送信さえすれば攻撃は成立します。 (下図は読み取られるケースについての説明ですが、当然攻撃者が任意の非SecureなCookieをセットすることも可能です)
対策としてのSecure属性
こういった状況に対して、Cookieに「httpの通信では送信されず、httpsの通信においてのみ送信される」という挙動を設定するのがSecure属性です。
以下のキャプチャを見ると、Set-Cookieヘッダに”Secure”というディレクティブが付加されていることがわかります。
これを受け取ったブラウザは、以下のように、Secure属性を有効化した状態でCookieを保存します。
そして、この状態でhttpとhttpsそれぞれの経路で同じサイトにアクセスすると、以下のように、「httpsだとCookieが付与されるが、httpだと付与されない」という挙動になります。
以上より、攻撃者がユーザにhttpで通信させても、攻撃者がCookieを読み取ることはできなくなります。
しかし、読み取りではなく、Cookieに対して攻撃者が恣意的な値を書き込むような攻撃への対策としてはSecure属性では不十分です。 Secure属性のそもそもの仕様の観点から、どのようなケースで攻撃が成功するのか、という点を以降で解説します。
Secure属性の仕様上の注意点
前述した通り、Secure Cookieはhttpの通信に介入した攻撃者から読み取ることはできませんが、Secure Cookieを保持した状態で同名の通常のCookie(Secure属性を持たないCookie)の書き込みはできてしまいます。
具体的にはSecure Cookieを直接上書きするわけではなく、Secure Cookieと通常のCookieが両方存在する状態を実現できてしまうことが確認できました。この状態下でリクエストを送信する際は2つのCookieを両方送信しており、送信される順番やサーバサイドアプリケーションの実装などによって、どちらのCookieが受理されるかは変化します。
ただし、本記事執筆時点でSafari, Chrome, Firefoxについて検証した際にはSafariのみ確認されています。
この挙動により部分的にSecure Cookieの上書きが実現できますが、「Secure属性に関する仕様としては正しい挙動であり、対策しているブラウザ側が仕様にない挙動を独自に実装している」というのが現状です。
では、Cookieの仕様についての記述があるRFC6265のSecure属性に関する箇所を確認しましょう。
既に解説したSecure属性の挙動(安全な経路でのみ送信される)についての記述のあとに、以下のような一文があります。
An active network attacker can overwrite Secure cookies from an insecure channel, disrupting their integrity
これは日本語訳すると以下のようになります。
アクティブなネットワーク攻撃者は安全でない経路からSecure Cookieを上書きして完全性を破壊することができます
このことから仕様上、Secure属性が中間者攻撃からCookieを守ることができるのはあくまで読み取れなくするだけ、と想定されていることがわかります。
ここまでの内容については、徳丸浩さんの以下のブログで10年以上前に既にまとめられているため、問題の概要についてはこちらもご参照ください(実際のブラウザの挙動や対策については最新の状況と差分が発生しているためご注意ください)。
では、実際に書き込みができてしまうのか、というのをこれから試していきます。
Secure Cookieと同名の非Secure Cookieを中間者が書き込みできるかの実験
まず、今回の実験は複数のドメインをまたいで行うため、登場するドメインについて先に触れておきます。
subdomain.website.example
: 攻撃者が不正にwebsite.example
ドメインのCookieをセットするために、平文のURLをユーザに踏ませるドメインwebsite.example
: 正規のサービスが動いており、最終的に攻撃者によって不正なCookieをセットされた状態でユーザがアクセスするドメイン
どちらのドメインも攻撃者の管理下にはありません。また、website.exampleで動いているサービスはhttpsでない通信は受け付けないようになっています
実験のシナリオとしては以下のようになります。
手順0. 被害者となるユーザは、事前に https://website.example
で以下のSecure Cookieをセットされている。
COOKIE_WRITER=official
手順1. ユーザは攻撃者によって平文URL( http://subdomain.website.example
) へのリクエストを誘発させられる(平文のリンクを踏むなど)
手順2. ユーザが送信した http://subdomain.website.example
へのリクエストに対して、攻撃者は中間者攻撃により、以下のDomain属性を持つSet-Cookieヘッダを含むレスポンスを返しつつ、https://website.example
にユーザを誘導する。
Set-Cookie: COOKIE_WRITER=!attacker! Domain=website.example
手順3. ユーザは不正なCookieがセットされている状態で、 https://website.example
にアクセスしてしまう(例えばこれがセッションIDを保持するCookieなどであれば、ユーザは攻撃者が指定するユーザとしてログインさせられるなどの被害につながる)
※ちなみにこれらの挙動は同一ドメインのhttpsとhttpとのURL間でも再現しますが、ドメインで通信先が分かれていたほうがわかりやすいと判断し、便宜上サブドメイン経由の中間者攻撃としています
では、実際にやってみましょう。
最初に手順0時点でのSafari上でのhttps://website.exmapleのCookieの保存状態を確認します。
この時点では正規のサービスから書き込まれたSecure Cookieがあるだけです。前提条件はこれで整っているので実験の手順を進めます。
手順1は経路が何であれ、http://subdomain.website.example
へのリクエストが発生すれば良いため、今回はユーザが平文のリンクを踏んでしまったという設定で直接遷移します。
手順2で実際にユーザに返ってきたレスポンスが以下です。
また、このときのブラウザ上のCookieの保存状況は以下のようになっています
ここまでで、平文の経路からSecure Cookieと同名のCookieの書き込みが可能であることを示しました。
では次に、手順3で実際に攻撃対象のドメインにアクセスした際に、攻撃者によってセットされたCookieがリクエストヘッダに付加されるかどうかを確認します。
上図のように、リクエストヘッダには攻撃者によってセットされたCookieが乗っていることが確認できます(なお、正規のサイトが書き込んだSecure Cookieも同時に送信されています。どちらを利用するかはアプリケーションのサーバーサイドの実装次第です)。
以上より、httpの経路上で行われる中間者攻撃によってSecure Cookieと同名のCookieを書き込み可能であり、最終的にSecure Cookieの上書きと同じ被害に繋がり得るということが確認できました。
同名のCookieが複数存在する、というこの挙動についてRFC6265を見ると、”5.3. Storage Model” に以下のような記述が見つかりました。
- If the cookie store contains a cookie with the same name, domain, and path as the newly created cookie:
- Let old-cookie be the existing cookie with the same name, domain, and path as the newly created cookie. (Notice that this algorithm maintains the invariant that there is at most one such cookie.)
即ち、Cookieは「名前」「Domain」「Path」の3つによって特定されるのであり、名前が一緒であっても残りの2つが違う(今回はDomainが違う)場合は、別のCookieとして共存する、という仕様であることがわかります。
ちなみにSafari以外のブラウザの現状を確認するためにChrome, Firefoxで挙動を確認しましたが、この攻撃は一度Secure Cookieを保持した状態では再現しませんでした。具体的には、既に対象ドメインでセットされているSecure Cookieがある場合、同名の非SecureなCookieはDomain属性を付与してもセットできない、という挙動が確認できました。
Chromeでは、”Strict Secure Cookies”という名前でこの仕様について言及しており、この仕様によりChromeでは安全な経路でのみCookieを変更可能になる、としています。
今回検証した3ブラウザ(Chrome, Safari, Firefox)の攻撃耐性をまとめると、以下の通りです。
- Safari
- 被害者がすでに正規サイトにアクセスし、一度Secure Cookieを保持した状態であっても攻撃者が同名の非Secure Cookieを書き込み可能
- Chrome, Firefox
- 被害者が同名のSecure Cookieを保持していない場合に限り、攻撃者が非Secure Cookieを書き込み可能
Secure Cookieの落とし穴について実験も踏まえて示したところで、更にこの攻撃に対しての対策を検討していきましょう。
HSTS(HTTP Strict Transport Security)
サブドメインを含めて、ユーザがそもそもhttpのリクエストを送信しないようにすれば今回のケースにおける攻撃は防げます。「(httpのリンクを踏んだとしても)httpsの通信のみ行うようにブラウザに指定する」という仕組みがHSTS(HTTP Strict Transport Security)です。
HSTSは以下のような形でレスポンスヘッダを返すと指定された秒数(以下の例では365日)は、そもそもブラウザ側がhttpのアクセスをしなくなる(自動でhttpsのアクセスに切り替える)ような仕様です。
Strict-Transport-Security: max-age=31536000;
これに、更に includeSubDomains
ディレクティブを最後につけると、サブドメインに対しても同様の制約を強制することができます。
また、以上の仕組みでは「初回のリクエストのときにはhttpでのリクエストを許可してしまう」という問題があります。これに対して、リクエスト先のサイトでHSTSが有効かどうかを事前に判断するために、HSTS対応サイトのリストを読み込む仕組みがあります。 この仕組みはpreloadと呼ばれ、いくつか満たさなければならない条件はありますが、大まかには以下のような流れで利用します。
Strict-Transport-Security
ヘッダにpreload
ディレクティブがある場合、Googleが提供するフォーム からリストに登録できる。- ブラウザがこのリストを参照する(ブラウザの実装によるが少なくともChromeに関してはハードコードする形で実現されている)。
- あとは通常のHSTSと同様に、リクエスト時に強制的にhttpsのリクエストに切り替える。
preloadはHSTSの本来の仕様には存在しない仕組みですが、主要なブラウザ(Chrome, Firefox, Safari)は対応しています。
参考: MDNの解説(https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Strict-Transport-Security)
preloadは今回の実験のような、httpの経路における中間者攻撃に対しての根本的な対策になりますが、以下の条件を満たさなければ使えません。
- サーバ側が有効な証明書を返す
- httpのリクエストに対してhttpsへリダイレクトする
- すべてのサブドメイン上のサイトがhttps化されている
- ベースドメインでHSTSヘッダを返す
- HSTSヘッダ中に
includeSubdomains
とpreload
ディレクティブが存在する - HSTSヘッダの
max-age
ディレクティブの値が31536000(1年)以上である
例えばサブドメイン上でhttps化できていないサービスなどがありpreloadが使えない、というケースが考えられます。 こういったケースでも今回の攻撃を防げる、Cookieで現在提案中の仕様があるため以降で紹介します。
Cookieを守るための追加策: __Host- prefix
今回の攻撃は、そもそもCookieへのDomain属性の付与や、暗号化されていない(httpsではない)通信経路からのCookieの書き込みを禁止することができれば成立しません。そして、それを実現する仕様がCookie Prefixesという形で提案されています。
この仕様では、 __Host-
という文字列から始まる名前のCookieに対して以下のような制約を与えます。
- Secure属性が有効である必要がある
- 安全な経路(今回のケースで言えばhttpsの通信のレスポンス)にセットされる必要がある
- Domain属性が空である必要がある
- Path属性が
/
である必要がある
3点目と4点目により、同名のCookieが一個だけであることが保証されます。
今回検証した3ブラウザ(Chrome, Safari, Firefox)では、以上の制約を満たさないCookieを __Host-
から始まる名前で保存できなくするようになっています。
(Secure属性だけを要求する__Secure prefixも同じdraftで提案されていますが、今回は特に関係がないため言及しません)
では、最後にこの仕組みで攻撃が防げるかを実験します。
__Host- prefixで攻撃が防げるかの実験
実験のシナリオ自体は同じですが、サービス側が要求するCookie(つまり攻撃者側がセットしないといけないCookie)の名前を __Host-COOKIE_WRITER
とします。
では、手順通りに進めていきますが、手順1は同じであるため手順2から見ていきます。 まず、Safariが受け取ったレスポンスのキャプチャを示します。
上図では、中間者攻撃によって書き換えられたレスポンスでサブドメインから上位ドメインのCookieに対して
__Host-COOKIE_WRITER=!attacker!
とセットするよう試みています。
しかし、実際には以下のようにSafari側のストレージには保存されておらず、このCookieを受理していないことがわかります。
(既に正規のドメインからセットされているCookieにDomain属性があるように見えますが、これは何も指定していないときの値で、ドメインを指定すると先頭に .
がつきます)
これは、__Host- prefixの条件に対して以下のように違反していることが原因です。
- Secure属性が有効ではない
- 安全な経路ではない(httpである)
- Domain属性が空ではない
以上より、そもそもCookieがセットされなくなったため、攻撃を防ぐことに成功しました。
まとめ
この記事では、Cookieの仕様それ自体が内包する「Secure Cookieはhttpの経路から書き換えられてしまい得る」という問題と、その問題への対策としてHSTS preloadや現在draftとして提案されている__Host- prefixが有効であることを示しました。
今回紹介したCookie prefixes自体はdraftとはいえ2016年に提案されており、主要ブラウザはどれも取り入れている事実上の標準仕様になりつつあります。このように、大本の仕様だけでなく、その時点の各ブラウザの挙動を踏まえた対策が必要なのは現在のWebアプリケーション開発における難しいポイントと言えるでしょう。
お知らせ
Flatt Security ではWebアプリケーションをはじめとする、様々なプロダクトへのセキュリティ診断サービスを提供しています。仕様・実装に不安のある方はぜひお気軽にお問い合わせください。
上記のデータが示すように、診断は幅広いご予算帯に応じて実施が可能です。ご興味のある方向けに下記バナーより料金に関する資料もダウンロード可能です。
また、Flatt Security はセキュリティに関する様々な発信を行っています。 最新情報を見逃さないよう、公式X のフォローをぜひお願いします!
では、ここまでお読みいただきありがとうございました。
Flatt Securityは外部パートナーと連携して技術記事を発信しています
本稿はFlatt Securityの外部パートナーが執筆し、Flatt Securityが監修を行った記事です。通常、企業の技術ブログは自社の技術力やカルチャーの発信のため、雇用関係にある社内メンバーの執筆によって発信されることがほとんどだと思います。
一方、Flatt Securityの技術ブログでは、本稿のようにセキュリティの知見をお持ちの外部の方に依頼しているケースがあります。種々の脆弱性情報や情報発信に知見を持つFlatt Securityの技術ブログ編集部が執筆者の方と連携することで、テーマや構成の検討・レビューをサポートしています。
これは、Flatt Securityの技術ブログの目的が自社のアピールにとどまらず、セキュリティに関する有益な情報をより多く社会に還元し、セキュリティ企業とセキュリティサービス利用者の間の情報の非対称性を無くすことを目的としているためです。
本文章は執筆者がセキュリティ診断のようなFlatt Securityのサービス提供に直接的に関わっているかのような誤解を防ぐために明記していますが、執筆にご興味を持たれた方はお問い合わせください。
※Flatt Securityが技術ブログ企画において重視している考え方については、以下の記事をご覧ください。