Skip to main content

I realize that last week’s blog may have felt like a cliffhanger to a TV show, but today I’m going to make up for it by diving straight into building rules using the graph_override command. If you are just joining us and need some additional context, take a moment and check out last week’s blog (insert link). Otherwise, here we go!

 

The rule that we are going to use as a starting point for today is reasonably straightforward and detects process launch events on the active directory server.

rule process_launch_activedir_new_employees {


 meta:
   author = "Google Cloud Security"
   description = "Detect process launch events by new employees on the active directory server"
   type = "alert"
   data_source = "microsoft windows events"
   platform = "Windows"
   severity = "Medium"
   priority = "Medium"

 events:
   $process.metadata.event_type = "PROCESS_LAUNCH"
   re.regex($process.principal.hostname, `^activedir`)
   $process.principal.hostname = $hostname
   NOT re.regex($process.principal.user.userid, `\$$`)
   $process.principal.user.userid = $userid

 match:
   $userid over 15m

 condition:
   $process
}

 

Let’s think about this rule in the context of the methodology I covered in the first blog on data tables and entity graph integration. The title and description of our rule would seem to indicate that we care about detecting new employees, yet the logic above doesn’t have any logic pertaining to these employees and their tenure or standing. Alright, let’s address that. Because we are looking for new employees, we could simply add a data table with a listing of the user, their probationary status and their start date for this rule. 

 

rule process_launch_activedir_new_employees {

meta:
  (snip)

events:
  $process.metadata.event_type = "PROCESS_LAUNCH"
  re.regex($process.principal.hostname, `^activedir`)
  $process.principal.hostname = $hostname
  NOT re.regex($process.principal.user.userid, `\$$`)
  $process.principal.user.userid = $userid

  %subsidiary_users.userid = $userid
  %subsidiary_users.probationary = "yes"
  %subsidiary_users.start_date = $start_date

match:
  $userid over 1h

outcome:
  $hire_date_formatted = timestamp.get_timestamp(max($start_date))
  $date_diff = min(timestamp.current_seconds() - $start_date)

condition:
  $process and $date_diff <= 2592000 //last 30 days in seconds
}

 

In this version of the rule, we added row based matching of the userid to the data table and added criteria that the user had to have a probationary status of yes and then we calculated the difference between the start date in the data table and current time and if the difference was less than 30 days (represented in seconds), we would detect.

 

 

Testing the rule, we get two detections and we can see frank.kolzig with a hire date of September 1, 2025 meets these conditions. Also notice when we expand the detection to see the events section, there is a D icon and when hovering over it, we can see that there are events joined to the data table.

 

So, maybe we don’t need graph_override for this example. Let’s evolve our example a bit further and let’s say we want to identify new employees who are in the Information Technology department with a start date within the past month. 

 

Because the department and the hire_date are both enriched fields from the entity graph, we could use these enriched fields in the rule. Now I don’t have to use the entity graph or data tables, so this is even easier, right?

rule process_launch_activedir_new_employees {

meta:
  (snip)

events:
  $process.metadata.event_type = "PROCESS_LAUNCH"
  re.regex($process.principal.hostname, `^activedir`)
  $process.principal.hostname = $hostname
  NOT re.regex($process.principal.user.userid, `\$$`)
  $process.principal.user.userid = $userid
  $process.principal.user.department = "Information Technology"
  $process.principal.user.hire_date.seconds > 0
  $process.principal.user.hire_date.seconds = $start_date

match:
  $userid over 1h

outcome:
  $hire_date_formatted = timestamp.get_timestamp(max($start_date))
  $date_diff = min(timestamp.current_seconds() - $start_date)

condition:
  $process and $date_diff <= 2592000 //last 30 days in seconds
}

 

Oh yeah, I forgot to mention, when we onboarded our user context data from Active Directory, we didn’t have data in the system to track the user’s hire date, so when we enrich the event data, this value is 0, the events don’t match the rule logic and we get no results for our detection.

 

While we could use the entity graph for this rule, as we have below, the data doesn’t exist in the entity graph, otherwise the enriched values would already be there, so we end up with a more complex rule with a join and no detection to show for it.

rule process_launch_activedir_new_employees {

meta:
  (snip)

events:
  $process.metadata.event_type = "PROCESS_LAUNCH"
  re.regex($process.principal.hostname, `^activedir`)
  $process.principal.hostname = $hostname
  NOT re.regex($process.principal.user.userid, `\$$`)
  $process.principal.user.userid = $userid

  $graph.graph.metadata.entity_type = "USER"
  $graph.graph.metadata.source_type = "ENTITY_CONTEXT"
  $graph.graph.entity.user.department = "Information Technology"
  $graph.graph.entity.user.hire_date.seconds > 0
  $graph.graph.entity.user.hire_date.seconds = $start_date
  $graph.graph.entity.user.userid = $userid

match:
  $userid over 1h

outcome:
  $hire_date_formatted = timestamp.get_timestamp(max($start_date))
  $date_diff = min(timestamp.current_seconds() - $start_date)

condition:
  $process and $graph and $date_diff <= 2592000 //last 30 days in seconds
}

 

