セキュリティブログ

危険なCookieのキャッシュとRailsの脆弱性CVE-2024-26144

危険なCookieのキャッシュとRailsの脆弱性CVE-2024-26144

更新日:2024.07.08




高度診断部アプリケーションセキュリティ課の山崎です。

弊社エンジニアの名古屋と山崎がRuby on RailsのActive Storageの脆弱性CVE-2024-26144を報告しました。
本脆弱性はRailsの5.2.0から7.1.0のバージョンに影響するもので、お使いのRailsのバージョンが最新でない場合にはアップデートを推奨します。

本記事では本脆弱性の注意点と、関連してCookieのキャッシュに関する調査内容を紹介します。

  • TL;DR
    • Set-Cookieヘッダがキャッシュされると別人ログイン問題が発生する
    • ・ RailsのActive StorageでSet-Cookieヘッダがキャッシュ可能な設定であった(CVE-2024-26144)
    • ・ Nginx(+ Passenger), Apache(+ mod_cache)等のキャッシュ機構と合わせて利用すると実際に事故が発生しうる

危険なCookieのキャッシュ

Webサービスにおいて、CDN等を用いてHTTPレスポンスをキャッシュしレスポンスを高速化することは多くのサービスが実施していることと思います。

そうした時、キャッシュ制御を行うCache-Controlヘッダに設定不備があると、本来キャッシュすべきでないユーザ情報をキャッシュしてしまい、他のユーザから見えてしまう事故が発生する場合があります。

2017年にメルカリにおいて発生した事案では、Cache-Control: no-cacheと一見キャッシュが無効化されるような設定であったにも関わらず個人情報がキャッシュされる事故が発生してしまっており、Cache-Controlヘッダやキャッシュ条件の難しさが広く知られるようになりました。

参考: https://engineering.mercari.com/blog/entry/2017-06-22-204500/

この件のように、ユーザごとに変わるコンテンツはキャッシュしないよう制御するというのは、CDNやキャッシュを用いる上での注意点としてよく語られています。
しかしながら、コンテンツ自体がロゴファイルのように無害なものであったとしても、HTTPキャッシュサーバでは多くの場合HTTPレスポンス全体がキャッシュされるため、HTTPヘッダもキャッシュされることに注意する必要があります。

特にHTTPヘッダにSet-Cookieが含まれているとユーザの認証情報がキャッシュされ、そのまま他のユーザにも適用されてしまい、別人としてログインしてしまう可能性があります。

Set-Cookieヘッダがキャッシュされると別人としてログインしてしまう

セッション情報漏洩の脆弱性: CVE-2024-26144

今回、このような事故が発生しうる脆弱性が、有名なWebフレームワークであるRuby on RailsのActive Storageというモジュールに存在することが判明しました。

Active StorageはAmazon S3等の各種ストレージへのファイルのアップロード、参照を容易にしてくれるモジュールです。
アップロードしたファイルの参照方式にはProxy ModeRedirect Modeの2種類が存在し、Proxy Modeにこの脆弱性が存在します。

Redirect ModeではAmazon S3等のオブジェクトストレージのURLへユーザがリダイレクトされるのに対し、Proxy ModeではActive Storageがファイルをストレージから取得して、ユーザへ直接返却します。

Proxy ModeとRedirect Modeの違い

問題のあったProxy Modeでは、ファイルが経路上のCDN等でキャッシュされるように、HTTPレスポンスヘッダCache-Control: max-age=3155695200, publicが付与されています。

class ActiveStorage::Representations::ProxyController < ActiveStorage::Representations::BaseController
  include ActiveStorage::Streaming

  def show
    http_cache_forever public: true do
      send_blob_stream @representation.image, disposition: params[:disposition]
    end
  end
end

引用: https://github.com/rails/rails/blob/v7.0.8/activestorage/app/controllers/active_storage/representations/proxy_controller.rb

