危険なCookieのキャッシュとRailsの脆弱性CVE-2024-26144
高度診断部アプリケーションセキュリティ課の山崎です。
弊社エンジニアの名古屋と山崎が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
が含まれているとユーザの認証情報がキャッシュされ、そのまま他のユーザにも適用されてしまい、別人としてログインしてしまう可能性があります。
セッション情報漏洩の脆弱性: CVE-2024-26144
今回、このような事故が発生しうる脆弱性が、有名なWebフレームワークであるRuby on RailsのActive Storageというモジュールに存在することが判明しました。
Active StorageはAmazon S3等の各種ストレージへのファイルのアップロード、参照を容易にしてくれるモジュールです。
アップロードしたファイルの参照方式にはProxy Mode
とRedirect Mode
の2種類が存在し、Proxy Mode
にこの脆弱性が存在します。
Redirect Mode
ではAmazon S3等のオブジェクトストレージのURLへユーザがリダイレクトされるのに対し、Proxy Mode
ではActive Storageがファイルをストレージから取得して、ユーザへ直接返却します。
問題のあった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
ユーザ固有のページではなく静的ファイルをキャッシュする設定であるため、一見すると問題がないように見えるかもしれません。
しかしこれは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レスポンスを受け取った別人が同じセッションを共有してしまうことになり、別人としてログインしてしまう事故が発生してしまいます。
実のところ、私達がこの脆弱性に気付いた当時、既にこの問題は一部で認識されていました。
偶然、副次的にこの脆弱性を解消する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 脆弱性の修正バージョンがリリースされる