CVE-2026-8054: Verified Reproduction
CVE-2026-8054: Unauthenticated SQL injection in dotCMS Publish Audit API
CVE-2026-8054 is verified against dotCMS/core · github. Affected versions: dotCMS Core 25.11.04-1 through 26.04.28-02. Vulnerability class: SQLi. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00194.
What Is CVE-2026-8054?
CVE-2026-8054 is a critical unauthenticated SQL injection vulnerability in dotCMS Core's Publish Audit API endpoints. Pruva reproduced it (reproduction REPRO-2026-00194).
CVE-2026-8054 Severity & CVSS Score
CVE-2026-8054 is rated critical severity, with a CVSS base score of 10.0 out of 10.
Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
Affected dotCMS/core Versions
dotCMS/core · github versions dotCMS Core 25.11.04-1 through 26.04.28-02 are affected.
How to Reproduce CVE-2026-8054
pruva-verify REPRO-2026-00194 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00194/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-8054
- reached the target end-to-end
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
JSON array element in POST /api/auditPublishing/getAll
- POST /api/auditPublishing/getAll
reproduction_steps.sh How the agent worked
Root Cause and Exploit Chain for CVE-2026-8054
CVE-2026-8054 is an unauthenticated, time-based SQL injection in the dotCMS Core Publish Audit API. The affected endpoints (/api/auditPublishing/get and /api/auditPublishing/getAll) accept attacker-controlled bundle identifiers that are concatenated into an IN (...) SQL clause without parameterization. A remote unauthenticated attacker can inject PostgreSQL pg_sleep payloads and observe response delays of several seconds, demonstrating that attacker input is executed as SQL in the backend database. The issue is fixed in dotCMS Core 26.04.28-03, which both parameterizes the bundle-id query and enforces push-publish authentication on the resource.
- Product / component: dotCMS Core Publish Audit API (
com.dotcms.rest.AuditPublishingResource,com.dotcms.publisher.business.PublishAuditAPIImpl) - Affected versions: 25.11.04-1 through 26.04.28-02 (per advisory data; not backported to LTS)
- Fixed version: 26.04.28-03
- Risk: Critical. An unauthenticated attacker can read, modify, or destroy database contents, because the SQL injection runs with the privileges of the dotCMS application user. The demonstrated time-based blind payload proves that the database evaluates attacker-controlled SQL.
- Consequences: Data breach, data integrity loss, and potential full database compromise.
Impact Parity
- Disclosed / claimed maximum impact: SQL injection in an unauthenticated remote API, allowing arbitrary database content read/modify/destroy.
- Reproduced impact: Unauthenticated, time-based blind SQL injection through
POST /api/auditPublishing/getAllwith apg_sleep(5)payload. The vulnerable build returns HTTP 200 after a ~5-second delay, while the fixed build returns HTTP 401 with no delay. - Parity:
partialfor the full read/modify/destroy claim — the reproduction proves SQL injection (a database primitive), but it does not execute a concrete read, modify, or destroy operation. The surface parity isfullbecause the same unauthenticated HTTP API path is exercised on both the vulnerable and fixed versions. - Not demonstrated: Direct extraction of data, DML/DDL abuse, or code execution beyond the database boundary.
Root Cause
The vulnerable code is in PublishAuditAPIImpl.getPublishAuditStatuses(List<String> bundleIds):
final List<String> parameter = bundleIds.stream()
.map(id -> "'" + id + "'")
.collect(Collectors.toList());
dc.setSQL(String.format(SELECT_ALL_BY_BUNDLES_IDS, String.join(",", parameter)));
Each bundle id is wrapped in single quotes and joined directly into the SQL string, so a value such as
x' || (SELECT CASE WHEN 1=1 THEN pg_sleep(5)::text ELSE '' END) || '
breaks out of the quoted literal and appends a PostgreSQL sleep expression to the generated query. The query is then executed by DotConnect.loadObjectResults(), causing the HTTP response to be delayed for the sleep duration.
Additionally, AuditPublishingResource had no authentication check on its endpoints, so the request could be sent anonymously. The fix in dotCMS/core#35553 (commit dc515d9 and subsequent review commits) addresses both aspects:
- Parameterization — replaces the quote-concatenated
INlist with?placeholders and binds the bundle ids withdc.addParam(...). - Authentication — adds
processAuthHeader(request)andPushPublishResourceUtil.getFailResponse(...)toAuditPublishingResource.get(...)andgetAll(...), so unauthenticated callers receive HTTP 401 before the query logic is reached.
Reproduction Steps
The reproduction is fully automated by bundle/repro/reproduction_steps.sh:
- Ensures the required Docker images are available (
dotcms/dotcms:26.04.28-02,dotcms/dotcms:26.04.28-03,postgres:15,opensearchproject/opensearch:1.3.19). - Spawns a per-version stack: one OpenSearch container, one PostgreSQL container, and one dotCMS container on a dedicated Docker network.
- Waits for dotCMS to finish startup (
MainServlet init completedin the container log and HTTP 200 from/api/v1/system/status). - Sends the following unauthenticated HTTP requests to each stack:
- Baseline:
POST /api/auditPublishing/getAllwith body["x' || (SELECT CASE WHEN 1=2 THEN pg_sleep(0)::text ELSE '' END) || '"](no sleep, should be fast). - Time-delay trigger:
POST /api/auditPublishing/getAllwith body["x' || (SELECT CASE WHEN 1=1 THEN pg_sleep(5)::text ELSE '' END) || '"](should delay ~5 seconds). - Optional GET probe:
GET /api/auditPublishing/get?bundleId=<url-encoded sqli>.
- Baseline:
- Compares response times and status codes between the vulnerable and fixed builds.
Expected evidence
- Vulnerable build (26.04.28-02):
POST /api/auditPublishing/getAllwith the true payload returns HTTP 200 and takes at least ~5 seconds. - Fixed build (26.04.28-03): the same request returns HTTP 401 and takes well under 1 second, proving both parameterization and authentication gating.
Evidence
- Runtime manifest:
bundle/repro/runtime_manifest.json - Reproduction log:
bundle/logs/reproduction_steps.log - Per-stack results:
bundle/logs/vuln_results.json,bundle/logs/fixed_results.json - Timing summary:
bundle/logs/timing_summary.tsv - Container logs:
bundle/logs/vuln_dotcms_container.log,bundle/logs/fixed_dotcms_container.log,bundle/logs/vuln_postgres_container.log,bundle/logs/fixed_postgres_container.log,bundle/logs/vuln_opensearch_container.log,bundle/logs/fixed_opensearch_container.log - Nuclei template reference:
https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/http/cves/2026/CVE-2026-8054.yaml - Fix commit / PR:
https://github.com/dotCMS/core/pull/35553
Key excerpts from the current run (see bundle/logs/timing_summary.tsv and bundle/logs/*_results.json):
- Vulnerable dotCMS 26.04.28-02 (
POST /api/auditPublishing/getAllwith a false-conditionpg_sleep(0)payload): HTTP 200, body[], duration 0.061 s. - Vulnerable dotCMS 26.04.28-02 (
POST /api/auditPublishing/getAllwith a true-conditionpg_sleep(5)payload): HTTP 200, body[], duration 5.023 s. - Fixed dotCMS 26.04.28-03 (
POST /api/auditPublishing/getAllwith the samepg_sleep(5)payload): HTTP 401, body reports an invalid authentication token, duration 0.007 s.
The ~5-second delay on the vulnerable build and the sub-10-millisecond 401 response on the fixed build demonstrate that the unauthenticated time-based SQL injection is present in 26.04.28-02 and absent in 26.04.28-03.
Recommendations / Next Steps
- Upgrade to dotCMS Core 26.04.28-03 or later (the patch is not backported to LTS, so LTS users must verify their release line).
- Do not roll back the authentication gating on
AuditPublishingResource; the endpoints are intended only for push-publish-authenticated callers. - Regression test with the exact nuclei-template payload and with malicious bundle ids such as
x' OR '1'='1,x'; DROP TABLE publishing_queue_audit; --, andx' UNION SELECT ...to confirm parameter binding prevents injection and preserves thepublishing_queue_audittable. - Review other REST resources that consume list parameters and build SQL
INclauses; ensure they use parameterized queries.
Additional Notes
- The script is idempotent: it removes and recreates containers, networks, and log files on each run, so it can be executed twice in a row without manual cleanup.
- The reproduction does not rely on ASAN/UBSAN or any static-analysis tool; it exercises the real dotCMS HTTP API through the full stack (PostgreSQL + OpenSearch + dotCMS).
- The GET
/api/auditPublishing/getendpoint is also protected by the same fix, but the primary time-delay evidence is obtained fromPOST /api/auditPublishing/getAll, which matches the public nuclei template and the advisory description.
CVE-2026-8054 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.
Artifacts and Evidence for CVE-2026-8054
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-8054
FAQ: CVE-2026-8054
Which dotCMS Core versions are affected by CVE-2026-8054, and where is it fixed?
How severe is CVE-2026-8054?
How can I reproduce CVE-2026-8054?
References for CVE-2026-8054
Authoritative sources for CVE-2026-8054 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.