Skip to content

CVE-2026-8054: Verified Repro With Script Download

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.

REPRO-2026-00194 dotCMS/core · github SQLi Variant found Jul 1, 2026 CVE entry ↗ .txt
Severity
CRITICAL
CVSS
10.0
Confidence
HIGH
Reproduced in
116m 7s
Tool calls
532
Spend
$10.15
01 · Overview

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).

02 · Severity & CVSS

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 threat level
10.0 / 10 CVSS base
Weakness CWE-89 SQL Injection — Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')

Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.

03 · Affected Versions

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
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00194/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-8054

Security impact — reproduced
  • reached the target end-to-end
  • on the real production code path
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

JSON array element in POST /api/auditPublishing/getAll

Attack chain
  1. POST /api/auditPublishing/getAll
Runnable proof: reproduction_steps.sh
Captured evidence: fixed opensearch containerfixed dotcms containerfixed postgres container
Variants tested

No distinct bypass or alternate entry point confirmed for CVE-2026-8054. Exhaustive testing of the fixed dotCMS 26.04.28-03 build shows the patch parameterizes the SQL sink and authenticates all external paths to the Publish Audit API.

How the agent worked 1,531 events · 532 tool calls · 1h 56m
1h 56mDuration
532Tool calls
493Reasoning steps
1,531Events
4Dead-ends
Agent activity over 1h 56m
Support
15
Hypothesis
2
Repro
1,084
Judge
30
Variant
295
Coding
100
0:00116:07

Root Cause and Exploit Chain for CVE-2026-8054

Versions: 25.11.04-1 through 26.04.28-02 (per advisory data; not backported to LTS)Fixed: 26.04.28-03

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/getAll with a pg_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: partial for 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 is full because 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:

  1. Parameterization — replaces the quote-concatenated IN list with ? placeholders and binds the bundle ids with dc.addParam(...).
  2. Authentication — adds processAuthHeader(request) and PushPublishResourceUtil.getFailResponse(...) to AuditPublishingResource.get(...) and getAll(...), 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:

  1. 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).
  2. Spawns a per-version stack: one OpenSearch container, one PostgreSQL container, and one dotCMS container on a dedicated Docker network.
  3. Waits for dotCMS to finish startup (MainServlet init completed in the container log and HTTP 200 from /api/v1/system/status).
  4. Sends the following unauthenticated HTTP requests to each stack:
    • Baseline: POST /api/auditPublishing/getAll with 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/getAll with 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>.
  5. Compares response times and status codes between the vulnerable and fixed builds.
Expected evidence
  • Vulnerable build (26.04.28-02): POST /api/auditPublishing/getAll with 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/getAll with a false-condition pg_sleep(0) payload): HTTP 200, body [], duration 0.061 s.
  • Vulnerable dotCMS 26.04.28-02 (POST /api/auditPublishing/getAll with a true-condition pg_sleep(5) payload): HTTP 200, body [], duration 5.023 s.
  • Fixed dotCMS 26.04.28-03 (POST /api/auditPublishing/getAll with the same pg_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

  1. 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).
  2. Do not roll back the authentication gating on AuditPublishingResource; the endpoints are intended only for push-publish-authenticated callers.
  3. 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; --, and x' UNION SELECT ... to confirm parameter binding prevents injection and preserves the publishing_queue_audit table.
  4. Review other REST resources that consume list parameters and build SQL IN clauses; 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/get endpoint is also protected by the same fix, but the primary time-delay evidence is obtained from POST /api/auditPublishing/getAll, which matches the public nuclei template and the advisory description.

Variant Analysis & Alternative Triggers for CVE-2026-8054

Versions: versions tested: 26.04.28-02 (vulnerable), 26.04.28-03 (fixed).

CVE-2026-8054 is an unauthenticated, time-based SQL injection in the dotCMS Core Publish Audit API. The original reproduction confirmed that POST /api/auditPublishing/getAll with a pg_sleep payload in a JSON array element delays ~5 seconds on the vulnerable build (26.04.28-02) and is rejected with HTTP 401 on the fixed build (26.04.28-03). This variant stage exhaustively tested the same and adjacent endpoints, different request bodies/content types, and several authentication-bypass tricks against the fixed build. No distinct bypass or alternate entry point that reaches the same SQL-injection sink was confirmed. The GET endpoint (/api/auditPublishing/get/{bundleId}) is reachable unauthenticated in the vulnerable version but uses a parameterized query, so it does not trigger the same SQLi root cause. LTS 24.x images do not contain the vulnerable getPublishAuditStatuses(List<String>) method at all.

Fix Coverage / Assumptions

The vendor patch (PR #35553, commit 6a5f4188715baaf5b4ffdf0f8f80c402ccfb97ab) relies on two invariants:

  1. Sink parameterization: com.dotcms.publisher.business.PublishAuditAPIImpl.getPublishAuditStatuses(List<String>) now builds IN (...) with ? placeholders and calls dc.addParam() for each bundle ID, so no attacker-controlled string can be concatenated into the SQL text.
  2. Endpoint authentication: com.dotcms.rest.AuditPublishingResource.get() and .getAll() now require a valid push-publish token (JWT admin token or endpoint auth key tied to the remote IP). Anonymous requests are rejected by PushPublishResourceUtil.getFailResponse().

The patch explicitly covers the only external entry point to the vulnerable sink (AuditPublishingResource.getAll). PublisherQueueJob was updated to send the required Authorization header for legitimate internal use.

Variant / Alternate Trigger

The following candidate paths were tested against both the vulnerable and fixed builds via bundle/vuln_variant/reproduction_steps.sh:

  1. Positive controlPOST /api/auditPublishing/getAll with the original pg_sleep JSON array payload.
  2. GET path variantGET /api/auditPublishing/get/{bundleId} where the path parameter contains a pg_sleep payload.
  3. Non-array body variantPOST /api/auditPublishing/getAll with a single JSON string body instead of a list.
  4. Empty list edge casePOST /api/auditPublishing/getAll with [].
  5. Content-type variantstext/plain body and missing Content-Type.
  6. Auth bypass: empty BearerAuthorization: Bearer .
  7. Auth bypass: IP spoofing headerX-Forwarded-For: 127.0.0.1.
  8. Auth bypass: HTTP method overrideX-HTTP-Method-Override: GET.

None of the candidates produced a duration >= 4.0s HTTP 200 response on the fixed build. The only code path that still reaches the original sink is the internal PublisherQueueJob, which now supplies a valid push-publish token and is not attacker-reachable.

  • Product / component: dotCMS Core Publish Audit API (com.dotcms.rest.AuditPublishingResource, com.dotcms.publisher.business.PublishAuditAPIImpl).
  • Affected versions tested: 26.04.28-02 (vulnerable), 26.04.28-03 (fixed).
  • Risk: Critical for the original unauthenticated SQLi; no additional unauthenticated SQLi path was reproduced in this stage.
  • Consequences: No new data breach or database-compromise primitive was found beyond the already-disclosed POST /getAll path.

Impact Parity

  • Disclosed / claimed maximum impact: Unauthenticated remote SQL injection allowing arbitrary database read/modify/destroy.
  • Reproduced impact from this variant stage: Only the original POST /getAll time-based SQLi reproduced on the vulnerable build; the fixed build rejected all tested payloads with HTTP 401 and no time delay.
  • Parity: none for a new variant — no additional SQLi surface or bypass was demonstrated.
  • Not demonstrated: A concrete read, modify, or destroy query on any new path; a bypass of the authentication on the fixed build; a variant that works against the LTS line.

Root Cause

The same SQL-injection root cause (string concatenation of bundle IDs into an IN (...) clause) can only be reached through PublishAuditAPIImpl.getPublishAuditStatuses(List<String>). The patch removes that concatenation entirely. The only REST endpoint that historically called that method, AuditPublishingResource.getAll, is now authenticated. Because the single-ID method getPublishAuditStatus(String) has always been parameterized, the GET endpoint never shared the same SQLi sink.

Reproduction Steps

  1. Run bash bundle/vuln_variant/reproduction_steps.sh.
  2. The script starts a vulnerable dotCMS stack (26.04.28-02) and a fixed dotCMS stack (26.04.28-03), each with Postgres and OpenSearch.
  3. It waits for both stacks to be live, then sends the eight candidate requests to each stack from a Python sidecar.
  4. It compares HTTP status codes and response durations and writes bundle/logs/vuln_variant_analysis.json.
  5. Expected evidence: vulnerable build returns HTTP 200 with ~5s delay for the original POST /getAll payload; fixed build returns HTTP 401 with no delay for every candidate.

Evidence

  • Variant script: bundle/vuln_variant/reproduction_steps.sh
  • Per-stack results: bundle/logs/vuln_variant_results.json, bundle/logs/fixed_variant_results.json
  • Analysis summary: bundle/logs/vuln_variant_analysis.json
  • Runtime log: bundle/logs/vuln_variant_reproduction_steps.log
  • Patch source: https://github.com/dotCMS/core/pull/35553 (merge commit 6a5f4188715baaf5b4ffdf0f8f80c402ccfb97ab)

Key excerpts from the second run:

Test Vuln status/duration Fixed status/duration
getAll_true_condition (positive control) 200 / 5.02s 401 / 0.007s
get_path_sqli 404 / 0.047s 401 / 0.010s
getAll_empty_bearer 200 / 5.02s 401 / 0.006s
getAll_x_forwarded_for_localhost 200 / 5.02s 401 / 0.006s
getAll_method_override 200 / 5.02s 401 / 0.006s

Recommendations / Next Steps

  • No additional code change is required for the specific CVE-2026-8054 surface: the patch fully covers the SQLi sink and the external entry point.
  • Hardening idea: Consider adding authentication enforcement at the JAX-RS class level or a servlet filter for all /auditPublishing paths, so any future methods added to AuditPublishingResource inherit the same protection.
  • LTS policy: If any 25.x/26.x LTS branch contains the method introduced by 09fbef6b5a9f02e9f70804251783ff267c85eaf6, the same two-part fix (parameterization + authentication) should be backported.
  • Code audit: The same id -> "'" + id + "'" concatenation pattern found in BrowserAPIImpl (out of scope for this CVE) should be reviewed for authenticated SQLi risk independently.

Additional Notes

  • The script was run twice and produced consistent results; both runs exited 1 because no bypass/variant was found, which is the expected outcome for a negative variant search.
  • The vulnerable build returns HTTP 500 for POST /api/auditPublishing/getAll with [] because the code calls bundleIds.get(0) in the exception handler. This is a secondary bug but not a SQL-injection variant.
  • LTS 24.x images (e.g., 24.12.27_lts_v23) were not tested by runtime because bytecode inspection showed they do not contain getPublishAuditStatuses(List<String>), so the vulnerable sink is absent.

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.

Event 1/40
0:005:25
0:00
session startedaccounts/fireworks/models/kimi-k2p7-code · CVE-2026-8054 · REPRO-20
0:04
0:05
0:12
0:14
web search
0:24
0:25
1:26
1:26
extract_facts
no facts extracted
1:27
1:27
1:27
supportrepro
5:06
5:10
5:10
5:10
5:10
5:13
5:13
5:13
5:15
5:15
5:19
5:19
5:19
5:19
5:22
5:22
5:25
08 · How to Fix

How to Fix CVE-2026-8054

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-8054 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-8054

Which dotCMS Core versions are affected by CVE-2026-8054, and where is it fixed?

dotCMS Core 25.11.04-1 through 26.04.28-02 are affected, fixed in 26.04.28-03. The vulnerable code path was not backported to LTS releases.

How severe is CVE-2026-8054?

Critical — the Publish Audit API endpoints can be queried without authentication, so a remote attacker can inject SQL directly against the dotCMS database.

How can I reproduce CVE-2026-8054?

Download the verified script from this page and run it in an isolated environment against dotCMS Core 25.11.04-1 through 26.04.28-02. It sends unauthenticated requests to /api/auditPublishing/get and /api/auditPublishing/getAll with SQL injection payloads and confirms the injection against the backing database, then confirms 26.04.28-03 rejects it.
11 · References

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.