Sure. Anytime you have an outcome variable in a search or rule with a match section, the events are aggregated or grouped. Because of that fields in the outcome section must also be aggregated. Risk scores that are derived from fields must also be aggregated. A function like max, count, count_distinct, array, array_distinct and many more are good ways to aggregate the value.
Sure. Anytime you have an outcome variable in a search or rule with a match section, the events are aggregated or grouped. Because of that fields in the outcome section must also be aggregated. Risk scores that are derived from fields must also be aggregated. A function like max, count, count_distinct, array, array_distinct and many more are good ways to aggregate the value.
So I just want to check if the parameters
$e1.principal.ip_geo_artifact.location.country_or_region and $e2.principal.ip_geo_artifact.location.country_or_region nocase are equal or not and assign risk score on the basis of that. how to do that?
Sorry for the overly quick response I saw the UDM fields on my phone and didn't see the rest. OK, so the guidance about needing an aggregation function for the UDM fields still stands so we do need to encapsulate that portion of the risk calculation with the max function. However, the remainder of the mathematical operation was already aggregated in the previous statement to calculate the distance, so we can then just use mathematical operations with the conditional statement, like below. I think this should work.
$risk_score = max(
if($e1.principal.ip_geo_artifact.location.country_or_region = $e2.principal.ip_geo_artifact.location.country_or_region nocase, 90)) +
if($distance_kilometers > 100 and $distance_kilometers <= 500, 20) +
if($distance_kilometers > 500 and $distance_kilometers <= 1000, 30) +
if($distance_kilometers > 1000, 50)
Thanks for the quick support
As per my understanding,
$risk_score = max(
if($e1.principal.ip_geo_artifact.location.country_or_region = $e2.principal.ip_geo_artifact.location.country_or_region nocase, 90)) +
if($distance_kilometers > 100 and $distance_kilometers <= 500, 20) +
if($distance_kilometers > 500 and $distance_kilometers <= 1000, 30) +
if($distance_kilometers > 1000, 50)
This piece of code will assign the risk score as 90 if both the locations are same. and if not then 20, 30 or 50 based on the condition.
correct me if I am wrong.
That is how I interpreted it but you may want a nocase on both events to ensure those values are the same. Actually a strings.to_upper or strings.to_lower would be better for a comparison but tune it as you see fit. If the first if is not met, you are assigning a 0 and because the next 3 values that can be summed together do not cover a distance of less than 100, you are opening yourself to having a 0 value in there as well.
yes I don't want the event to trigger for a distance of less than 100 km
rule impossible_travel_activity_piramal{
meta:
author = "Anurag Singh"
description = "Detects potential account compromise by identifying logon attempts from two different geo locations within a short span of time, indicating impossible travel between the locations."
severity = "High"
events:
$e1.metadata.event_type = "USER_LOGIN"
$e1.metadata.product_event_type = "UserLoggedIn"
$user = $e1.target.user.userid
$e1_lat = $e1.principal.location.region_coordinates.latitude
$e1_long = $e1.principal.location.region_coordinates.longitude
$location1 = $e1.principal.ip_geo_artifact.location.country_or_region
$e2.metadata.event_type = "USER_LOGIN"
$e2.metadata.product_event_type = "UserLoggedIn"
// match variables
$user = $e2.target.user.userid
$e2_lat = $e2.principal.location.region_coordinates.latitude
$e2_long = $e2.principal.location.region_coordinates.longitude
$location2 = $e2.principal.ip_geo_artifact.location.country_or_region
match:
$user over 1h
outcome:
$distance_kilometers = math.ceil(
max(math.geo_distance($e1_long, $e1_lat, $e2_long,$e2_lat)) / 1000
)
$risk_score = max(
if(strings.to_lower($location1) != strings.to_lower($location2), 90)) +
if($distance_kilometers > 100 and $distance_kilometers <= 500, 20) +
if($distance_kilometers > 500 and $distance_kilometers <= 1000, 30) +
if($distance_kilometers > 1000, 50)
condition:
$e1 and $e2 and $risk_score >= 20
}
This is my updated code.
Any suggestions are welcomed.
Thanks for your help.
On the surface it looks generally fine, are you getting results or do you have any issues? My one comment is that as I look at the risk score that first statement location 1 and 2 not being the same (different country/region) could be less than 100 km away and you still end up with a risk score of 90 due to that inequality.
So, in my case, if I am in the US and cross the bridge to Canada and login, this rule will trigger with a risk of 90 even though I am under 100km. Similar issue in the EU for instance. Again, it's not a bad thing but I do want to mention that as you test and refine.
yes I don't want the event to trigger for a distance of less than 100 km
rule impossible_travel_activity_piramal{
meta:
author = "Anurag Singh"
description = "Detects potential account compromise by identifying logon attempts from two different geo locations within a short span of time, indicating impossible travel between the locations."
severity = "High"
events:
$e1.metadata.event_type = "USER_LOGIN"
$e1.metadata.product_event_type = "UserLoggedIn"
$user = $e1.target.user.userid
$e1_lat = $e1.principal.location.region_coordinates.latitude
$e1_long = $e1.principal.location.region_coordinates.longitude
$location1 = $e1.principal.ip_geo_artifact.location.country_or_region
$e2.metadata.event_type = "USER_LOGIN"
$e2.metadata.product_event_type = "UserLoggedIn"
// match variables
$user = $e2.target.user.userid
$e2_lat = $e2.principal.location.region_coordinates.latitude
$e2_long = $e2.principal.location.region_coordinates.longitude
$location2 = $e2.principal.ip_geo_artifact.location.country_or_region
match:
$user over 1h
outcome:
$distance_kilometers = math.ceil(
max(math.geo_distance($e1_long, $e1_lat, $e2_long,$e2_lat)) / 1000
)
$risk_score = max(
if(strings.to_lower($location1) != strings.to_lower($location2), 90)) +
if($distance_kilometers > 100 and $distance_kilometers <= 500, 20) +
if($distance_kilometers > 500 and $distance_kilometers <= 1000, 30) +
if($distance_kilometers > 1000, 50)
condition:
$e1 and $e2 and $risk_score >= 20
}
This is my updated code.
Any suggestions are welcomed.
Thanks for your help.
Hi @anurag.q.singh
How about the below
rule impossible_travel_activity_piramal{
meta:
author = "Anurag Singh"
description = "Detects potential account compromise by identifying logon attempts from two different geo locations within a short span of time, indicating impossible travel between the locations."
severity = "High"
events:
$e1.metadata.event_type = "USER_LOGIN"
$e1.metadata.product_event_type = "UserLoggedIn"
$user = $e1.target.user.userid
$e1_lat = $e1.principal.location.region_coordinates.latitude
$e1_long = $e1.principal.location.region_coordinates.longitude
$location1 = $e1.principal.ip_geo_artifact.location.country_or_region
$e2.metadata.event_type = "USER_LOGIN"
$e2.metadata.product_event_type = "UserLoggedIn"
// match variables
$user = $e2.target.user.userid
$e2_lat = $e2.principal.location.region_coordinates.latitude
$e2_long = $e2.principal.location.region_coordinates.longitude
$location2 = $e2.principal.ip_geo_artifact.location.country_or_region
match:
$user over 1h
outcome:
$distance_kilometers = math.ceil(
max(math.geo_distance($e1_long, $e1_lat, $e2_long,$e2_lat)) / 1000
)
$risk_score = max(
if(strings.to_lower($location1) != strings.to_lower($location2), 90)) +
if($distance_kilometers > 100 and $distance_kilometers <= 500, 20) +
if($distance_kilometers > 500 and $distance_kilometers <= 1000, 30) +
if($distance_kilometers > 1000, 50)
condition:
$e1 and $e2 and $risk_score >= 20 and not $distance_kilometers <= 100
}
Kind Regards,
Ayman Charkaui
rule impossible_travel_activity {
meta:
author = "Anurag Singh"
description = "Detects potential account compromise by identifying logon attempts from two different geo locations within a short span of time, indicating impossible travel between the locations."
severity = "High"
events:
$e1.metadata.event_type = "USER_LOGIN"
$e1.metadata.product_event_type = "UserLoggedIn"
$user = $e1.target.user.userid
$e1_lat = $e1.principal.location.region_coordinates.latitude
$e1_long = $e1.principal.location.region_coordinates.longitude
$location1 = $e1.principal.ip_geo_artifact.location.country_or_region
$e2.metadata.event_type = "USER_LOGIN"
$e2.metadata.product_event_type = "UserLoggedIn"
$user = $e2.target.user.userid
$e2_lat = $e2.principal.location.region_coordinates.latitude
$e2_long = $e2.principal.location.region_coordinates.longitude
$location2 = $e2.principal.ip_geo_artifact.location.country_or_region
match:
$user over 1h
outcome:
$distance_kilometers = math.ceil(
max(math.geo_distance($e1_long, $e1_lat, $e2_long,$e2_lat)) / 1000
)
$duration = cast.as_int(
min(
($e2.metadata.event_timestamp.seconds - $e1.metadata.event_timestamp.seconds)
/ 60
)
)
$risk_score = max(
if(strings.to_lower($location1) != strings.to_lower($location2), 90)) + // if logging in from different countries
if($distance_kilometers > 100 and $distance_kilometers <= 500, 20) +
if($distance_kilometers > 500 and $distance_kilometers <= 1000, 30) +
if($distance_kilometers > 1000, 50)
$impossibleTravelActivity_description = array_distinct(strings.concat("The user '", $user, "' logged in from two locations with a distance of '", $distance_kilometers, "' km within a duration of '", $duration , "minutes"))
condition:
$e1 and $e2 and $risk_score >= 20 and not $distance_kilometers <= 100
}
now the issue I an facing is that $impossibleTravelActivity_description is showing an error
validating intermediate representation: aggregation cannot refer to outcome variables or contain another aggregation
line: 48
column: 45-225
any suggestions?
@jstoner @AymanC
rule impossible_travel_activity {
meta:
author = "Anurag Singh"
description = "Detects potential account compromise by identifying logon attempts from two different geo locations within a short span of time, indicating impossible travel between the locations."
severity = "High"
events:
$e1.metadata.event_type = "USER_LOGIN"
$e1.metadata.product_event_type = "UserLoggedIn"
$user = $e1.target.user.userid
$e1_lat = $e1.principal.location.region_coordinates.latitude
$e1_long = $e1.principal.location.region_coordinates.longitude
$location1 = $e1.principal.ip_geo_artifact.location.country_or_region
$e2.metadata.event_type = "USER_LOGIN"
$e2.metadata.product_event_type = "UserLoggedIn"
$user = $e2.target.user.userid
$e2_lat = $e2.principal.location.region_coordinates.latitude
$e2_long = $e2.principal.location.region_coordinates.longitude
$location2 = $e2.principal.ip_geo_artifact.location.country_or_region
match:
$user over 1h
outcome:
$distance_kilometers = math.ceil(
max(math.geo_distance($e1_long, $e1_lat, $e2_long,$e2_lat)) / 1000
)
$duration = cast.as_int(
min(
($e2.metadata.event_timestamp.seconds - $e1.metadata.event_timestamp.seconds)
/ 60
)
)
$risk_score = max(
if(strings.to_lower($location1) != strings.to_lower($location2), 90)) + // if logging in from different countries
if($distance_kilometers > 100 and $distance_kilometers <= 500, 20) +
if($distance_kilometers > 500 and $distance_kilometers <= 1000, 30) +
if($distance_kilometers > 1000, 50)
$impossibleTravelActivity_description = array_distinct(strings.concat("The user '", $user, "' logged in from two locations with a distance of '", $distance_kilometers, "' km within a duration of '", $duration , "minutes"))
condition:
$e1 and $e2 and $risk_score >= 20 and not $distance_kilometers <= 100
}
now the issue I an facing is that $impossibleTravelActivity_description is showing an error
validating intermediate representation: aggregation cannot refer to outcome variables or contain another aggregation
line: 48
column: 45-225
any suggestions?
@jstoner @AymanC
Hi @anurag.q.singh
The reason why, is within your outcome variable 'impossibleTravelActivity_description, you're signifying a 'array_distinct', and then using the variables 'distance_kilometers', and 'duration' which ar using max and mins. However the below should satisfy your use case.
rule impossible_travel_activity {
meta:
author = "Anurag Singh"
description = "Detects potential account compromise by identifying logon attempts from two different geo locations within a short span of time, indicating impossible travel between the locations."
severity = "High"
events:
$e1.metadata.event_type = "USER_LOGIN"
$e1.metadata.product_event_type = "UserLoggedIn"
$user = $e1.target.user.userid
$e1_lat = $e1.principal.location.region_coordinates.latitude
$e1_long = $e1.principal.location.region_coordinates.longitude
$location1 = $e1.principal.ip_geo_artifact.location.country_or_region
$e2.metadata.event_type = "USER_LOGIN"
$e2.metadata.product_event_type = "UserLoggedIn"
$user = $e2.target.user.userid
$e2_lat = $e2.principal.location.region_coordinates.latitude
$e2_long = $e2.principal.location.region_coordinates.longitude
$location2 = $e2.principal.ip_geo_artifact.location.country_or_region
match:
$user over 1h
outcome:
$distance_kilometers = math.ceil(
max(math.geo_distance($e1_long, $e1_lat, $e2_long,$e2_lat)) / 1000
)
$duration = cast.as_int(
min(
($e2.metadata.event_timestamp.seconds - $e1.metadata.event_timestamp.seconds)
/ 60
)
)
$risk_score = max(
if(strings.to_lower($location1) != strings.to_lower($location2), 90)) + // if logging in from different countries
if($distance_kilometers > 100 and $distance_kilometers <= 500, 20) +
if($distance_kilometers > 500 and $distance_kilometers <= 1000, 30) +
if($distance_kilometers > 1000, 50)
$impossibleTravelActivity_description = array_distinct(strings.concat("The user '", $user, "' logged in from two locations with a distance of '", math.ceil(math.geo_distance($e1_long, $e1_lat, $e2_long,$e2_lat) / 1000), " km within a duration of ", cast.as_int(($e2.metadata.event_timestamp.seconds - $e1.metadata.event_timestamp.seconds)
/ 60), " minutes"))
condition:
$e1 and $e2 and $risk_score >= 20 and not $distance_kilometers <= 100
}
There is one more possibility. What if $e1.metadata.event_timestamp.seconds occured after $e2.metadata.event_timestamp.seconds ?
won't it cause issue?
is there any mod function which I can use?
Hi @anurag.q.singh
If you only $e1 to be a timestamp before $e2, then you would add something like the below
$e2.metadata.event_timestamp.seconds > $e1.metadata.event_timestamp.seconds
Kind Regards,
Ayman Charkaui
Hi @anurag.q.singh
If you only $e1 to be a timestamp before $e2, then you would add something like the below
$e2.metadata.event_timestamp.seconds > $e1.metadata.event_timestamp.seconds
Kind Regards,
Ayman Charkaui
$impossibleTravelActivity_description = array_distinct(strings.concat("The user '", $user, "' logged in from two locations with a distance of '", math.ceil(math.geo_distance($e1_long, $e1_lat, $e2_long,$e2_lat) / 1000), " km within a duration of ", if($e2.metadata.event_timestamp.seconds > $e1.metadata.event_timestamp.seconds , cast.as_int(($e2.metadata.event_timestamp.seconds - $e1.metadata.event_timestamp.seconds)
/ 60) , cast.as_int(($e1.metadata.event_timestamp.seconds - $e2.metadata.event_timestamp.seconds)
/ 60) ) , " minutes"))
something like this?
I guess I have made some error which is leading to a warning saying that
parsing: Only placeholders, event fields, and constants are allowed in then clause
$impossibleTravelActivity_description = array_distinct(strings.concat("The user '", $user, "' logged in from two locations with a distance of '", math.ceil(math.geo_distance($e1_long, $e1_lat, $e2_long,$e2_lat) / 1000), " km within a duration of ", if($e2.metadata.event_timestamp.seconds > $e1.metadata.event_timestamp.seconds , cast.as_int(($e2.metadata.event_timestamp.seconds - $e1.metadata.event_timestamp.seconds)
/ 60) , cast.as_int(($e1.metadata.event_timestamp.seconds - $e2.metadata.event_timestamp.seconds)
/ 60) ) , " minutes"))
something like this?
I guess I have made some error which is leading to a warning saying that
parsing: Only placeholders, event fields, and constants are allowed in then clause
Hi @anurag.q.singh
If you categorically don't want the rule to fire, unless $e2 occurs after $e1, then the below solution will work.
rule impossible_travel_activity {
meta:
author = "Anurag Singh"
description = "Detects potential account compromise by identifying logon attempts from two different geo locations within a short span of time, indicating impossible travel between the locations."
severity = "High"
events:
$e1.metadata.event_type = "USER_LOGIN"
$e1.metadata.product_event_type = "UserLoggedIn"
$user = $e1.target.user.userid
$e1_lat = $e1.principal.location.region_coordinates.latitude
$e1_long = $e1.principal.location.region_coordinates.longitude
$location1 = $e1.principal.ip_geo_artifact.location.country_or_region
$e2.metadata.event_type = "USER_LOGIN"
$e2.metadata.product_event_type = "UserLoggedIn"
$user = $e2.target.user.userid
$e2_lat = $e2.principal.location.region_coordinates.latitude
$e2_long = $e2.principal.location.region_coordinates.longitude
$location2 = $e2.principal.ip_geo_artifact.location.country_or_region
$e2.metadata.event_timestamp.seconds > $e1.metadata.event_timestamp.seconds
match:
$user over 1h
outcome:
$distance_kilometers = math.ceil(
max(math.geo_distance($e1_long, $e1_lat, $e2_long,$e2_lat)) / 1000
)
$duration = cast.as_int(
min(
($e2.metadata.event_timestamp.seconds - $e1.metadata.event_timestamp.seconds)
/ 60
)
)
$risk_score = max(
if(strings.to_lower($location1) != strings.to_lower($location2), 90)) + // if logging in from different countries
if($distance_kilometers > 100 and $distance_kilometers <= 500, 20) +
if($distance_kilometers > 500 and $distance_kilometers <= 1000, 30) +
if($distance_kilometers > 1000, 50)
$impossibleTravelActivity_description = array_distinct(strings.concat("The user '", $user, "' logged in from two locations with a distance of '", math.ceil(math.geo_distance($e1_long, $e1_lat, $e2_long,$e2_lat) / 1000), " km within a duration of ", cast.as_int(($e2.metadata.event_timestamp.seconds - $e1.metadata.event_timestamp.seconds)
/ 60), " minutes"))
condition:
$e1 and $e2 and $risk_score >= 20 and not $distance_kilometers <= 100
}
Kind Regards,
Ayman Charkaui