Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Rule Tuning] login_activity_by_source_address.toml #4287

Open
farbod-sec opened this issue Dec 9, 2024 · 2 comments
Open

[Rule Tuning] login_activity_by_source_address.toml #4287

farbod-sec opened this issue Dec 9, 2024 · 2 comments
Assignees
Labels

Comments

@farbod-sec
Copy link

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

Image

Image

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

@farbod-sec farbod-sec added Rule: Tuning tweaking or tuning an existing rule Team: TRADE labels Dec 9, 2024
@farbod-sec
Copy link
Author

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)

@Aegrah
Copy link
Contributor

Aegrah commented Dec 10, 2024

Hello @farbod-sec, thank you for the issue! I assigned it and will take a closer look later. Will keep you posted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants