
Plausible Analytics master d44e104 - 高 - リモート - IDOR - PUT traffic-change-notification site_id マスアサインメント
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · plausible-traffic-notify
Plausible Analytics コミュニティエディション、GitHub master d44e104ae3943a9240d5cb6b62e67dff0f069d3a (2026-10-16) - Plausible Insights OÜ
分類: IDOR (マスアサインメント)。到達範囲: リモート (攻撃者がすでに管理しているサイトの owner / admin / editor)。
Plausible.Site.TrafficChangeNotification.changeset/2 は依然として :site_id をキャストしている。SiteController.update_traffic_change_notification/2 は認可済みのパスドメインで行をロードし、その後クライアントのマップを Repo.update! する。ルート PUT /sites/:domain/traffic-change-notification/:type は :browser + CSRF + AuthorizeSiteAccess [:owner, :admin, :editor, :super_admin] の下に位置する。HTML フォームは threshold のみを送信する。しかし Phoenix は追加のキーを依然として受け付ける。
whoareme Bug 02 は Shield / Goal の site_id を同じ方法で修正した: socket.assigns.site.id をバインドし、:site_id のキャストを停止した。このコントローラはそのパッチには含まれていなかった。有効化 (POST .../enable) は assigns から site_id: site.id を設定する。攻撃者は自身のサイトで有効化し、その後 traffic_change_notification[site_id]=<victim_pk> を PUT する。ユニークインデックスは (site_id, type)。デフォルトインストールにはスパイク/ドロップの行が存在しないため、更新は成功し、その行は被害者のものになる。テーブル名はレガシーな spike_notifications である。
自身のサイトの editor が、スパイク/ドロップ通知の行を被害者サイトに付け替える。受信者は攻撃者のメールボックスのまま。Oban が被害者のトラフィックについて攻撃者にメールを送信する。
| ID | CVE 未割当 |
| CWE | CWE-915 / CWE-639 |
| CVSS | High: 7.1 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L |
| 製品 | Plausible Analytics |
| 影響範囲 | GitHub master d44e104ae3943a9240d5cb6b62e67dff0f069d3a (2026-10-16)。最後の CE タグ v3.2.1 (e4f5a87a5570bd61605217e7ceb376636db8eddb, 2026-05-15) は5か月遅れている。CE + EE (lib/、extra/lib ではない)。 |
| 認証 | 自身が管理するサイトの owner / admin / editor。viewer は不可。CSRF 必須 (同一セッション内 IDOR)。 |
| ライセンス | GNU Affero GPL v3.0 |
| ラボ | 127.0.0.1:18183 (http) 18184 (postgres) 18185 (clickhouse http) のみ |
Plausible Analytics leftovers の残存項目 #2。同じピン、別パック: plausible-editor-invite (People HTML が transfer nanoid を editor に漏洩する)。本パックは、依然として :site_id をキャストしている Shield/Goal の兄弟パックである。
すでに管理している任意のサイトの owner、admin、または editor。そのセッションのセッションクッキーと CSRF トークンが必要。被害者側のメンバーシップは不要。被害者のパスドメインを PUT することはない (それは 404 になる)。自身のドメインに対して、外部の site_id 整数 PK を付けて PUT する。
権限テーブル (網羅的)。Signal-Server Dropwizard admin と同じ基準: メソッド + パス、その呼び出しが付与するもの、recon 対 integrity 対 availability、ラボで実証済み 対 推測、閉じたままの隣接サーフェス。
| 軸 | メソッド + パス | 付与されるもの | 根拠 |
|---|---|---|---|
| 整合性 | POST /sites/:attacker_domain/traffic-change-notification/:type/enable の後に PUT /sites/:attacker_domain/traffic-change-notification/:type を traffic_change_notification[site_id]=<victim_pk> 付きで実行 | 攻撃者の spike_notifications 行を victim.sites.id に付け替える。受信者は攻撃者のアドレスのまま。一意な (site_id, type) スロットを占有する。しきい値は攻撃者が選択可能(ラボでは 1 を使用)。 | ラボで実証済み。 行 1 の site_id が 2 → 1 に移動、受信者は {[email protected]} |
| 偵察 | Oban TrafficChangeNotifier が notification.recipients に送るメール | 件名は Traffic spike on <victim.domain>。本文には現在の訪問者数、上位 3 ソース、上位 3 ページが含まれる。初回送信は即時(last_sent が nil)。以降は最大 12 時間ごとに繰り返す。しきい値 1 では、被害者に現在の訪問者がいる限りメールが生成される。メール内のダッシュボード HTML / インストールリンクは、受信者がチーム/ゲストメンバーでない限り省略される(攻撃者は該当しない)。 | 通知機能のソースから推測。ラボではメールを捕捉できず(MAILER_ADAPTER=Bamboo.LocalAdapter) |
| 可用性 | 被害者が同じ type に対して POST .../enable を実行 | 一意な (site_id, type) により拒否される。植え付けられた行が削除されるまで、被害者はこの機能を有効化できない。 | ソース(unique_constraint) |
| 可用性 | 被害者の disable(site_id による delete_all) | 余分な受信者に気づけば、被害者は植え付けられた行を消去できる。 | ソース(disable_traffic_change_notification) |
このピンでは閉じたまま
| 軸 | メソッド + パス | 閉じている理由 |
|---|---|---|
| 整合性 | PUT /sites/:victim_domain/traffic-change-notification/:type | パスのドメインは AuthorizeSiteAccess の対象。攻撃者はメンバーではない。ラボで 404 を実証済み。 attacker-not-member-of-victim=1 |
| 整合性 | Shield / Goal / ファネル / セグメント / 共有リンクの書き込み | whoareme Bug 02 がそれらの cast :site_id ハンドラにパッチを適用した。これらは assigns にバインドする。 |
| 偵察 | ClickHouse ダンプ、Stats API、ダッシュボード HTML | メンバーシップなし、共有リンクなし。通知メールは非メンバーにはダッシュボードリンクを省略する。 |
| 整合性 | トラッカー取り込み | すでにクライアントが選択したドメインで未認証の POST /api/event が可能。これはこの残存脆弱性とは別。 |
| 整合性 | 被害者がすでにその type を持つ場合の Repo.update! | 一意な (site_id, type) により 500 エラー。デフォルトインストールには行がないため、最初の植え付けは成功する。 |
| 整合性 | 攻撃者サイトの viewer | AuthorizeSiteAccess は owner/admin/editor が対象。viewer は不可。 |
| 整合性 | OS シェル、スーパー管理者 LiveView | 閉じている。シェルなし。 |
ClickHouse ダンプ、リバースシェル、被害者としてのダッシュボードログインを主張しているわけではない。ダンプとは、攻撃者がすでに受け取っているメール内のトラフィック偵察情報である:ドメイン、ライブ訪問者数、上位ソース、上位ページ。site_id は整数 PK(列挙可能。whoareme は site_id=1 を使用)である。ダッシュボードの data-* はこれを出力しない。
whoareme Bug 02 は、Shield と Goal が :site_id をキャストしないように教えた。私は lib/ 以下に残る cast(.*:site_id を grep した。トラフィック変更通知にはまだそれが残っていた。enable は assigns を刻印する。update はクライアントのマップを受け取る。これがその残存部分である。
run.sh は plausible-lab:d44e104ae3943a が存在しない場合にピンをクローンし、DISABLE_REGISTRATION=false で compose を起動して 2 つの独立したチームが登録できるようにし、victim.lab(id 1 team 1)と attacker.lab(id 2 team 2)をシードし、攻撃者でスパイク通知を有効化してから site_id=1 を PUT する。
すでに記録済みの誤った道:SECRET_KEY_BASE が 64 バイト未満(Plug.Session.COOKIE が拒否する);被害者パスのドメインへの PUT(404、それは閉じた隣接経路);enable をバグとみなすこと(enable は正しく割り当てられている)。茶番である。
python3 plausible-traffic-notify-Abraxas-Labs.py
または
cd lab
./run.sh
必ず http://127.0.0.1:18183 のみを対象とすること。イメージ plausible-lab:d44e104ae3943a(MIX_ENV=ce、ピン d44e104ae3943a9240d5cb6b62e67dff0f069d3a)。Docker が必要。初回実行時にその SHA で https://github.com/plausible/analytics をクローンし、イメージをビルドする。ランチャーは lab/ に chdir して run.sh を exec する。
IOC victim.lab id=1 team=1 attacker.lab id=2 team=2
IOC attacker-not-member-of-victim=1
IOC put-victim-path http=404
IOC spike_notifications row 1 site_id 2 -> 1 recipients={[email protected]}
SUCCESS plausible-traffic-notify PLAUSIBLE-H03-TRAFFIC-NOTIFY-SITEID-WITNESS
Witness 文字列: PLAUSIBLE-H03-TRAFFIC-NOTIFY-SITEID-WITNESS。オラクルは spike_notifications.site_id の移動です。Notifier のメール偵察は推測のままです。
:site_id のキャストをやめます。whoareme Bug 02 以降に Shield と Goal がすでに行っているように、conn.assigns から site.id をバインドします。
def changeset(schema, attrs) do
schema
|> cast(attrs, [:recipients, :threshold, :type])
|> validate_required([:recipients, :threshold, :type])
|> validate_number(:threshold, greater_than_or_equal_to: 1)
|> unique_constraint([:site_id, :type])
end
enable での insert 時に site_id を assigns から取得したままにしてください。PUT の body から取得してはいけません。
パッチ適用済みビルドに対して loopback client を再実行してください。外部の site_id を指定した PUT の後でも、その行は攻撃者の sites.id のままである必要があります。被害者のパスへの PUT は 404 のままである必要があります。
それまでは、「Shield と Goal にパッチを当てた」を「Site に belongs_to するすべての changeset が :site_id をキャストしなくなった」と解釈しないでください。