A deployable Splunk app for SSH authentication monitoring: parsing, CIM
field mapping, scheduled correlation searches, dashboards, and live
threat-intel feeds. Built to run against real sshd logs on real Splunk
(Either Splunk Enterprise 9.x / 10.x or Splunk Cloud with app upload).
| Detection | Signal | MITRE ATT&CK |
|---|---|---|
| Brute force | 10+ failed logins / source IP / 5 min | T1110 |
| Distributed brute force | 8+ source IPs against one account | T1110.003 |
| Credential stuffing | 15+ usernames from one source in 2 min | T1110.004 |
| Successful brute force | Accepted login after repeated failures | T1078 / T1110 |
| Impossible travel | Two logins too far apart in the elapsed time | T1078 |
| Off-hours login | Successful logins before 06:00 / after 22:00 | T1078 |
| Dormant account login | Account silent 7 days, login in last 15 min | T1078.002 |
| Threat-intel match | Source IP on Tor / abuse.ch C2 / Spamhaus DROP | T1078 / T1090 |
| Privileged login | Direct login to root / admin / svc_* |
T1078.003 |
bin/update_threat_intel.py pulls real, public feeds straight into the
app's lookups:
| Feed | Source | Lookup |
|---|---|---|
| Tor bulk exit list | check.torproject.org |
lookups/tor_exit_nodes.csv |
| Feodo Tracker C2 | feodotracker.abuse.ch |
lookups/c2_blocklist.csv |
| SSLBL C2 (TLS) | sslbl.abuse.ch |
lookups/c2_blocklist.csv |
| Spamhaus DROP | spamhaus.org/drop |
lookups/spamhaus_drop.csv |
The lookup files ship with a real snapshot (taken at commit time) and are
refreshed by running the script — see Operations. Spamhaus
ranges are matched with match_type = CIDR(src_ip); the IP feeds match exactly.
SSHLogAnalysis/
├── .gitattributes # forces LF so Splunk conf is never CRLF on clone
├── architecture.jpg # diagram shown above
├── default/
│ ├── app.conf # app manifest (id: SSHLogAnalysis)
│ ├── inputs.conf # monitor auth.log / secure on the forwarder
│ ├── props.conf # linux_secure parsing + CIM field mapping
│ ├── transforms.conf # field extractions + lookup definitions
│ ├── eventtypes.conf # ssh_login_success / failure / invalid user ...
│ ├── tags.conf # action::success|failure, app::ssh (CIM)
│ ├── macros.conf # base searches + enrichment macros
│ ├── savedsearches.conf # 9 scheduled correlation searches
│ └── data/ui/views/ # ssh-threat-dashboard.xml, ssh-monitoring-dashboard.xml
├── bin/
│ └── update_threat_intel.py # refresh the live feeds (Splunk scripted input)
├── lookups/ # CSV lookups (feeds + allowlist + watchlist)
├── metadata/default.meta # object permissions
├── spl-queries/ # ad-hoc SPL triage library (.spl)
├── sample-data/ # ALL sample logs live here:
│ ├── auth.log.sample # checked-in fixture (documentation-range IPs)
│ ├── secure.log.sample # RHEL/CentOS flavoured fixture
│ └── generated/ # bulk fixtures, gitignored (tools/generate-sample-logs.py)
├── tools/ # developer tooling (not shipped to Splunk)
│ ├── generate-sample-logs.py # generate bulk test logs
│ └── validate_repo.py # 15 static deployment checks
└── docs/
Deploy as an app so every knowledge object (macros, lookups, event types, searches, views) loads together — no manual copy/paste:
# 1. Put the app in place (folder name MUST be SSHLogAnalysis)
git clone https://github.com/Tamerktb/SSH-Log-Analysis-using-Splunk.git
mv SSH-Log-Analysis-using-Splunk $SPLUNK_HOME/etc/apps/SSHLogAnalysis
# 2. Refresh the live threat-intel feeds
python $SPLUNK_HOME/etc/apps/SSHLogAnalysis/bin/update_threat_intel.py \
--out $SPLUNK_HOME/etc/apps/SSHLogAnalysis/lookups
# 3. Restart (or Debug/Refresh) to load the app
$SPLUNK_HOME/bin/splunk restartThen, on the forwarder, take default/inputs.conf to monitor
/var/log/auth.log (Debian/Ubuntu) and /var/log/secure (RHEL family). The
index linux_secure is referenced by the `ssh_events` macro — change it
in one place (default/macros.conf) if your index differs.
If the official Splunk Add-on for Unix and Linux (
Splunk_TA_nix) is installed, it handles base parsing forsourcetype=linux_secure; this app's CIM mapping and detections layer on top of it.
Events are tagged for the Authentication data model (action::success,
action::failure, app::ssh) and the app maps CIM fields — src, user,
dest, action, app, vendor_product — so tstats/data-model searches and
ES work without extra config:
| tstats count from datamodel=Authentication.Authentication
where Authentication.action=failure by Authentication.src, Authentication.user
| sort - count
Refresh feeds (recommended: daily). Add a cron entry or a Splunk scripted input:
python bin/update_threat_intel.py # all feeds
python bin/update_threat_intel.py --only tor,c2
python bin/update_threat_intel.py --dry-run # fetch + count, do not writeTune false positives. Add internal / VPN / CI sources to
lookups/ssh_allowlist.csv (src_ip,allow_reason,owner,added); the
`ssh_allowlist_filter` macro drops them from the alerts.
Internal watchlist. lookups/known_attackers.csv is an analyst-maintained
(src_ip,threat_type,confidence,source) list, intentionally empty — add your
own known-bad actors; the `ssh_watchlist_enrich` macro enriches with it.
Alert routing. Point action.email.to at your on-call alias (replace
soc@example.com) and set action.email = 1. Thresholds live next to each
search in default/savedsearches.conf.
python tools/validate_repo.py # 15 deployment checks -> "all checks PASSED"
python bin/update_threat_intel.py --dry-runIn Splunk, after onboarding logs:
-
index=linux_secure sourcetype=linux_secure | stats countreturns events -
| tstats count from datamodel=Authentication.Authenticationis non-zero -
`ssh_events` | stats countexpands without error - Threat + monitoring dashboards render (map, tables, impossible travel)
-
SSH - Threat Intel Matchfires against a Tor exit IP from the feed - No
lookuperrors in the job status bar
To exercise every rule without a live fleet, generate and onboard the bulk fixtures:
python tools/generate-sample-logs.py # 24h of realistic auth.log + secure.log
splunk add oneshot sample-data/generated/auth.log \
-index linux_secure -sourcetype linux_secureThe generator emits UTC timestamps (matching the app's TZ = UTC) and
includes a pattern for every rule — brute force, credential stuffing,
distributed brute force, successful brute force, impossible travel, off-hours,
dormant account, privileged login and threat-intel hits — so a fresh batch
fires all 9 saved searches inside their scheduled windows.
spl-queries/*.spl holds ready-to-paste searches for manual investigation
(brute force, credential stuffing, geo/threat-intel, user behaviour, anomaly
detection, CIM via tstats). They use built-in iplocation for geo — no
external .mmdb upload and no geoip lookup required.
MIT — see LICENSE.
