PoC Archive PoC Archive
Critical CVE-2026-20253 patched

Splunk Enterprise Pre-Auth RCE via PostgreSQL Sidecar (CVE-2026-20253)

by Piotr (@chudyPB) / watchTowr · 2026-06-28

Severity
Critical
CVE
CVE-2026-20253
Category
web
Affected product
Splunk Enterprise
Affected versions
10.0.0–10.0.6, 10.2.0–10.2.3 (NOT 9.x)
Disclosed
2026-06-28
Patch status
patched

Metadata

FieldValue
Date Added2026-06-28
Last Updated2026-06-12
Author / ResearcherPiotr (@chudyPB) / watchTowr
CVE / AdvisoryCVE-2026-20253
Categoryweb
SeverityCritical
CVSS ScoreN/A (Critical, pre-auth RCE chain; see vendor advisory SVD-2026-0603)
StatusPoC
Tagspre-auth, RCE, PostgreSQL, Splunk, CISA-KEV, lo-export, sidecar, unauthenticated, file-write
RelatedN/A

Affected Target

FieldValue
Software / SystemSplunk Enterprise
Versions Affected10.0.0–10.0.6, 10.2.0–10.2.3 (NOT 9.x)
Language / PlatformPython (PoC); Linux (target)
Authentication RequiredNo (unauthenticated)
Network Access RequiredYes (HTTP/HTTPS, default port 8000)

Summary

CVE-2026-20253 is a critical unauthenticated RCE vulnerability in Splunk Enterprise arising from a missing authentication check on the PostgreSQL sidecar service endpoint /v1/postgres/recovery/backup. An unauthenticated attacker can reach this endpoint and exploit the PostgreSQL lo_export function to write arbitrary files to the OS, which can be chained to achieve remote code execution. A watchTowr PoC was published; exploitation spiked shortly after. CISA added to KEV on June 18, 2026 with a federal remediation deadline of June 21.


Vulnerability Details

Root Cause

The PostgreSQL sidecar service bundled with Splunk Enterprise exposes an unauthenticated HTTP endpoint at /v1/postgres/recovery/backup. This endpoint allows interaction with the PostgreSQL process without credentials, enabling the lo_export function to write attacker-controlled data to arbitrary filesystem paths. Written content can include a Splunk scripted input or SOAR playbook that triggers RCE when the Splunk process loads it.

Attack Vector

  1. Send unauthenticated HTTP request to http://TARGET:8000/<region>/v1/postgres/recovery/backup.
  2. Exploit lo_export to write a malicious file to a Splunk-loaded path.
  3. Trigger Splunk to load the file → RCE as the Splunk service user.

Impact

Unauthenticated remote code execution as the Splunk service account. Splunk typically runs with broad filesystem access and may have credentials for integrated data sources (cloud APIs, databases, SIEMs).


Environment / Lab Setup

Target:   Splunk Enterprise 10.0.0–10.0.6 or 10.2.0–10.2.3, Linux
Attacker: Python 3 with requests

Proof of Concept

Detection Script

See watchTowr-vs-Splunk-CVE-2026-20253.py in this folder. Detection only — checks endpoint accessibility, does not exploit.

1
2
3
python3 watchTowr-vs-Splunk-CVE-2026-20253.py \
  -H http://TARGET:8000 \
  -r en-US

Expected Output (vulnerable)

[+] VULNERABLE - access to /v1/postgres/recovery/backup not blocked

Expected Output (patched)

[-] NOT VULNERABLE - access to /v1/postgres/recovery/backup blocked

Detection & Indicators of Compromise

1
2
3
4
5
grep "postgres/recovery/backup" /opt/splunk/var/log/splunk/web_access.log

find /opt/splunk/etc/apps -newer /opt/splunk/etc/apps/search -name "*.py" -o -name "*.sh"

grep "ExecProcessor" /opt/splunk/var/log/splunk/splunkd.log

Remediation

ActionDetail
PatchUpgrade to Splunk Enterprise 10.4.0, 10.2.4, or 10.0.7
Verifysplunk version; confirm endpoint returns 401 post-patch
Vendor advisoryhttps://advisory.splunk.com/advisories/SVD-2026-0603

References


Notes

Auto-ingested from https://github.com/watchtowrlabs/watchTowr-vs-Splunk-CVE-2026-20253 on 2026-06-28. First Splunk CVE ever added to CISA KEV (June 18, 2026; federal deadline June 21). The included script is a detection artifact — the full lo_export → RCE chain is documented in the watchTowr blog post but not included here. Older Splunk 9.x builds do not ship the vulnerable PostgreSQL sidecar and are not affected.

watchTowr-vs-Splunk-CVE-2026-20253.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import argparse
import urllib3

import requests

banner = """			 __         ___  ___________                   
	 __  _  ______ _/  |__ ____ |  |_\\__    ____\\____  _  ________ 
	 \\ \\/ \\/ \\__  \\    ___/ ___\\|  |  \\|    | /  _ \\ \\/ \\/ \\_  __ \\
	  \\     / / __ \\|  | \\  \\___|   Y  |    |(  <_> \\     / |  | \\/
	   \\/\\_/ (____  |__|  \\___  |___|__|__  | \\__  / \\/\\_/  |__|   
				  \\/          \\/     \\/                            
	  
        watchTowr-vs-Splunk-CVE-2026-20253.py
        (*) CVE-2026-20253 Splunk PostgreSQL Sidecar Service Detection Artifact Generator

          - Piotr (@chudyPB) of watchTowr (@watchTowrcyber)
"""

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

def dag(host, region):

    url = f"{host}{region}/splunkd/__raw/v1/postgres/recovery/backup"
    headers = {"Authorization":"Basic ZGFnOg=="}

    resp = requests.post(url, headers=headers, verify = False)

    if resp.status_code == 400 and 'Failed to decode' in resp.text:
        print('[+] VULNERABLE - access to /v1/postgres/recovery/backup not blocked')
    elif resp.status_code == 401:
        print('[-] NOT VULNERABLE - access to /v1/postgres/recovery/backup blocked')
    else:
        print('[+/-] UNKNOWN MESSAGE - please verify manually. PostgreSQL Sidecar Service may not be installed/enabled')


if __name__ == "__main__":

    print(banner)

    usage = """python3 poc.py [-h] -H HOST \r\n\r\n\
        For more help, use "python3 poc.py --help"
        INFO HERE
        """
    parser = argparse.ArgumentParser(description = 'CVE-2026-20253 Splunk PostgreSQL Sidecar Service Detection Artifact Generator', usage = usage)
    
    #required arg
    parser.add_argument('-H', dest = 'host', action = "store", type = str, help = 'Host, eg. "http://splunk.lab:8000"', required = True)
    parser.add_argument('-r', dest = 'region', action = "store", type = str, help = 'Region for your installation, eg. "en-US"', required = True)

    #get arguments
    args = parser.parse_args()
    host = args.host
    region = args.region

    if host[-1] != '/':
        host += '/'

    dag(host, region)