Back to blog

CVE-2026-20253: how a localhost-only database helper turned every AWS Splunk box into a pre-auth RCE

· · 16 min read
CVE-2026-20253: how a localhost-only database helper turned every AWS Splunk box into a pre-auth RCE

On June 10, 2026, Splunk published advisory SVD-2026-0603, and three days later watchTowr Labs released the full technical chain and a working proof of concept. The verdict is short and ugly: an unauthenticated attacker who can reach a Splunk Enterprise web interface can write arbitrary files to the host and, by abusing the same path, run code as the Splunk user with no credentials at all. The bug is tracked as CVE-2026-20253 and carries a CVSS 9.8. The detail that should make every security operations team uncomfortable is where it lives: not in some exotic add-on, but in a PostgreSQL helper service that ships inside Splunk Enterprise 10 and is turned on by default on AWS, the platform many teams use to watch everything else.

This is a two-layered breakdown. The first part is for readers who need to decide quickly whether they are exposed and what to do about it this week, with no PostgreSQL internals required. The second part dissects the chain the way watchTowr reconstructed it, from an unauthenticated file-write primitive to full remote code execution, including the connection-string trick and the .pgpass abuse that turn a "limited" file bug into a server takeover. The article closes by looking at how the Pragma Core platform addresses exactly the kind of problem that made this possible: a trusted internal service quietly exposed across a boundary nobody was checking.


Part I. Executive breakdown

What happened

Splunk Enterprise version 10 introduced a PostgreSQL "sidecar" service. A sidecar is a small helper process that runs next to the main application and handles one job, in this case backing up and restoring an internal database that Splunk uses. It was designed to listen only on the local machine, which is usually considered safe, because nothing outside the host can talk to it.

The problem is twofold. First, the helper performs no authentication. It accepts any request, including requests carrying empty or junk credentials, and happily runs database backup and restore operations on the caller's behalf. Second, the main Splunk web interface, the one that does sit on the network, forwards certain requests straight to that local helper. So the "only reachable from localhost" assumption quietly stopped being true. Anyone who can reach the Splunk web port can reach the helper through it.

The concrete result: an attacker with no account, no password, and no prior foothold can make the helper write files anywhere the Splunk process can write, overwrite the right one, and end up executing commands as Splunk. Splunk is frequently the most privileged log-and-monitoring system in the building, so the attacker lands exactly where the defenders watch from.

Who is affected

The file-operation flaw depends on the PostgreSQL sidecar, which only exists in the Splunk Enterprise 10 line. The older 9.x branches were patched in the same release batch for unrelated bundled-component issues, but they do not carry this specific bug.

Component Status
Splunk Enterprise 10.2.0 to 10.2.3 Vulnerable. Upgrade to 10.2.4.
Splunk Enterprise 10.0.0 to 10.0.6 Vulnerable. Upgrade to 10.0.7.
Splunk Enterprise 10.4.x Fixed at 10.4.0
Splunk Enterprise 9.4.x and 9.3.x Sidecar not present; not affected by CVE-2026-20253
Splunk Enterprise on AWS (10.x) Highest risk. Sidecar installed and enabled by default.
Splunk Enterprise on-prem (Windows) Sidecar not installed or enabled by default; verify before assuming safe
Splunk Cloud Platform Splunk states Cloud does not use the affected sidecars and is not exposed to this CVE

The number that matters here is "enabled by default on AWS." On a self-managed Windows box the component may not be present, but on a cloud image it is live out of the gate, which means a large share of internet-adjacent Splunk deployments shipped exploitable from first boot. At the time of the advisory there were no confirmed reports of in-the-wild exploitation, but a public proof of concept landed on June 13, which collapses the gap between "interesting writeup" and "scriptable attack."

Why this matters beyond Splunk

Strip away the Splunk branding and the pattern is one of the most common architectural mistakes of the last decade: a service that is "secure because it only listens on localhost" sitting behind a proxy that makes localhost reachable. The moment an application-layer router forwards external requests to an internal endpoint, the network boundary that the internal service relied on for its security stops existing, but the internal service never learns that and keeps trusting everyone.