ユーザ固有のページではなく静的ファイルをキャッシュする設定であるため、一見すると問題がないように見えるかもしれません。
しかしこれはRailsのコントローラの処理を経由しているため、既にセッションが発行されたユーザがアクセスしてきた場合、そのユーザに対する新しいセッションCookieも同時にHTTPレスポンスに含まれてしまいます。(Railsはデフォルトの設定でセッション情報を変更していなくともSet-Cookieヘッダを付与します。)

実際に、Active Storage(修正前のバージョン)からファイルを取得しようとすると、更新されたセッション情報がSet-Cookieヘッダについた状態で返却されることが確認できます。

HTTP/1.1 200 OK
Date: Wed, 12 Jun 2024 08:44:47 GMT
ETag: W/"086b03ec3d6db21e130802e2e88df190"
Last-Modified: Sat, 01 Jan 2011 00:00:00 GMT
Accept-Ranges: bytes
Content-Type: image/png
Content-Disposition: inline; filename="logo.png"; filename*=UTF-8''logo.png
Cache-Control: max-age=3155695200, public
Set-Cookie: _rails_app_session=qXt9TIV6t0Ep4SoDRo6Tljxa4xsse2wDO3dTlVhc1un208xLErNndRaEbisDCxFaym2oI4KE%2B9L7jNb1ByJKr9Fqpaom1WvZBG4hxk9ejdlb1UfkrPwjQvXdivAe3CzO8P9FAVtroslMLzaEf%2FWAlO8uqRWvssYFpX2n7atcaXHry2eG0TD2mXkZh1zbI7cyMIU5xl2xhSa%2BoRYsgUIb5h9H--NrWa0c%2FxYSVa4iwJ--mckEpRFNnYSP0n4MAeeDSg%3D%3D; path=/; HttpOnly; SameSite=Lax
X-Request-Id: 2fc016a2-ef83-4b78-9a51-3c9e71c195cd
X-Runtime: 0.002313
Transfer-Encoding: chunked

...

HTTPレスポンスにCache-Control: publicが付与されているため、通信経路上にあるCDN等でこのHTTPレスポンス全体がキャッシュされてしまう可能性があります。
もしそのようなことが起きると、キャッシュされたHTTPレスポンスを受け取った別人が同じセッションを共有してしまうことになり、別人としてログインしてしまう事故が発生してしまいます。

Set-Cookieヘッダがキャッシュされることで別人としてログインしてしまう

実のところ、私達がこの脆弱性に気付いた当時、既にこの問題は一部で認識されていました。 
偶然、副次的にこの脆弱性を解消するPull Requestが提出されており、それに対するコメントとして機密情報が誤ってキャッシュされてしまう可能性が指摘されていました。

This is a problem because rails uses said header to store its session, which might contain sensitive data. Worst case scenario, it might contain the ID of the logged user, which means that when the file is served, the user will be automatically switched to a different account.

引用: https://github.com/rails/rails/pull/48869#issuecomment-1662196817

しかしながらこのPull Requestでも挙げられているように、CloudflareのようなCDNでは機密情報を含む可能性が高いSet-Cookieヘッダをキャッシュしないため、この問題は脆弱性ではなく機能面での不具合修正という位置づけで取り扱われているようでした。
そのため、具体的に脆弱性となる構成を提示して報告したところ、無事に脆弱性として取り扱われ、少し古いバージョンのRailsにもパッチが適用されることになりました。

どんな構成でCookieはキャッシュされるのか?

ここまでCookieがキャッシュされる可能性について述べましたが、Cookieがセッション管理の用途で頻繁に利用されていることはCDN等のキャッシュサーバの設計者も恐らく把握しているでしょう。
機密情報を含む可能性の非常に高いSet-Cookieヘッダがキャッシュされる、という事故はそこまで警戒すべきなのでしょうか?

例えば、既に述べた通りCloudflareはSet-Cookieヘッダをキャッシュすることはなく、Set-Cookieヘッダが含まれるレスポンスはキャッシュしない、もしくはSet-Cookieヘッダを削除するという動作を取るようです。

