Detection rules › Splunk
SSH Brute Force detection
Detect possible brute force attempts using SSH in Unix
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
References
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Linux | auditd USER_AUTH record: User system access authentication |
Rule body
id: '5453.5608'
title: SSH Brute Force detection
description: 'Detect possible brute force attempts using SSH in Unix. - Threat Actor
Association: 8220 Gang, Daixin Team, Lancefly, Sandworm (UAC-0165)'
logic_format: Splunk
logic: '`get_endpoint_data` `get_endpoint_data_unix` TERM(ssh) TERM(res=failed) TERM(type=USER_AUTH)
| rex field=_raw "[^\w]"(?<process_name>"(ssh)")"[^\w]"| eval src_ip=mvfilter( NOT
match(addr,"\?")), user=mvappend("",acct,user,auid,uid), user=mvfilter(NOT match(user,"\?|\(unknown\)|\d+|unset|^\s$|^$")),
user_id=mvappend("",auid,uid), user_id=mvfilter(match(user_id,"\d+")) | table _time,
host, user process, process_*, res, src_ip, user_id | bin _time span=10sec | eventstats
count by host, _time, res | stats values(*) as * by host, _time, res | where count
> 3 | bin _time span=10min | stats values(*) as * list(count) as count_list sum(count)
as count_span by host, _time, res | where mvcount(count_list) > 1 OR count_span
> 20 | rename count_span as count| lookup dnslookup clientip as src_ip OUTPUT clienthost
as src_dns | iplocation prefix="src_" src_ip | rename src_Country as src_country '
techniques:
- credential-access:brute force:password guessing
- credential-access:brute force:password spraying
technique_id:
- T1110.001
- T1110.003
data_category:
- Process command-line parameters
- Linux audit logs
references:
- https://linuxhint.com/bruteforce_ssh_ftp/
Stages and Predicates
Stage 1: search
`get_endpoint_data` `get_endpoint_data_unix` TERM(ssh) TERM(res=failed) TERM(type=USER_AUTH)
Stage 2: rex
| rex field=_raw "[^\w]"(?<process_name>"(ssh)")"[^\w]"
Stage 3: eval
| eval src_ip=mvfilter( NOT match(addr,"\?")), user=mvappend("",acct,user,auid,uid), user=mvfilter(NOT match(user,"\?|\(unknown\)|\d+|unset|^\s$|^$")), user_id=mvappend("",auid,uid), user_id=mvfilter(match(user_id,"\d+"))
Stage 4: table
| table _time, host, user process, process_*, res, src_ip, user_id
Stage 5: bucket
| bin _time span=10sec
Stage 6: eventstats
| eventstats count by host, _time, res
Stage 7: stats
| stats values(*) as * by host, _time, res
Stage 8: where
| where count > 3
Stage 9: bucket
| bin _time span=10min
Stage 10: stats
| stats values(*) as * list(count) as count_list sum(count) as count_span by host, _time, res
Stage 11: where
| where mvcount(count_list) > 1 OR count_span > 20
Stage 12: rename
| rename count_span as count
Stage 13: lookup
| lookup dnslookup clientip as src_ip OUTPUT clienthost as src_dns
Stage 14: search
| iplocation prefix="src_" src_ip
Stage 15: rename
| rename src_Country as src_country
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
count | gt |
| field:"count" kind:gt value:"3" |
count_span | gt |
| field:"count_span" kind:gt value:"20" |
prefix | eq |
| field:"prefix" kind:eq |
Search terms
These SPL tokens match against raw event text.
| Stage | Term |
|---|---|
| 1 | ssh |
| 1 | "res=failed" |
| 1 | "type=USER_AUTH" |
| 14 | iplocation |
| 14 | src_ip |