So far this has been a bit of a bust…Which brings us back to the graph_override command. Here we have the following circumstances:

  • We have some data in the entity graph (and enriched into UDM events) 
  • We have additional data in a data table and we want to use it in concert with the entity graph
  • We want the data in the data table to take precedence over the entity graph data

 

Let’s look at what that rule might look like.

rule process_launch_activedir_new_employees {

meta:
  (snip)

setup:
 graph_override($graph.graph.entity.user.userid = %subsidiary_users.userid)

events:
 $process.metadata.event_type = "PROCESS_LAUNCH"
 re.regex($process.principal.hostname, `^activedir`)
 $process.principal.hostname = $hostname
 NOT re.regex($process.principal.user.userid, `\$$`)
 $process.principal.user.userid = $userid

 $graph.graph.metadata.entity_type = "USER"
 $graph.graph.metadata.source_type = "ENTITY_CONTEXT"
 $graph.graph.entity.user.department = "Information Technology"
 $graph.graph.entity.user.hire_date.seconds > 0
 $graph.graph.entity.user.hire_date.seconds = $start_date
 $graph.graph.entity.user.userid = $userid

match:
 $userid over 1h

outcome:
 $org = array_distinct($graph.graph.entity.user.company_name)
 $state_entity_graph = array_distinct($graph.graph.entity.user.personal_address.state)
 $dept = array_distinct($graph.graph.entity.user.department)
 $hire_date = max($graph.graph.entity.user.hire_date.seconds)
 $hire_date_formatted = timestamp.get_timestamp(max($graph.graph.entity.user.hire_date.seconds))
 $state_data_table = array_distinct($graph.graph.additional.fields["state"])
 $probation_data_table = array_distinct($graph.graph.additional.fields["probationary"])
 $date_diff = min(timestamp.current_seconds() - $start_date)

condition:
 $process and $graph and $date_diff <= 2592000
}

 

 

This is the fifth time in this blog you’ve seen this rule, so we aren’t going to rehash much it it, but there are a few key items I want to draw your attention to:

  • Notice that in the events section that the term $graph.graph.entity.user.hire_date.seconds > 0 returns a value. This is because of the override of the entity graph data with the data in the data table.
  • The outcome variable of $org displays the company name from the data table (Cymbal Hockey) yet the entity value displays the value Stacked Pads Goalie Training, which is stored in the entity.user.company_name field. This is a good example where we can view the entity graph value as well as the overridden value within the detection.
  • Notice the outcome variable $probation_data_table returns yes in the detection, but when we look in the outcome section of the rule we see that the field is mapped to $graph.graph.additional.fields["probationary"]. This is an example of a column that exists in the data table that is not mapped to an entity field but can be still used in a rule.

 

In fact, if I wanted to use that probationary key/value pair as part of the rule, I could place it in the events section like this.

rule process_launch_activedir_new_employees {

meta:
  (snip)

setup:
 graph_override($graph.graph.entity.user.userid = %subsidiary_users.userid)

events:
 $process.metadata.event_type = "PROCESS_LAUNCH"
 re.regex($process.principal.hostname, `^activedir`)
 $process.principal.hostname = $hostname
 NOT re.regex($process.principal.user.userid, `\$$`)
 $process.principal.user.userid = $userid

 $graph.graph.metadata.entity_type = "USER"
 $graph.graph.metadata.source_type = "ENTITY_CONTEXT"
 $graph.graph.additional.fields["probationary"] = "yes"
 $graph.graph.entity.user.userid = $userid

match:
 $userid over 1h

condition:
 $process and $graph
}

 

After iterating through a rule six times in one blog, I think we’ve reached the end for today. 

 

Hopefully, it’s clear that the graph_override command is a powerful option for detection engineers who are using the entity graph and data tables in concert with one another. Here are a few key things to keep in mind:

  • The setup section is after the meta section and before the events section
  • The mapping of entity graph fields to data table fields can be one or many but if all joins are not met, the override will not be used
  • In the test rule, we can still view the original values in the entity graph by clicking on the entity in the detection, but any outcome variables will be mapped to the values within the data table
  • The command in the setup section is evaluated first before criteria in the event section which allows us to use override values in the rule if desired

 

Finally, it’s important to understand that this is another tool in the utility belt. Depending on the use case and the data you have available, jumping straight to the row matching with a data table might be perfectly fine and quite frankly is a lot more straightforward than some of the other iterations we went through. However, there are times when we need data from the entity graph and have the ability to override certain entities with data table entries, so it’s nice to have that flexibility when we need it.

Be the first to reply!