For non-cacheable requests, Set-Cookie is always preserved. For cacheable requests, there are three possible behaviors:

  • Set-Cookie is returned from origin and the default cache level is used. If origin cache control is not enabled, Cloudflare removes the Set-Cookie and caches the asset. If origin cache control is enabled, Cloudflare does not cache the asset and preserves the Set-Cookie. A cache status of BYPASS is returned.

  • Set-Cookie is returned from origin and the cache level is set to Cache Everything. In this case, Cloudflare preserves the Set-Cookie but does not cache the asset. A cache MISS will be returned every time.

  • Set-Cookie is returned from origin, the cache level is set to Cache Everything and edge cache TTL is set. In this case, Cloudflare removes the Set-Cookie and the asset is cached.

引用: https://developers.cloudflare.com/cache/concepts/cache-behavior/

これはまさにSet-Cookieヘッダがキャッシュされることによるセッション漏洩事故を防ぐための仕組みであり、多くのCDNでは安全のためにこういった仕組みがあるかもしれません。

翻ってHTTP/1.1のキャッシュに関するRFC 7234では、機密情報を誤ってキャッシュしないようにという注意と同時に、Set-Cookieヘッダはキャッシュを阻害するものではないとも書かれています。
本来はキャッシュサーバが気をつけるべきではなく、Set-Cookieヘッダを発行するWebサーバが適切なCache-Controlヘッダを発行すべきなのです。

Note that the Set-Cookie response header field [RFC6265] does not inhibit caching; a cacheable response with a Set-Cookie header field can be (and often is) used to satisfy subsequent requests to caches. Servers who wish to control caching of these responses are encouraged to emit appropriate Cache-Control response header fields.

引用: https://httpwg.org/specs/rfc7234.html#security.considerations

そこで、実際にいくつかのCDNやリバースプロキシを使って、挙動を確認してみました。
今回は対象として以下のシステムを選びました。

  • リバースプロキシ
    • ・ Nginx + proxy_cache
    • ・ Nginx + Passenger
    • ・ Apache + mod_cache
    • ・ HAProxy
  • CDN
    • ・ Cloudflare
    • ・ CloudFront
    • ・ Fastly

それぞれのシステムをできる限り手を加えずに標準的と思われるキャッシュ設定で構築し、Rails(バージョン7.0.8)のActive Storageが返すSet-Cookie付きのレスポンスがキャッシュされるかどうかを確認した結果が以下の表になります。

システム レスポンスがキャッシュされる キャッシュにSet-Cookieが含まれる
Nginx + proxy_cache NO -
Nginx + Passenger(Turbocaching) YES YES
Apache + mod_cache YES YES
HAProxy YES YES
Cloudflare (Free plan) NO -
CloudFront (CachingOptimized) YES NO
CloudFront (UseOriginCacheControlHeaders) NO -
Fastly NO -

* 検証コードは https://github.com/gmo-ierae/CVE-2024-26144-test にて公開しております。

意外なことにリバースプロキシを利用した構成では、ほぼ全てでSet-Cookieヘッダがキャッシュされてしまいました。
実際に、前段にHAProxy、後段にRailsサーバという構成で構築された環境に対して、管理者がアクセスした後に匿名ユーザがアクセスすると、管理者のCookieが降ってきてしまうことが確認できます。

# サーバ起動(前段: HAProxy, 後段: Rails)
docker compose up

# 管理者がロゴ画像にアクセス
curl localhost:12345/logo -i -L -H 'Cookie: _rails_app_session=73WeMieff%2FPjxtxx2aCBxjin...;'
...
HTTP/1.1 200 OK
...
cache-control: max-age=3155695200, public
set-cookie: _rails_app_session=2SETjNa0z9NYJDH84IhuNohiSs...; path=/; HttpOnly; SameSite=Lax
x-request-id: acf3bf9a-47e8-49dd-8fd1-73fba0e1b49f
...

