Detection rules › YARA-L
YARA-L rules: 36
AWS EC2 User Data Modified
#Detect modifications to user data script on an EC2 instance.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Telemetry Coverage
Rule Body
// Copyright 2023 Google LLC. Licensed under Apache-2.0.
rule aws_ec2_user_data_modified {
meta:
author = "Google Cloud Security"
description = "Detect modifications to user data script on an EC2 instance."
rule_id = "mr_3167a29e-abbb-4d72-9f33-93df151d0224"
rule_name = "AWS EC2 User Data Modified"
mitre_attack_tactic = "Privilege Escalation"
mitre_attack_technique = "Boot or Logon Initialization Scripts"
mitre_attack_url = "https://attack.mitre.org/techniques/T1037/"
mitre_attack_version = "v13.1"
type = "Alert"
data_source = "AWS CloudTrail"
platform = "AWS"
severity = "High"
priority = "High"
events:
$cloudtrail.metadata.vendor_name = "AMAZON"
$cloudtrail.metadata.product_name = "AWS CloudTrail"
$cloudtrail.metadata.product_event_type = "ModifyInstanceAttribute"
$cloudtrail.target.resource.attribute.labels["Request Parameters User Data"] != ""
$cloudtrail.security_result.action = "ALLOW"
$cloudtrail.target.resource_ancestors.product_object_id = $instanceId
match:
$instanceId over 1h
outcome:
$risk_score = max(75)
$mitre_attack_tactic = "Privilege Escalation"
$mitre_attack_technique = "Boot or Logon Initialization Scripts"
$mitre_attack_technique_id = "T1037"
$event_count = count_distinct($cloudtrail.metadata.id)
$network_http_user_agent = array_distinct($cloudtrail.network.http.user_agent)
$principal_ip = array_distinct($cloudtrail.principal.ip)
$principal_ip_country = array_distinct($cloudtrail.principal.ip_geo_artifact.location.country_or_region)
$principal_ip_state = array_distinct($cloudtrail.principal.ip_geo_artifact.location.state)
$principal_user_display_name = array_distinct($cloudtrail.principal.user.user_display_name)
$recipient_aws_account_id = array_distinct($cloudtrail.additional.fields["recipientAccountId"])
$aws_region = array_distinct($cloudtrail.principal.location.name)
$target_resource_name = array_distinct($cloudtrail.target.resource.name)
$target_resource_product_object_id = array_distinct($cloudtrail.target.resource.product_object_id)
condition:
$cloudtrail
}
YARA-L Rule Structure
Condition
Fires when at least one $cloudtrail event in the 1h window.
Events
$cloudtrail
metadata.product_event_type = "ModifyInstanceAttribute"target.resource.attribute.labels["Request Parameters User Data"] is not nullsecurity_result.action = "ALLOW"
Match and Correlation
Outcome
Outcome variables add context to a detection. When the rule generates an alert, $risk_score is displayed with it.
| Field | Expression |
|---|---|
risk_score | max(75) |
event_count | count_distinct($cloudtrail.metadata.id) |
network_http_user_agent | array_distinct($cloudtrail.network.http.user_agent) |
principal_ip | array_distinct($cloudtrail.principal.ip) |
principal_ip_country | array_distinct($cloudtrail.principal.ip_geo_artifact.location.country_or_region) |
principal_ip_state | array_distinct($cloudtrail.principal.ip_geo_artifact.location.state) |
principal_user_display_name | array_distinct($cloudtrail.principal.user.user_display_name) |
recipient_aws_account_id | array_distinct($cloudtrail.additional.fields["recipientAccountId"]) |
aws_region | array_distinct($cloudtrail.principal.location.name) |
target_resource_name | array_distinct($cloudtrail.target.resource.name) |
target_resource_product_object_id | array_distinct($cloudtrail.target.resource.product_object_id) |
GitHub Access Granted To Personal Access Token Followed By High Number Of Cloned Non Public Repositories
#Detects when a user grants access to a GitHub Personal Access Token prior to cloning several GitHub non-public GitHub repositories. An adversary may grant access to a Personal Access Token before attempting to steal the contents of several GitHub repositories using a an automated script or offensive tool.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection |
References
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- Github Activity on a Private Repository from an Unusual IP (Elastic)
- GitHub Exfiltration via High Number of Repository Clones by User (Elastic)
- GitHub High Number Of Non Public GitHub Repositories Cloned (YARA-L)
- GitHub User Initial Access to Private Repo (Panther)
- High Number of Cloned GitHub Repos From PAT (Elastic)
- New GitHub Personal Access Token (PAT) Added (Elastic)
Rule Body
// Copyright 2024 Google LLC. Licensed under Apache-2.0.
rule github_access_granted_to_personal_access_token_followed_by_high_number_of_cloned_non_public_repositories {
meta:
author = "Google Cloud Security"
description = "Detects when a user grants access to a GitHub Personal Access Token prior to cloning several GitHub non-public GitHub repositories. An adversary may grant access to a Personal Access Token before attempting to steal the contents of several GitHub repositories using a an automated script or offensive tool."
rule_id = "mr_11aaa6f7-7fcc-42cb-af2e-42ed39226c43"
rule_name = "GitHub Access Granted To Personal Access Token Followed By High Number Of Cloned Non Public Repositories"
assumption = "Your GitHub enterprise audit log settings are configured to log the source IP address for events. Reference: https://docs.github.com/en/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/displaying-ip-addresses-in-the-audit-log-for-your-organization"
type = "alert"
severity = "High"
priority = "High"
platform = "GitHub"
data_source = "github"
mitre_attack_tactic = "Collection"
mitre_attack_technique = "Data from Information Repositories: Code Repositories"
mitre_attack_url = "https://attack.mitre.org/versions/v14/techniques/T1213/003/"
mitre_attack_version = "v14"
reference = "https://docs.github.com/en/enterprise-cloud@latest/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/audit-log-events-for-your-enterprise"
events:
// GitHub Personal Access Token (PAT) access granted event
$github_pat.metadata.vendor_name = "GITHUB" nocase
$github_pat.metadata.product_name = "GITHUB"
$github_pat.metadata.product_event_type = "personal_access_token.access_granted"
// GitHub repository clone event
$github_pat.metadata.vendor_name = "GITHUB" nocase
$github_clone.metadata.product_name = "GITHUB"
$github_clone.metadata.product_event_type = "git.clone"
$github_clone.target.resource.name = $github_repo_name
// Join GitHub PAT access granted event to GitHub repository clone event
$github_pat.principal.user.userid = $github_clone.principal.user.userid
// Placeholder for match section
$github_pat.principal.user.userid = $user_id
// Ensure PAT access granted event occurred before repository clone events
$github_pat.metadata.event_timestamp.seconds < $github_clone.metadata.event_timestamp.seconds
match:
$user_id over 30m
outcome:
$github_repo_name_distinct_count = count_distinct($github_repo_name)
$risk_score = max(85)
$mitre_attack_tactic = "Collection"
$mitre_attack_technique = "Data from Information Repositories: Code Repositories"
$mitre_attack_technique_id = "T1213.003"
$event_count = count_distinct($github_clone.metadata.id)
$principal_ip = array_distinct($github_pat.principal.ip)
$principal_user_userid = array_distinct($github_pat.principal.user.userid)
$principal_ip_country = array_distinct($github_pat.principal.ip_geo_artifact.location.country_or_region)
$principal_ip_state = array_distinct($github_pat.principal.ip_geo_artifact.location.state)
$principal_ip_city = array_distinct($github_pat.principal.location.city)
$security_result_summary = array_distinct($github_pat.security_result.summary)
condition:
// Customize GitHub repo count to fit your environment
$github_pat and $github_clone and $github_repo_name_distinct_count > 5
}
YARA-L Rule Structure
Condition
Fires when at least one $github_pat event in the 30m window and at least one $github_clone event in the 30m window and $github_repo_name_distinct_count > 5.
Events
$github_pat
metadata.product_event_type = "personal_access_token.access_granted"principal.user.userid cross_field_compare "github_clone.principal.user.userid"
$github_clone
metadata.product_event_type = "git.clone"
Match and Correlation
Outcome
Outcome variables add context to a detection. When the rule generates an alert, $risk_score is displayed with it.
| Field | Expression |
|---|---|
github_repo_name_distinct_count | count_distinct($github_repo_name) |
risk_score | max(85) |
event_count | count_distinct($github_clone.metadata.id) |
principal_ip | array_distinct($github_pat.principal.ip) |
principal_user_userid | array_distinct($github_pat.principal.user.userid) |
principal_ip_country | array_distinct($github_pat.principal.ip_geo_artifact.location.country_or_region) |
principal_ip_state | array_distinct($github_pat.principal.ip_geo_artifact.location.state) |
principal_ip_city | array_distinct($github_pat.principal.location.city) |
security_result_summary | array_distinct($github_pat.security_result.summary) |
Hunt for Non-Anonymous Office 365 file downloads
#Detects file downloads using O365 or Graph Activity logs, not including anonymous file links
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft 365 | FileDownloaded |
Rules Detecting the Same Action
These rules filter on the same operation.
Rule Body
// Copyright 2025 Google LLC. Licensed under Apache-2.0.
rule o365_file_download {
meta:
author = "Google Cloud Security"
description = "Detects file downloads using O365 or Graph Activity logs, not including anonymous file links"
rule_id = "mr_0dfb4338-4b4c-4af1-82da-fd5221d611a0"
rule_name = "Hunt for Non-Anonymous Office 365 file downloads"
assumption = "Because file downloads occur all the time, additional criteria to narrow this rule is expected. Areas to filter include user agent, specific users, IPs, applications and folders or items in the directory structure."
tactic = "TA0010"
technique = "T1048.002"
type = "hunt"
platform = "azure"
data_source = "o365, ms graph activity logs"
severity = "Low"
priority = "Low"
events:
(
$file.metadata.event_type = "USER_RESOURCE_UPDATE_CONTENT" and
$file.metadata.product_event_type = "FileDownloaded" and
$file.metadata.product_name = "Office 365" and
$file.metadata.vendor_name = "Microsoft" and
//This could be modified to specify downloading only thorugh specific applications
//$file.target.application = "OneDrive" and
//Add folder or docs of interest to monitor for downloads like this - focus in example is pdf in R&D folder - could also use a list
//re.regex($file.src.url, `^https://.*sharepoint.com/sites/.*/R&D/.*\.pdf$`) nocase and
NOT (
$file.principal.user.userid = /^urn:spo:anon#/ or
$file.principal.user.userid = "anonymous"
)
)
or
(
$file.metadata.event_type = "NETWORK_HTTP" and
$file.metadata.product_event_type = "Microsoft Graph Activity" and
$file.network.http.method = "GET" and
$file.network.http.response_code = 302 //and
//Could modify this to focus on a specific UA string or a UA strings not commonly used in environment
//$file.network.http.user_agent = /PowerShell/ nocase
//Could tighten to specify drives or items using an example like this
//re.regex($file.target.url, `^https://graph.microsoft.com/.*/drives/.*/content$`) nocase
)
$file.principal.ip = $ip
match:
$ip over 5m
outcome:
$risk_score = 35
$event_count = count_distinct($file.metadata.id)
$referral_url = array_distinct($file.network.http.referral_url)
$user_agent = array_distinct($file.network.http.user_agent)
$principal_application = array_distinct($file.principal.application)
$principal_ip = array_distinct($file.principal.ip)
$target_application = array_distinct($file.target.application)
//$principal_user_email_address = array_distinct(principal.user.email_addresses)
$principal_user_userid = array_distinct($file.principal.user.userid)
$src_file_full_path = array_distinct($file.src.file.full_path)
$src_url = array_distinct($file.src.url)
$session = array_distinct($file.network.session_id)
$location = array_distinct($file.principal.location.name)
$target_resource_guid = array_distinct($file.target.resource.product_object_id)
$target_url = array_distinct($file.target.url)
condition:
$file
}
YARA-L Rule Structure
Condition
Fires when at least one $file event in the 5m window.
Events
$file
metadata.product_event_type = "FileDownloaded"principal.user.userid matches "^urn:spo:anon#"principal.user.userid = "anonymous"metadata.product_event_type = "Microsoft Graph Activity"network.http.method = "GET"network.http.response_code = "302"
Match and Correlation
Outcome
Outcome variables add context to a detection. When the rule generates an alert, $risk_score is displayed with it.
| Field | Expression |
|---|---|
risk_score | 35 |
event_count | count_distinct($file.metadata.id) |
referral_url | array_distinct($file.network.http.referral_url) |
user_agent | array_distinct($file.network.http.user_agent) |
principal_application | array_distinct($file.principal.application) |
principal_ip | array_distinct($file.principal.ip) |
target_application | array_distinct($file.target.application) |
principal_user_userid | array_distinct($file.principal.user.userid) |
src_file_full_path | array_distinct($file.src.file.full_path) |
src_url | array_distinct($file.src.url) |
session | array_distinct($file.network.session_id) |
location | array_distinct($file.principal.location.name) |
target_resource_guid | array_distinct($file.target.resource.product_object_id) |
target_url | array_distinct($file.target.url) |
RDP Sensitive Settings Changed to Zero
#Detects tampering of RDP Terminal Service/Server sensitive settings. Such as allowing unauthorized users access to a system via the 'fAllowUnsolicited' or enabling RDP via 'fDenyTSConnections', etc.
Known False Positives
- Some of the keys mentioned here could be modified by an administrator while setting group policy
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Defense Impairment |
References
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Sysmon | Event ID 13: RegistryEvent (Value Set) |
| Security-Auditing | Event ID 4657: A registry value was modified. |
Rule Body
// Copyright 2025 Google LLC. Licensed under Apache-2.0.
rule rdp_sensitive_settings_changed_to_zero {
meta:
author = "Samir Bousseaden, David ANDRE, Roberto Rodriguez @Cyb3rWard0g, Nasreddine Bencherchali"
description = "Detects tampering of RDP Terminal Service/Server sensitive settings. Such as allowing unauthorized users access to a system via the 'fAllowUnsolicited' or enabling RDP via 'fDenyTSConnections', etc."
reference = "https://github.com/SigmaHQ/sigma/blob/master/rules/windows/registry/registry_set/registry_set_terminal_server_suspicious.yml"
license = "https://github.com/SigmaHQ/Detection-Rule-License/blob/main/LICENSE.Detection.Rules.md"
rule_name = "RDP Sensitive Settings Changed to Zero"
rule_id = "mr_553a08a3-f1b6-4962-9393-151b0fecdf55"
sigma_uuid = "a2863fbc-d5cb-48d5-83fb-d976d4b1743b"
sigma_status = "test"
tactic = "TA0005"
//tactic = "TA0003"
technique = "T1112"
type = "Detection"
data_source = "Sysmon"
platform = "Windows"
severity = "Medium"
priority = "Medium"
false_positives = "Some of the keys mentioned here could be modified by an administrator while setting group policy"
events:
$reg.metadata.event_type = "REGISTRY_MODIFICATION"
(
re.regex($reg.target.registry.registry_key, `\\fDenyTSConnections$`) nocase or
re.regex($reg.target.registry.registry_key, `\\fSingleSessionPerUser$`) nocase or
re.regex($reg.target.registry.registry_key, `\\UserAuthentication$`) nocase
)
$reg.target.registry.registry_value_data = "DWORD (0x00000000)" nocase
$reg.principal.hostname = $hostname
match:
$hostname over 5m
outcome:
//example usage of specifying test user and hostname to adjust risk score
$risk_score = max(if($reg.principal.user.userid = "user" and $reg.principal.hostname = "hostname", 0, 15))
$principal_hostname = array_distinct($reg.principal.hostname)
$principal_process_pid = array_distinct($reg.principal.process.pid)
$principal_process_file_full_path = array_distinct($reg.principal.process.file.full_path)
$principal_process_product_specific_process_id = array_distinct($reg.principal.process.product_specific_process_id)
$principal_user_userid = array_distinct($reg.principal.user.userid)
$target_registry_key = array_distinct($reg.target.registry.registry_key)
$target_registry_value_data = array_distinct($reg.target.registry.registry_value_data)
$log_type = array_distinct(strings.concat($reg.metadata.log_type,"/",$reg.metadata.product_event_type))
condition:
$reg
}
YARA-L Rule Structure
Condition
Fires when at least one $reg event in the 5m window.
Events
$reg
target.registry.registry_key matches "\\fDenyTSConnections$"target.registry.registry_key matches "\\fSingleSessionPerUser$"target.registry.registry_key matches "\\UserAuthentication$"target.registry.registry_value_data = "DWORD (0x00000000)"
Match and Correlation
Outcome
Outcome variables add context to a detection. When the rule generates an alert, $risk_score is displayed with it.
| Field | Expression |
|---|---|
risk_score | max(if($reg.principal.user.userid = "user" and $reg.principal.hostname = "hostname", 0, 15)) |
principal_hostname | array_distinct($reg.principal.hostname) |
principal_process_pid | array_distinct($reg.principal.process.pid) |
principal_process_file_full_path | array_distinct($reg.principal.process.file.full_path) |
principal_process_product_specific_process_id | array_distinct($reg.principal.process.product_specific_process_id) |
principal_user_userid | array_distinct($reg.principal.user.userid) |
target_registry_key | array_distinct($reg.target.registry.registry_key) |
target_registry_value_data | array_distinct($reg.target.registry.registry_value_data) |
log_type | array_distinct(strings.concat($reg.metadata.log_type,"/",$reg.metadata.product_event_type)) |
Recon Environment Enumeration System CISA Report
#Detects system enumeration events as identified in CISA Living off the Land pdf. Alone they may be normal but in concert, they may be worth looking into
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
References
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Sysmon | Event ID 1: Process creation |
| Security-Auditing | Event ID 4688: A new process has been created. |
Rule Body
// Copyright 2023 Google LLC. Licensed under Apache-2.0.
rule recon_environment_enumeration_system_cisa_report {
meta:
author = "Google Cloud Security"
description = "Detects system enumeration events as identified in CISA Living off the Land pdf. Alone they may be normal but in concert, they may be worth looking into"
rule_id = "mr_086dede6-33ba-42df-b001-5595ceb0d589"
rule_name = "Recon Environment Enumeration System CISA Report"
type = "hunt"
platform = "Windows"
data_source = "microsoft sysmon, windows event logs"
tactic = "TA0007"
technique = "T1082"
reference = "https://media.defense.gov/2023/May/24/2003229517/-1/-1/0/CSA_Living_off_the_Land.PDF"
severity = "Low"
priority = "Low"
events:
$process.metadata.event_type = "PROCESS_LAUNCH"
$process.principal.hostname = $hostname
$process.target.process.command_line = $command_line
re.regex($process.target.process.command_line, `wevtutil.*qe.*security.*\/rd:true.*\/f:text.*\/q:.*\[System\[\(EventID`) nocase
// below is the more specific command down to the event id, could comment out the about and use this instead if desired
//re.regex($process.target.process.command_line, `wevtutil.*qe.*security.*\/rd:true.*\/f:text.*\/q:\*\[System\[\(EventID=4624\).*TimeCreated\[@SystemTime.*\]\].*EventData\[Data.*\]\]`) nocase
or
// cisa report referenced cmd /c in their report throughout, can filter this in/out for tuning as desired
// other wmic switches like /user and /password, these have been excluded to focus on the commands being issued since local access does not require these
(
re.regex($process.target.process.command_line, `(|cmd.*/c).*ldifde.exe.*-f.*-p.*subtree`) nocase or
re.regex($process.target.process.command_line, `(|cmd.*/c).*reg.*query.*hklm\\software\\`) nocase or
re.regex($process.target.process.command_line, `(|cmd.*/c).*systeminfo`) nocase or
re.regex($process.target.process.command_line, `(|cmd.*/c).*tasklist.*\/v`) nocase or
re.regex($process.target.process.command_line, `(|cmd.*/c).*whoami`) nocase or
re.regex($process.target.process.command_line, `(|cmd.*/c).*wmic.*volume.*list.*brief`) nocase or
// cisa report called out wmic volume list brief, below is alternate that removed brief to widen criteria
//re.regex($process.target.process.command_line, `(|cmd.*/c).*wmic.*volume.*list`) nocase or
re.regex($process.target.process.command_line, `(|cmd.*/c).*wmic.*service.*brief`) nocase or
re.regex($process.target.process.command_line, `(|cmd.*/c).*wmic.*product.*list.*brief`) nocase or
re.regex($process.target.process.command_line, `(|cmd.*/c).*wmic.*baseboard.*list.*full`) nocase or
re.regex($process.target.process.command_line, `(|cmd.*/c).*wmic.*path.*win32_logicaldisk.*get.*(caption|filesystem|freespace|size|volumename)`) nocase
)
match:
$hostname over 15m
outcome:
$risk_score = 35
$event_count = count_distinct($process.metadata.id)
$unique_command_line_threshold = max(5)
// added to populate alert graph with additional context
// Commented out principal.hostname because it is already represented in graph as match variable. If match changes, can uncomment to add to results
//$principal_hostname = array_distinct($process.principal.hostname)
$principal_process_pid = array_distinct($process.principal.process.pid)
$principal_process_command_line = array_distinct($process.principal.process.command_line)
$principal_process_file_sha256 = array_distinct($process.principal.process.file.sha256)
$principal_process_file_full_path = array_distinct($process.principal.process.file.full_path)
$principal_process_product_specific_process_id = array_distinct($process.principal.process.product_specific_process_id)
$principal_process_parent_process_product_specific_process_id = array_distinct($process.principal.process.parent_process.product_specific_process_id)
$target_process_pid = array_distinct($process.target.process.pid)
$target_process_command_line = array_distinct($process.target.process.command_line)
$target_process_file_sha256 = array_distinct($process.target.process.file.sha256)
$target_process_file_full_path = array_distinct($process.target.process.file.full_path)
$target_process_product_specific_process_id = array_distinct($process.target.process.product_specific_process_id)
$principal_user_userid = array_distinct($process.principal.user.userid)
condition:
// modify the condition value for command line to throttle how many of these commands can be issued until the rule is triggered
$process and #command_line > 5
}
YARA-L Rule Structure
Condition
Fires when at least one $process event in the 15m window and $command_line occurs more than 5 times in the 15m window.
Events
$process
target.process.command_line matches "wevtutil.*qe.*security.*\/rd:true.*\/f:text.*\/q:.*\[System\[\(EventID"target.process.command_line matches "(|cmd.*/c).*ldifde.exe.*-f.*-p.*subtree"target.process.command_line matches "(|cmd.*/c).*reg.*query.*hklm\\software\\"target.process.command_line matches "(|cmd.*/c).*systeminfo"target.process.command_line matches "(|cmd.*/c).*tasklist.*\/v"target.process.command_line matches "(|cmd.*/c).*whoami"target.process.command_line matches "(|cmd.*/c).*wmic.*volume.*list.*brief"target.process.command_line matches "(|cmd.*/c).*wmic.*service.*brief"target.process.command_line matches "(|cmd.*/c).*wmic.*product.*list.*brief"target.process.command_line matches "(|cmd.*/c).*wmic.*baseboard.*list.*full"target.process.command_line matches "(|cmd.*/c).*wmic.*path.*win32_logicaldisk.*get.*(caption|filesystem|freespace|size|volumename)"
Match and Correlation
Outcome
Outcome variables add context to a detection. When the rule generates an alert, $risk_score is displayed with it.
| Field | Expression |
|---|---|
risk_score | 35 |
event_count | count_distinct($process.metadata.id) |
unique_command_line_threshold | max(5) |
principal_process_pid | array_distinct($process.principal.process.pid) |
principal_process_command_line | array_distinct($process.principal.process.command_line) |
principal_process_file_sha256 | array_distinct($process.principal.process.file.sha256) |
principal_process_file_full_path | array_distinct($process.principal.process.file.full_path) |
principal_process_product_specific_process_id | array_distinct($process.principal.process.product_specific_process_id) |
principal_process_parent_process_product_specific_process_id | array_distinct($process.principal.process.parent_process.product_specific_process_id) |
target_process_pid | array_distinct($process.target.process.pid) |
target_process_command_line | array_distinct($process.target.process.command_line) |
target_process_file_sha256 | array_distinct($process.target.process.file.sha256) |
target_process_file_full_path | array_distinct($process.target.process.file.full_path) |
target_process_product_specific_process_id | array_distinct($process.target.process.product_specific_process_id) |
principal_user_userid | array_distinct($process.principal.user.userid) |
sap critical role assigned to new user
#Detects the assignment of high-privilege roles by correlating Change Documents (PFCG) with Security Audit logs (AUD) for IP attribution.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Rule Body
// Copyright 2026 Google LLC. Licensed under Apache-2.0.
rule sap_critical_role_assigned_to_new_user {
meta:
author = "Google Cloud Security"
description = "Detects the assignment of high-privilege roles by correlating Change Documents (PFCG) with Security Audit logs (AUD) for IP attribution."
severity = "Critical"
tactic = "TA0004"
technique = "T1098"
events:
$change.metadata.log_type = "SAP_CHANGE_DOCUMENT"
$change.target.resource.attribute.labels["TABNAME"] = "AGR_USERS"
$change.target.resource.attribute.labels["CHNGIND"] = "I"
$role = $change.target.resource.product_object_id
$audit.metadata.log_type = "SAP_SECURITY_AUDIT"
$audit.additional.fields["msg_1"] = "AU7"
//join
$target_user = re.capture($change.target.resource.attribute.labels["TABKEY"], "^.{33}\\s*(\\S+)\\s*\\d{16}$")
$target_user = $audit.additional.fields["param1_1"]
$role in %sap_sensitive_roles.role
match:
$target_user over 1h
outcome:
$user_that_created_account = array_distinct($audit.principal.user.userid)
$user_that_assigned_role = array_distinct($change.principal.user.userid)
$source_ip = array_distinct($audit.principal.ip)
$assigned_role = array_distinct($role)
$system_id = array_distinct($audit.target.application)
$risk_score = 95
condition:
$change and $audit
}
YARA-L Rule Structure
Condition
Fires when at least one $change event in the 1h window and at least one $audit event in the 1h window.
Events
$audit
metadata.log_type = "SAP_SECURITY_AUDIT"additional.fields["msg_1"] = "AU7"additional.fields["param1_1"] macro "re.capture($change.target.resource.attribute.labels["TABKEY"], "^.{33}\\s*(\\S+)\\s*\\d{16}$")"
$change
metadata.log_type = "SAP_CHANGE_DOCUMENT"target.resource.attribute.labels["TABNAME"] = "AGR_USERS"target.resource.attribute.labels["CHNGIND"] = "I"target.resource.product_object_id in "%sap_sensitive_roles.role"
Match and Correlation
Outcome
Outcome variables add context to a detection. When the rule generates an alert, $risk_score is displayed with it.
| Field | Expression |
|---|---|
user_that_created_account | array_distinct($audit.principal.user.userid) |
user_that_assigned_role | array_distinct($change.principal.user.userid) |
source_ip | array_distinct($audit.principal.ip) |
assigned_role | array_distinct($role) |
system_id | array_distinct($audit.target.application) |
risk_score | 95 |