Splunk Search

Detect Brute Force auth success after multiple failures

gnoriega
Explorer

Hi,

I'm trying to detect brute force activity by detecting multiple auth failures followed by success.  I started with the following search which works and shows when there has been over 20 failures and at least 1 success, but the success can happen anywhere during the search period. It could be 1 success followed by 20 failures or the success can happen in the middle.

index=main sourcetype="wineventlog" (EventCode=4624 OR EventCode=4625) Logon_Type IN (2,3,8,10,11) user!=*$
  | bin _time span=5m as Time 
  | stats count(eval(match(Keywords,"Audit Failure"))) as Failed,
       count(eval(match(Keywords,"Audit Success"))) as Success,
       count(eval(match(lower(Status),"0xc0000224"))) as "PwChangeReq",
       count(eval(match(lower(Sub_Status),"0xc0000071"))) as "Expired",
       count(eval(match(lower(Status),"0xc0000234"))) as "Locked" by Time user src_ip
  | where Success>0 AND Failed>=20 AND PwChangeReq=0 AND Locked=0 AND Expired=0

 

I need the query to only trigger if the success happens after 20 failures. I found some examples using streamstats so I created the following search but it's not working properly because the *reset_after* clears the failure_count for all src_ip. Therefore as long as there is 1 success from any IP address, the failure_count gets reset and I'm not seeing the failure count reach 20.

 

index=main sourcetype="wineventlog" EventCode IN (4624,4625) Logon_Type IN (2,3,8,10,11) 
 | eval action=if(match(Keywords,"Audit Failure"),"failed","success")
 | reverse
 | streamstats window=0 current=true reset_after="("action==\"success\"")" count as failure_count by src_ip
 | where action="success" and failure_count > 20
 | table _time, user, src_ip, action, failure_count

 

Is streamstats the way to go? Or how can I setup a query to detect the success after more than 20 failures?

0 Karma
1 Solution

gnoriega
Explorer

I found the solution. I had to replace line:

| reverse

with:

| sort src_ip _time

So that streamstats resets the counter each time the action is "success" for each src_ip.

Working code:

index=main sourcetype="wineventlog" EventCode IN (4624,4625) Logon_Type IN (2,3,8,10,11) 
 | eval action=if(match(Keywords,"Audit Failure"),"failed","success")
 | sort src_ip _time
 | streamstats window=0 current=true reset_after="("action==\"success\"")" count as failure_count by src_ip
 | where action="success" and failure_count > 20
 | table _time, user, src_ip, action, failure_count

 

View solution in original post

0 Karma

to4kawa
Ultra Champion

sample:

 

| makeresults count=100000
| eval status="failed", count=1
| eval status = if( count = random() % 20,"success",status), user=mvindex(split("testA,testB,testC",","),random() % 3)
| eval src_ip=mvindex(split("X.X.X.X,Y.Y.Y.Y",","),random() % 2)
| accum count
| eval _time=_time - count 
| sort 0 _time
| streamstats global=f count(eval(status="failed")) as Failed by user src_ip reset_before="status=\"success\""
| table _time src_ip user status Failed
| where Failed > 20

 

try streamstats global=f ...
The data is randomly generated, so the results may not be available.

0 Karma

gnoriega
Explorer

I tried using global=f but I get the same behavior. Once a success is seen and the stats reset they are reset for all src_ip.

0 Karma

to4kawa
Ultra Champion

sample:

| makeresults count=10000
| eval status="failed", count=1
| accum count
| eval status = if( count % 10 = 1,"success",status), user=mvindex(split("testA,testB,testC",","),random() % 3)
| eval src_ip=mvindex(split("X.X.X.X,Y.Y.Y.Y",","),random() % 2)
| eval _time=_time - count 
| sort 0 _time
| streamstats global=f count(eval(status="success")) as session by user src_ip
| streamstats count(eval(status="failed")) as failed_count by session user src_ip
| table _time src_ip user status failed_count
| sort user src_ip _time

how about this?

0 Karma

gnoriega
Explorer

@to4kawa thanks but I'm still not getting the behavior I need. I do see that it properly keeps count of each "session" and only resets the failure_count when there is a success for that user,src_ip which is better.

The issue I have is that I need to filter when there is a success and the failed count had reached over a certain amount. So if for example my threshold is 15, in the screenshot below I see that the success occurred after 19 failures which is what I need to detect. But I don't have a way to detect it.

With the previous "reset_after" command, the row with the success had the failed_count total, so I could do:

| where failed_count>15 AND status="success"

 

Would there be a way for the row with the status="success" to have the failed_count reached so far before resetting?

streamstats1.PNG

0 Karma

gnoriega
Explorer

I found the solution. I had to replace line:

| reverse

with:

| sort src_ip _time

So that streamstats resets the counter each time the action is "success" for each src_ip.

Working code:

index=main sourcetype="wineventlog" EventCode IN (4624,4625) Logon_Type IN (2,3,8,10,11) 
 | eval action=if(match(Keywords,"Audit Failure"),"failed","success")
 | sort src_ip _time
 | streamstats window=0 current=true reset_after="("action==\"success\"")" count as failure_count by src_ip
 | where action="success" and failure_count > 20
 | table _time, user, src_ip, action, failure_count

 

0 Karma
Get Updates on the Splunk Community!

Splunk Decoded: Service Maps vs Service Analyzer Tree View vs Flow Maps

It’s Monday morning, and your phone is buzzing with alert escalations – your customer-facing portal is running ...

What’s New in Splunk Observability – September 2025

What's NewWe are excited to announce the latest enhancements to Splunk Observability, designed to help ITOps ...

Fun with Regular Expression - multiples of nine

Fun with Regular Expression - multiples of nineThis challenge was first posted on Slack #regex channel ...