# ログインしていないユーザがロゴ画像にアクセスしても、キャッシュから管理者のCookieが返ってくる
curl localhost:12345/logo -i -L 
...
HTTP/1.1 200 OK
...
cache-control: max-age=3155695200, public
set-cookie: _rails_app_session=2SETjNa0z9NYJDH84IhuNohiSs...; path=/; HttpOnly; SameSite=Lax
x-request-id: acf3bf9a-47e8-49dd-8fd1-73fba0e1b49f
...

# 以降、管理者になりすますことができる
curl localhost:12345/ -H 'Cookie: _rails_app_session=2SETjNa0z9NYJDH84IhuNohiSs...'

一方でCloudflare, CloudFront, FastlyのようなCDNではSet-Cookieヘッダはキャッシュされませんでした。
もちろん設定を細かく制御することでSet-Cookieヘッダをあえてキャッシュするようなことも可能ですが、デフォルトの設定やよく利用されるであろう設定ではSet-Cookieヘッダはキャッシュされないようです。

また、CloudFrontで標準キャッシュポリシーの一つのCachingOptimizedを選択した場合、レスポンスからSet-Cookieヘッダを除外してキャッシュするような挙動が見られたのは興味深いところです。

結果としては、少なくともNginx + Passenger、Apache + mod_cache、HAProxyのようなリバースプロキシを利用しており、バックエンドがRailsアプリである場合には本脆弱性の影響を受ける可能性があります。
特に、Railsと合わせて利用されることの多いPassengerではTurbocachingという機能がデフォルトで有効になっており、最大2秒間キャッシュが保持されます。このため、特段何もしていなくとも、RailsのActiveStorageとPassengerを両方利用しているだけで本脆弱性の影響を受ける可能性が高いと思われます。

今回の調査結果はあくまでもActive Storageのレスポンスがキャッシュされるかどうかを検証したもので、他の状況ではVaryヘッダ等がキャッシュの使用条件に影響する場合があります。
またRailsを使用していない場合でもSet-Cookieヘッダがキャッシュされるようなレスポンスは生成しないよう、アプリケーション開発時に注意が必要です。

おわりに

本記事ではCookieのキャッシュとRailsの脆弱性CVE-2024-26144の内容について紹介いたしました。
また、どのような構成でCookieがキャッシュされうるのかについても紹介しましたが、いずれの場合でもお使いのRailsのバージョンをアップデートすることを推奨します。

GMOサイバーセキュリティ byイエラエではバグバウンティやセキュリティコンテストなどで活躍するセキュリティエンジニアが実施する「Webペネトレーションテスト <シナリオ型> / <調査型>」を提供しています。
標準的なWebアプリケーション診断では検出ができないような脆弱性も検出し、リスクを評価します。
また、ソースコードを共有いただくことによってより精度の高い調査を実施することが可能な他、気になるCVEやライブラリ、フレームワークを踏まえた脆弱性調査やご相談も可能です。
是非お気軽にご相談ください。

Webペネトレーションテスト <シナリオ型> / <調査型>

CVE-2024-26144の修正タイムライン:

  • 2023/09/08 脆弱性を報告
  • 2023/10/10 Rails開発チームによるトリアージ
  • 2024/02/22 脆弱性の修正バージョンがリリースされる
セキュリティ診断のことなら
お気軽にご相談ください
セキュリティ診断で発見された脆弱性と、具体的な内容・再現方法・リスク・対策方法を報告したレポートのサンプルをご覧いただけます。

関連記事

経験豊富なエンジニアが
セキュリティの不安を解消します

Webサービスやアプリにおけるセキュリティ上の問題点を解消し、
収益の最大化を実現する相談役としてぜひお気軽にご連絡ください。

疑問点やお見積もり依頼はこちらから

お見積もり・お問い合わせ

セキュリティ診断サービスについてのご紹介

資料ダウンロード