-
Notifications
You must be signed in to change notification settings - Fork 613
Description
Link to Rule
Rule Tuning Type
False Negatives - Enhancing detection of true threats that were previously missed.
Description
The detection logic will not work for the authentication success count (count_success)
from logs-system.auth-* | where @timestamp > now() - 7 day | where host.os.type == "linux" and event.category == "authentication" and event.action in ("ssh_login", "user_login") and **event.outcome == "failure"** and source.ip IS NOT null and not CIDR_MATCH(source.ip, "127.0.0.0/8", "169.254.0.0/16", "224.0.0.0/4", "::1") | eval failed = case(event.outcome == "failure", source.ip, null), **success = case(event.outcome == "success", source.ip, null)** | stats count_failed = count(failed), count_success = count(success), count_user = count_distinct(user.name) by source.ip /* below threshold should be adjusted to your env logon patterns */ | where count_failed >= 100 and count_success <= 10 and count_user >= 20
If you look at these two lines:
| where host.os.type == "linux" and event.category == "authentication" and event.action in ("ssh_login", "user_login") and event.outcome == "failure" and source.ip IS NOT null and not CIDR_MATCH(source.ip, "127.0.0.0/8", "169.254.0.0/16", "224.0.0.0/4", "::1") | eval failed = case(event.outcome == "failure", source.ip, null), success = case(event.outcome == "success", source.ip, null)
The first WHERE clause specifically says: and event.outcome == "failure"
but the second EVAL is doing a case condition to count how many event.outcome == successes there would be when they would never exist with the filter looking only for failure logs.
The other possible issue is that event.category can be an ARRAY and doing a MV string match against an array is not currently supported in ES|QL. At least in my labs, I'm seeing the event.category == authentication also coupled with a session log, so "event.category: [authentication, success]" then the subsequent NIX logs are session only event category. I'm not sure if its just me on Debian 11 and 12 that experiences this or others. My fix is the following by starting with MV_EXPAND:
from logs-system.auth-* | MV_EXPAND event.category | where @timestamp > now() - 7 day | where host.os.type == "linux" and event.category == "authentication" and event.action in ("ssh_login", "user_login") and event.outcome IN ("failure", "success") and source.ip IS NOT null and not CIDR_MATCH(source.ip, "127.0.0.0/8", "169.254.0.0/16", "224.0.0.0/4", "::1") | eval failed = case(event.outcome == "failure", source.ip, null), success = case(event.outcome == "success", source.ip, null) | stats count_failed = count(failed), count_success = count(success), count_user = count_distinct(user.name) by source.ip /* below threshold should be adjusted to your env logon patterns */ | where count_failed >= 100 and count_success <= 10 and count_user >= 20
Hope that makes sense! Also let me know if you're seeing the same behavior with the array for event.category always having 2 entries for the authentication log or if its just me.
Example Data
from logs-system.auth-* | MV_EXPAND event.category | where @timestamp > now() - 7 day | where host.os.type == "linux" and event.category == "authentication" and event.action in ("ssh_login", "user_login") and event.outcome IN ("failure", "success") and source.ip IS NOT null and not CIDR_MATCH(source.ip, "127.0.0.0/8", "169.254.0.0/16", "224.0.0.0/4", "::1") | eval failed = case(event.outcome == "failure", source.ip, null), success = case(event.outcome == "success", source.ip, null) | stats count_failed = count(failed), count_success = count(success), count_user = count_distinct(user.name) by source.ip /* below threshold should be adjusted to your env logon patterns */ | where count_failed >= 100 and count_success <= 10 and count_user >= 20