You will find this exact shape in microservice meshes, in admin APIs bound to 127.0.0.1 behind an nginx reverse proxy, in Kubernetes sidecars, and in any "internal" management interface that someone later decided to expose through the main app for convenience. The bug class is missing authentication on a critical function, CWE-306, and the aggravating factor is a trust boundary that two different components describe differently.

Recommended actions

  1. Upgrade to a fixed release now: Splunk Enterprise 10.2.4, 10.0.7, or 10.4.0, depending on your track. There is no real workaround for CVE-2026-20253; patching is the fix.
  2. Inventory every Splunk Enterprise instance and record its exact version and host platform. Treat AWS-hosted 10.x instances as presumed-exposed until patched.
  3. Determine whether the PostgreSQL sidecar is actually running in each deployment, and restrict network access to the Splunk web interface so it is not reachable from untrusted networks while you roll out the patch.
  4. Hunt for abuse: review file integrity under the Splunk install directory, look for unexpected modifications to bundled Python scripts, and audit access logs to the backup and restore API paths.
  5. Apply the rest of the June batch at the same time, including the Splunk Secure Gateway deserialization fix, since these were disclosed together and an attacker who has read the advisory has read all of it.
  6. Move Splunk management interfaces behind segmentation so that "internal only" is enforced by the network, not assumed by a single proxy rule.

Part II. Technical breakdown

The architecture: a backup helper that trusted the network instead of the request

Splunk Enterprise 10 ships an internal PostgreSQL database for certain platform features, and alongside it a sidecar service whose job is backup and recovery. That sidecar exposes a small HTTP API and, by design, binds to the loopback interface on a local port (port 5435 in the deployments watchTowr examined). On its own, a service bound to 127.0.0.1 is not reachable from the network, and that is the entire basis on which it skipped authentication.

The design invariant the developers leaned on was: only local processes can call this, so local processes are trusted, so no credential check is needed. That invariant holds right up until something on the network is allowed to speak to the loopback port on the caller's behalf. In Splunk, the main web application on port 8000 proxies specific request paths to the sidecar. The two recovery endpoints that matter are:

POST /v1/postgres/recovery/backup
POST /v1/postgres/recovery/restore

Send a request to those paths through the public web service and it is forwarded to the local sidecar. The sidecar sees a connection from localhost, applies its "local equals trusted" rule, and does what it is told. The authentication header is accepted and ignored: any credentials, including empty ones, are good enough. That is the root of the CVSS 9.8.

The vulnerability: unauthenticated file creation and truncation

The recovery endpoints wrap the standard PostgreSQL command-line utilities pg_dump and pg_restore. The backup endpoint takes parameters describing what to dump and, critically, where to write the resulting file. By controlling the destination parameter (the backupFile value) and using directory-traversal sequences, an attacker steers the output to a path of their choosing rather than the intended backup directory.

# Conceptual shape of the unauthenticated request, simplified
POST /v1/postgres/recovery/backup HTTP/1.1
Host: victim:8000
Authorization: Basic <anything-or-empty>

{
  "database": "...",
  "backupFile": "../../../../opt/splunk/etc/.../target_path"
}

At this stage the primitive is exactly what the advisory describes: arbitrary file creation and truncation. An attacker can create new files or zero out existing ones anywhere the Splunk user can write. That alone is enough to corrupt the internal database, disable components, or wipe state, which is why integrity and availability are both rated High. But file creation by itself is not yet code execution, and the interesting part of watchTowr's work is the escalation.

Root cause and escalation: connection strings and the .pgpass trick

The escalation comes from two backend behaviors the attacker can influence.

First, the database parameter is not merely a name. PostgreSQL utilities accept connection strings, and by injecting a full connection string an attacker can override the default connection settings and force the helper to connect to a PostgreSQL server the attacker controls. Now the "restore" operation is reading content from an attacker-owned database, and that content gets written onto the Splunk filesystem. The attacker is no longer limited to creating empty or truncated files; they control the bytes.

Second, the restore path can authenticate to the internal PostgreSQL instance using credentials stored in a local .pgpass file. By leaning on those already-present credentials, the attacker can authenticate to the internal database and execute arbitrary SQL during the restore. Once arbitrary SQL is in play, PostgreSQL's large object export function, lo_export, becomes a clean file-write primitive:

-- Write attacker-controlled bytes to an arbitrary path as the splunk user
SELECT lo_export(<large_object_oid>, '/opt/splunk/.../legitimate_script.py');

lo_export extracts a binary large object from the database and writes it to a file on the host running the database backend. Combined with the connection-string override, the attacker has staged their payload as a large object in a database they control, then asked the trusted helper to export it to a precise location on the Splunk host. Full, controlled, arbitrary file write under the Splunk service account.

Exploitation: overwrite a script Splunk already runs

With a controlled file write as the Splunk user, reaching code execution is mechanical. In the proof of concept, the researchers overwrote a legitimate Splunk Python script that the platform executes during normal operation. The next time Splunk invoked that script, it ran the attacker's code instead, confirming command execution on the target.

The full chain, end to end:

  1. Reach /v1/postgres/recovery/... through the public web service on port 8000, no credentials needed.
  2. Use the unauthenticated backup or restore operation to obtain a file-write primitive.
  3. Inject a PostgreSQL connection string to point the helper at an attacker-controlled database, and use .pgpass credentials to run arbitrary SQL during restore.
  4. Use lo_export to write attacker-controlled bytes to a chosen path on the Splunk host.
  5. Overwrite a Splunk-owned script that gets executed routinely, and wait for it to run.

No memory corruption, no exotic gadgets, no authentication. Every step abuses a documented, intended capability of the components involved. The vulnerability is in how those capabilities connect across a trust boundary, not in any single function being buggy.

Affected versions

Operationally, the dividing line is not just the version but the deployment: an AWS-hosted Splunk Enterprise 10.x instance ships with the sidecar enabled, while a self-managed Windows install may not have it running at all. Verify, do not assume.

Timeline

Date Event
2026-06-10 Splunk publishes advisory SVD-2026-0603, assigning CVE-2026-20253 at CVSS 9.8
2026-06-11 Independent rapid-response coverage of the advisory batch is published
2026-06-12 Splunk updates the advisory with additional affected-version detail
2026-06-13 watchTowr Labs publishes the full RCE chain and a working proof of concept

A note on the discovery methodology

watchTowr's analysis is a clean example of attacker-style reasoning applied to an architecture rather than a single function. The starting observation was not "this function has a bug" but "this service trusts the network instead of the request, and something else makes the network reachable." From there the work was about composition: which parameter is actually a connection string, what credentials are already lying around on disk, which PostgreSQL feature turns SQL into a file write, and which file, if overwritten, gets executed by the host on its own schedule. None of those steps required a zero-day in PostgreSQL or in Python. The meta-lesson for the AppSec community is that the highest-impact findings increasingly come from reading how trusted components are wired together, not from fuzzing one of them in isolation.


What we should learn from CVE-2026-20253

  1. "Bound to localhost" is not an authentication control. It is a network assumption, and proxies, service meshes, and convenience routes break it constantly. Any service that skips auth because it is local must re-check that assumption every time a new path can reach it.
  2. A trust boundary described differently by two components is a vulnerability waiting to happen. The proxy thought it was forwarding to a trusted internal service; the internal service thought only trusted callers could reach it. Each was reasonable in isolation, and together they were exploitable.
  3. File write is rarely "just" file write. Given an arbitrary write as a service account, an attacker looks for something the system already executes: a script, a config that triggers a reload, a scheduled task. Treat arbitrary write to a privileged account's paths as equivalent to code execution.
  4. Credentials at rest become attacker credentials. A .pgpass file that lets a helper authenticate to a local database also lets an attacker who reaches that helper authenticate to it. Secrets sitting on disk are part of the attack surface, not a mitigation.
  5. Default-on, internet-adjacent, and high-privilege is the worst trio. A component that is enabled by default, reachable from the edge, and runs as your most trusted account is exactly the thing to audit first, because every one of those properties multiplies the others.

How Pragma Core addresses this class of problem

Pragma Core is built for exactly this kind of issue: the bug is not a single bad line, it is the relationship between a proxy, an internal service, an on-disk credential, and a database feature. Classical scanners look at functions; this vulnerability lives in how those functions connect across a trust boundary. The platform connects to your repositories and runs continuous scanning, research, and pentesting with that cross-component view in mind.

Interactive call graphs with vulnerability overlay

Pragma Core auto-generates call graphs for any connected repository and shows how a public route reaches an internal handler. For a Splunk-shaped stack the graph would make the dangerous edge visible: the web tier on the network forwarding to a recovery handler that performs no credential check. The flaw was in that edge, not in either node, and the edge is precisely what a call-graph overlay surfaces and what a per-file scan misses.

Autonomous AI agents for attack chain investigation

The autonomous agents reason over chains rather than stopping at one flagged line. Asked of this codebase, the agent's questions write themselves: which external paths reach the loopback-bound service, is the authorization header actually validated or merely accepted, can the database parameter carry a connection string, and what on disk can be written and then executed. Those are the questions that took a human researcher from "limited file write" to pre-auth RCE, asked systematically across every repository instead of one at a time.

SAST tuned for the relevant pattern, not just injection sinks

Off-the-shelf static analysis is tuned for taint flows that end in SQL or shell sinks and would shrug at a backup endpoint that ignores its auth header. Pragma Core's analysis can be tuned to the pattern at hand: a request handler reachable from a public proxy that performs a privileged file or database operation without a verified identity. That is a high-confidence finding when the rule is written for missing-authentication-on-critical-function, CWE-306, rather than for classic injection.

Continuous tracking of third-party packages

The PostgreSQL sidecar bundles real PostgreSQL utilities, and the same June batch shipped upgrades to bundled Postgres, etcd, and log4j components. Pragma Core tracks every third-party package and embedded service across connected repositories, including the ones buried inside an appliance image, and surfaces vulnerable versions with fixed upgrade paths. The moment a CVE lands against a component you ship inside a sidecar, you are told, instead of finding out from someone else's blog.

Human-guided AppSec investigations

The expert-led research module lets an AppSec operator drive exactly the investigation watchTowr ran here, supported by autonomous agents and the platform context already in your workspace, and pointed at the part of your own stack that feels fragile. The difference is that it runs as a continuous extension of your subscription against your code, not as a one-off external engagement against someone else's product after the fact.


Closing thoughts

CVE-2026-20253 is not, fundamentally, a bug about PostgreSQL or about Splunk. It is a bug about a trusted internal service that assumed the network would protect it, sitting behind a proxy that quietly revoked that protection. The same pattern lives in most modern architectures: an admin API bound to loopback behind a reverse proxy, a sidecar in a service mesh, a metadata endpoint reachable through a request-forwarding bug. The components are individually defensible and collectively exploitable, and no single-function scanner will ever tell you that, because the defect is in the wiring.

The difference between treating this writeup as a curiosity and using it as an audit trigger comes down to AppSec maturity and code visibility. Organizations that want to move from "we scan and report" to "we systematically investigate what is fragile" can reach Pragma Core at pragma-core.com for a demo.


Sources

Related posts
CVE-2026-74820: how an unsanitized ORDER BY clause turned ServiceNow's AI Platform into an unauthenticated database backdoor
Sep 18, 2026
CVE-2026-85978: how one path normalization mismatch turned Akana's admin console into unauthenticated remote code execution
Sep 9, 2026
CVE-2026-78174: how an unredacted session token in a diagnostic log turned a low-privileged WatchGuard Dimension admin into super admin
Sep 1, 2026

Start securing your codebase today

Connect your repositories and let AI agents handle continuous scanning, research, and triage.

Have questions? Get in touch →