You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
The text was updated successfully, but these errors were encountered:
Actually one last thing, I would probably also probably change this line: and count_success <= 10 at the very bottom.
It is possible that a sysadmins use the same login credentials for multiple boxes and if a brute force is successful on multiple boxes, it would not show up on this. I remove the definitive | WHERE clause and included 3 commented out | WHERE clauses with information on why those could be interesting to run independently. Thoughts?
| STATS count_failed = count(failed), count_success = count(success), count_user = count_distinct(user.name) by source.ip //| WHERE count_failed >= 20 and count_user >= 10 // Look for >= 20 login fails with 10 unique users. Sometimes bots will scan over and over again with the same username. //| WHERE count_failed IS NOT NULL and count_success > 20 // Look for greater than 20 login fails with at least 1 successul login. Very suspicious behavior... //| LIMIT 10000 // Show me the maximum results (if 1,000 returned)
Link to Rule
https://github.com/elastic/detection-rules/blob/main/hunting/linux/queries/login_activity_by_source_address.toml
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
The text was updated successfully, but these errors were encountered: