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?
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
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.
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.
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?
@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?
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