Detection rules › Kusto
Kusto rules: 1g
App GW WAF - Path Traversal Attack
#'Identifies a match for a Path Traversal based attack in the App Gateway WAF logs. The threshold value in the query can be changed as per your infrastructure's requirements. References: https://owasp.org/www-community/attacks/Path_Traversal'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Privilege Escalation | |
| Discovery |
Rule Body
id: b6c3a8a6-d22c-4882-9c57-abc01690938b
name: App GW WAF - Path Traversal Attack
description: |
'Identifies a match for a Path Traversal based attack in the App Gateway WAF logs. The threshold value in the query can be changed as per your infrastructure's requirements.
References: https://owasp.org/www-community/attacks/Path_Traversal'
severity: High
status: Available
requiredDataConnectors:
- connectorId: WAF
dataTypes:
- AzureDiagnostics
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- Execution
- InitialAccess
- PrivilegeEscalation
- Discovery
relevantTechniques:
- T1548
- T1203
- T1190
- T1548
- T1087
tags:
- Path Traversal
query: |
let Threshold = 3;
AGWFirewallLogs
| where Action == "Matched"
| where Message has "Path Traversal Attack"
| project TransactionId, Hostname, RequestUri, TimeGenerated, ClientIp, Message, DetailedMessage, DetailedData
| join kind = inner(
AGWFirewallLogs
| where Action == "Blocked") on TransactionId
| extend Uri = strcat(Hostname,RequestUri)
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), TransactionID = make_set(TransactionId,100), Message = make_set(Message,100), Detail_Message = make_set(DetailedMessage,100), Detail_Data = make_set(DetailedData,100), Total_TransactionId = dcount(TransactionId) by ClientIp, Uri, Action
| where Total_TransactionId >= Threshold
# The Threshold value above can be changed as per your infrastructure's requirement
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Uri
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIp
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let Threshold = 3;
Stage 1: source
AGWFirewallLogs
Stage 2: where
| where Action == "Matched"
Stage 3: where
| where Message has "Path Traversal Attack"
Stage 4: project
| project TransactionId, Hostname, RequestUri, TimeGenerated, ClientIp, Message, DetailedMessage, DetailedData
Stage 5: join
| join kind = inner(
AGWFirewallLogs
| where Action == "Blocked") on TransactionId
Stage 6: extend
extend Uri
Stage 7: summarize
summarize Detail_Data, Detail_Message, EndTime, Message, StartTime, Total_TransactionId, TransactionID by ClientIp, Uri, Action
Stage 8: where
where Total_TransactionId >= 3
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | eq |
| field:"Action" kind:eq |
Message | match |
| field:"Message" kind:match value:"Path Traversal Attack" |
Total_TransactionId | ge |
| field:"Total_TransactionId" kind:ge value:"3" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Action | summarize |
ClientIp | summarize |
Detail_Data | summarize |
Detail_Message | summarize |
EndTime | summarize |
Message | summarize |
StartTime | summarize |
Total_TransactionId | summarize |
TransactionID | summarize |
Uri | summarize |
AWSCloudTrail - Privilege escalation via CRUD IAM policy
#Detects creation of inline IAM policies that grant broad IAM create, read, update, and delete capabilities. This behavior can establish or expand privileged control paths in the account and should be reviewed as potential privilege escalation.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- AWS IAM Access Key Compromise Detection (Panther)
- AWS Lateral Movement from Kubernetes SA via AssumeRoleWithWebIdentity (Elastic)
- AWSCloudTrail - Privilege escalation via CloudFormation policy (Kusto)
- AWSCloudTrail - Privilege escalation via CRUD DynamoDB policy (Kusto)
- AWSCloudTrail - Privilege escalation via CRUD KMS policy (Kusto)
- AWSCloudTrail - Privilege escalation via CRUD Lambda policy (Kusto)
- AWSCloudTrail - Privilege escalation via CRUD S3 policy (Kusto)
- AWSCloudTrail - Privilege escalation via DataPipeline policy (Kusto)
Rule Body
id: e20d35a3-4fec-4c8b-81b1-fc33b41990b0
name: AWSCloudTrail - Privilege escalation via CRUD IAM policy
description: |
Detects creation of inline IAM policies that grant broad IAM create, read, update, and delete capabilities.
This behavior can establish or expand privileged control paths in the account and should be reviewed as potential
privilege escalation.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AWS
dataTypes:
- AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
relevantTechniques:
- T1098.003
query: |
AWSCloudTrail
| where EventName in ("PutUserPolicy","PutRolePolicy","PutGroupPolicy") and isempty(ErrorCode) and isempty(ErrorMessage)
| extend PolicyName = tostring(parse_json(RequestParameters).policyName)
| extend Statement = parse_json(tostring((parse_json(RequestParameters).policyDocument))).Statement
| mvexpand Statement
| extend Action = parse_json(Statement).Action , Effect = tostring(parse_json(Statement).Effect), Resource = tostring(parse_json(Statement).Resource), Condition = tostring(parse_json(Statement).Condition)
| extend Action = tostring(Action)
| where Effect =~ "Allow" and (Action contains "iam:Create" and (Action contains "iam:Get" or Action contains "iam:List") and Action contains "iam:Update" and Action contains "iam:Delete") and Resource == "*" and Condition == ""
| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
| distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityArn, UserIdentityUserName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- identifier: CloudAppAccountId
columnName: RecipientAccountId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIpAddress
customDetails:
PolicyName: PolicyName
EventName: EventName
UserIdentityArn: UserIdentityArn
RecipientAccountId: RecipientAccountId
alertDetailsOverride:
alertDisplayNameFormat: 'AWS IAM privilege escalation policy update by {{AccountName}}'
alertDescriptionFormat: 'Detected {{EventName}} Event, updating inline IAM escalation policy {{PolicyName}} in account {{RecipientAccountId}}.'
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
AWSCloudTrail
Stage 2: where
| where EventName in ("PutUserPolicy","PutRolePolicy","PutGroupPolicy") and isempty(ErrorCode) and isempty(ErrorMessage)
Stage 3: extend
| extend PolicyName = tostring(parse_json(RequestParameters).policyName)
Stage 4: extend
| extend Statement = parse_json(tostring((parse_json(RequestParameters).policyDocument))).Statement
Stage 5: mv-expand
| mvexpand Statement
Stage 6: extend
| extend Action = parse_json(Statement).Action , Effect = tostring(parse_json(Statement).Effect), Resource = tostring(parse_json(Statement).Resource), Condition = tostring(parse_json(Statement).Condition)
Stage 7: extend
| extend Action = tostring(Action)
Stage 8: where
| where Effect =~ "Allow" and (Action contains "iam:Create" and (Action contains "iam:Get" or Action contains "iam:List") and Action contains "iam:Update" and Action contains "iam:Delete") and Resource == "*" and Condition == ""
Stage 9: extend (4 consecutive steps)
| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
Stage 10: distinct
| distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityArn, UserIdentityUserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | contains |
| field:"Action" kind:contains |
Effect | eq |
| field:"Effect" kind:eq value:"Allow" |
ErrorCode | is_null | field:"aws::errorCode" kind:is_null | |
ErrorMessage | is_null | field:"aws::errorMessage" kind:is_null | |
EventName | in |
| field:"aws::eventName" kind:in |
Resource | eq |
| field:"Resource" kind:eq value:"*" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
PolicyName | extend |
Statement | extend |
Action | extend |
Condition | extend |
Effect | extend |
Resource | extend |
UserIdentityArn | extend |
UserName | extend |
AccountName | extend |
AccountUPNSuffix | extend |
BloodHound Attack Path Finding - WriteGpLink Privileges on Tier Zero Objects
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
BloodHound Attack Path Finding - WriteGpLink Privileges on Tier Zero Objects
Rule Body
id: b64ee057-94a4-42a5-a769-430a2943f764
name: BloodHound Attack Path Finding - WriteGpLink Privileges on Tier Zero Objects
description: BloodHound Attack Path Finding - WriteGpLink Privileges on Tier Zero
Objects
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: BloodHoundEnterprise
dataTypes:
- BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: BHEAttackPathsData_CL | where Finding == "T0WriteGPLink"
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: domain_name
customDetails:
Description: ShortDescription
Remediation: ShortRemediation
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
BHEAttackPathsData_CL
Stage 2: where
| where Finding == "T0WriteGPLink"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"T0WriteGPLink" |
Cisco WSA - Multiple attempts to download unwanted file
#'Detects when multiple attempts to download unwanted file occur.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 46b6c6fc-2c1a-4270-be10-9d444d83f027
name: Cisco WSA - Multiple attempts to download unwanted file
description: |
'Detects when multiple attempts to download unwanted file occur.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1189
query: |
let threshold = 2;
CiscoWSAEvent
| where DvcAction =~ 'BLOCK_ADMIN_FILE_TYPE'
| summarize i_src = makeset(SrcIpAddr) by UrlOriginal, bin(TimeGenerated, 15m)
| where array_length(i_src) >= threshold
| extend IPCustomEntity = i_src, UrlCustomEntity = UrlOriginal
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
- entityType: URL
fieldMappings:
- identifier: Url
columnName: UrlCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 2;
Stage 1: source
CiscoWSAEvent
Stage 2: where
| where DvcAction =~ 'BLOCK_ADMIN_FILE_TYPE'
Stage 3: summarize
| summarize i_src = makeset(SrcIpAddr) by UrlOriginal, bin(TimeGenerated, 15m)
Stage 4: where
| where array_length(i_src) >= threshold
Stage 5: extend
| extend IPCustomEntity = i_src, UrlCustomEntity = UrlOriginal
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DvcAction | eq |
| field:"DvcAction" kind:eq value:"BLOCK_ADMIN_FILE_TYPE" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
UrlOriginal | summarize |
i_src | summarize |
IPCustomEntity | extend |
UrlCustomEntity | extend |
Configuration Backup Job Failed
#Detects failed configuration backup operations. This might indicate system or storage issues, or a potential sabotage of the backup infrastructure.
Rule Body
id: 18833d01-703a-438d-8ac6-84faa82e9b52
name: Configuration Backup Job Failed
description: Detects failed configuration backup operations. This might indicate
system or storage issues, or a potential sabotage of the backup infrastructure.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 3h
queryPeriod: 3h
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetFinishedConfigurationBackupSessions\r\n| where Result == \"Failed\"\
\r\n| project\r\n Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\r\
\n DataSource = Computer,\r\n EventId = instanceId,\r\n UserName = user,\r\
\n [\"State\"] = Result,\r\n MessageDetails = Description"
version: 1.0.1
kind: Scheduled
customDetails:
Date: Date
DataSource: DataSource
EventId: EventId
MessageDetails: MessageDetails
Stages and Predicates
Stage 1: source
Veeam_GetFinishedConfigurationBackupSessions
Stage 2: where
| where Result == "Failed"
Stage 3: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = Computer,
EventId = instanceId,
UserName = user,
["State"] = Result,
MessageDetails = Description
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Result | eq |
| field:"Result" kind:eq value:"Failed" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
State | project |
UserName | project |
CTE - Blocked URL Attempted
#Cybereinforce blocked a URL/domain access attempt on an enrolled device based on a configured policy pattern (non-threat-intel match).
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 1747715b-1a94-411a-a2b5-d8c40422767c
name: CTE - Blocked URL Attempted
description: |
Cybereinforce blocked a URL/domain access attempt on an enrolled device based on a configured policy pattern (non-threat-intel match).
severity: Low
requiredDataConnectors:
- connectorId: cybereinforce_cte
dataTypes:
- CybereinforceCTE_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1189
query: |
CybereinforceCTE_CL
| where EventType == "block.url"
| extend d = parse_json(Details)
| extend
DeviceId = tostring(d.deviceId),
HostName = iff(isnotempty(tostring(d.hostname)), tostring(d.hostname), strcat("cte-device-", substring(EntityId, 0, 8))),
DeviceUser = tostring(d.deviceUser),
DeviceOs = tostring(d.deviceOs),
DeviceVersion = tostring(d.deviceVersion),
RuleId = tostring(d.ruleId),
RuleName = iff(isnotempty(tostring(d.ruleName)), tostring(d.ruleName), "Pattern Match"),
Pattern = tostring(d.pattern),
Url = tostring(d.url),
Action = tostring(d.action),
IsIoc = tobool(d.ioc),
ClientIp = tostring(split(tostring(d.clientIp), ":")[0]),
ClientPort = tostring(split(tostring(d.clientIp), ":")[1]),
UserAgent = tostring(d.userAgent)
| where RuleName != "Cybereinforce Threat Intelligence"
| project TimeGenerated, TenantId, DeviceId, HostName, DeviceUser, DeviceOs, DeviceVersion, RuleId, RuleName, Pattern, Url, Action, IsIoc, ClientIp, ClientPort, UserAgent, Severity, Category, Source
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIp
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
CybereinforceCTE_CL
Stage 2: where
| where EventType == "block.url"
Stage 3: extend
| extend d = parse_json(Details)
Stage 4: extend
| extend
DeviceId = tostring(d.deviceId),
HostName = iff(isnotempty(tostring(d.hostname)), tostring(d.hostname), strcat("cte-device-", substring(EntityId, 0, 8))),
DeviceUser = tostring(d.deviceUser),
DeviceOs = tostring(d.deviceOs),
DeviceVersion = tostring(d.deviceVersion),
RuleId = tostring(d.ruleId),
RuleName = iff(isnotempty(tostring(d.ruleName)), tostring(d.ruleName), "Pattern Match"),
Pattern = tostring(d.pattern),
Url = tostring(d.url),
Action = tostring(d.action),
IsIoc = tobool(d.ioc),
ClientIp = tostring(split(tostring(d.clientIp), ":")[0]),
ClientPort = tostring(split(tostring(d.clientIp), ":")[1]),
UserAgent = tostring(d.userAgent)
HostName =isnotempty(tostring(d.hostname))tostring(d.hostname)strcat("cte-device-", substring(EntityId, 0, 8))RuleName =isnotempty(tostring(d.ruleName))tostring(d.ruleName)"Pattern Match"Stage 5: where
| where RuleName != "Cybereinforce Threat Intelligence"
Stage 6: project
| project TimeGenerated, TenantId, DeviceId, HostName, DeviceUser, DeviceOs, DeviceVersion, RuleId, RuleName, Pattern, Url, Action, IsIoc, ClientIp, ClientPort, UserAgent, Severity, Category, Source
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | eq |
| field:"EventType" kind:eq value:"block.url" |
RuleName | ne |
| field:"RuleName" kind:ne value:"Cybereinforce Threat Intelligence" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Action | project |
Category | project |
ClientIp | project |
ClientPort | project |
DeviceId | project |
DeviceOs | project |
DeviceUser | project |
DeviceVersion | project |
HostName | project |
IsIoc | project |
Pattern | project |
RuleId | project |
RuleName | project |
Severity | project |
Source | project |
TenantId | project |
TimeGenerated | project |
Url | project |
UserAgent | project |
GTI - High and Critical Priority Alerts
#Triggers an incident whenever a GTI Relevance System Alert with priority level HIGH or CRITICAL is ingested. These alerts indicate threats that Google Threat Intelligence has assessed as requiring immediate attention based on relevance, severity, and potential business impact. Each unique Alert ID is grouped into a single incident.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Credential Access | |
| Exfiltration | |
| Impact |
Rule Body
id: b2c3d4e5-f6a7-8901-bcde-f12345678902
name: GTI - High and Critical Priority Alerts
description: |
Triggers an incident whenever a GTI Relevance System Alert with priority level HIGH or CRITICAL
is ingested. These alerts indicate threats that Google Threat Intelligence has assessed as requiring
immediate attention based on relevance, severity, and potential business impact.
Each unique Alert ID is grouped into a single incident.
severity: High
status: Available
requiredDataConnectors:
- connectorId: GoogleThreatIntelligenceRelevanceSystemAlertsAPI
dataTypes:
- RelevanceSystemAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Impact
- CredentialAccess
- Exfiltration
relevantTechniques:
- T1566
- T1078
- T1552
- T1486
- T1567
query: |
GTIRelevanceSystemAlerts
| where PriorityLevel in ("HIGH", "CRITICAL")
| extend
DetailSeverity = coalesce(DataLeakSeverity, InsiderThreatSeverity, InitialAccessBrokerSeverity, "")
| project
TimeGenerated,
AlertId,
AlertName = DisplayName,
AlertState = State,
PriorityLevel,
PriorityReasoning,
SeverityLevel,
SeverityConfidence,
DetailType,
DetailSeverity,
RelevanceLevel,
FindingCount,
AiSummary,
AuditCreateTime,
AuditUpdateTime
alertDetailsOverride:
alertDisplayNameFormat: 'GTI High/Critical Priority Alert: {{AlertName}}'
alertDescriptionFormat: |
Priority: {{PriorityLevel}}
Priority Reasoning: {{PriorityReasoning}}
Summary: {{AiSummary}}
customDetails:
AlertId: AlertId
AlertState: AlertState
PriorityLevel: PriorityLevel
SeverityLevel: SeverityLevel
DetailType: DetailType
FindingCount: FindingCount
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: P5D
matchingMethod: Selected
groupByCustomDetails:
- AlertId
eventGroupingSettings:
aggregationKind: AlertPerResult
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
GTIRelevanceSystemAlerts
Stage 2: where
| where PriorityLevel in ("HIGH", "CRITICAL")
Stage 3: extend
| extend
DetailSeverity = coalesce(DataLeakSeverity, InsiderThreatSeverity, InitialAccessBrokerSeverity, "")
Stage 4: project
| project
TimeGenerated,
AlertId,
AlertName = DisplayName,
AlertState = State,
PriorityLevel,
PriorityReasoning,
SeverityLevel,
SeverityConfidence,
DetailType,
DetailSeverity,
RelevanceLevel,
FindingCount,
AiSummary,
AuditCreateTime,
AuditUpdateTime
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PriorityLevel | in |
| field:"PriorityLevel" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AiSummary | project |
AlertId | project |
AlertName | project |
AlertState | project |
AuditCreateTime | project |
AuditUpdateTime | project |
DetailSeverity | project |
DetailType | project |
FindingCount | project |
PriorityLevel | project |
PriorityReasoning | project |
RelevanceLevel | project |
SeverityConfidence | project |
SeverityLevel | project |
TimeGenerated | project |
McAfee ePO - Attempt uninstall McAfee agent
#Detects attempts uninstalling McAfee agent on host.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth | |
| Defense Impairment |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| McAfee-ePolicy-Orchestrator | Event ID 2413: Attempt to uninstall the McAfee agent |
Rule Body
id: 2eff5809-bf84-48e0-8288-768689672c37
name: McAfee ePO - Attempt uninstall McAfee agent
description: |
'Detects attempts uninstalling McAfee agent on host.'
severity: Medium
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
- T1070
query: |
McAfeeEPOEvent
| where EventId == '2413'
| extend IPCustomEntity = DvcIpAddr, HostCustomEntity = DvcHostname
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: HostCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
McAfeeEPOEvent
Stage 2: where
| where EventId == '2413'
Stage 3: extend
| extend IPCustomEntity = DvcIpAddr, HostCustomEntity = DvcHostname
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventId | eq |
| field:"EventId" kind:eq value:"2413" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
HostCustomEntity | extend |
IPCustomEntity | extend |
Privileged Machines Exposed to the Internet
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
These are AWS Ec2 machines that are exposed to the internet. You can further filter by tags so that you can, for example, find exposed machines that are also "privileged".
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Rule Body
id: 72891de4-da70-44e4-9984-35fcea98d000
name: Privileged Machines Exposed to the Internet
kind: Scheduled
description: These are AWS Ec2 machines that are exposed to the internet. You can further filter by tags so that you can, for example, find exposed machines that are also "privileged".
severity: High
status: Available
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
- Impact
relevantTechniques:
- T1580
query: |-
Authomize_v2_CL
| where ingestion_time() >= ago(30m)
| extend EventID = id_s, Policy = policy_name_s, Severity = severity_s,Description = description_s,Recommendation = recommendation_s,URL = url_s,Tactics = tactics_s
| where Policy has "Privileged Machines Exposed to the Internet"
| project EventID, Policy, Severity, Description, Recommendation, URL, Category, Tactics
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URL
requiredDataConnectors:
- connectorId: Authomize
dataTypes: [ "Authomize_v2_CL" ]
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AnyAlert
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertnameFormat: Alert from Authomize - Privileged Machines Exposed to the Internet
alertDescriptionFormat: Privileged Machines Exposed to the Internet. These are AWS Ec2 machines that are exposed to the internet. You can further filter by tags so that you can, for example, find exposed machines that are also "privileged".
alertSeverity: Severity
alertTactics: Tactics
alertDynamicProperties:
- alertProperty: AlertLink
value: URL
customDetails:
AuthomizeEventID: EventID
EventName: Policy
EventDescription: Description
EventRecommendation: Recommendation
ReferencedURL: URL
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.2
Stages and Predicates
Stage 1: source
Authomize_v2_CL
Stage 2: where
| where ingestion_time() >= ago(30m)
Stage 3: extend
| extend EventID = id_s, Policy = policy_name_s, Severity = severity_s,Description = description_s,Recommendation = recommendation_s,URL = url_s,Tactics = tactics_s
Stage 4: where
| where Policy has "Privileged Machines Exposed to the Internet"
Stage 5: project
| project EventID, Policy, Severity, Description, Recommendation, URL, Category, Tactics
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Policy | match |
| field:"policy_name_s" kind:match value:"Privileged Machines Exposed to the Internet" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Category | project |
Description | project |
EventID | project |
Policy | project |
Recommendation | project |
Severity | project |
Tactics | project |
URL | project |
PROD (TM014.1) - GPO - Linked, Unlinked, or Enforced at the AD Site Level
#Monitoring AD Site GPOs being Linked, Unlinked, or Enforced.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation | |
| Defense Impairment |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 5136: A directory service object was modified. |
Rule Body
id: 38f2f6f4-2869-401c-8341-e3a5c11018a5
name: 'PROD (TM014.1) - GPO - Linked, Unlinked, or Enforced at the AD Site Level'
description: |
Monitoring AD Site GPOs being Linked, Unlinked, or Enforced.
severity: High
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- DefenseEvasion
relevantTechniques:
- T1484.001
query: |
SecurityEvent
| where EventID == 5136
and EventData matches regex @"(?i)CN=Sites"
and Activity has "modified"
and EventData has '"ObjectClass">site'
and EventData has '<Data Name="AttributeLDAPDisplayName">gPLink</Data>'
and (EventData has ';0]'
or EventData has ';1]'
or EventData has ';2]')
and EventData has '"OperationType">%%14674'
| extend ObjectName = extract("<Data Name=\"ObjectDN\">((?i)CN=[^,]+(?:,(?i)CN=[^,]+)*),DC=", 1, EventData)
| extend AttributeLDAP = extract("<Data Name=\"AttributeLDAPDisplayName\">([^<]+)</Data>", 1, EventData)
| extend AttributeValue = extract("\\[([^\\]]+)\\]", 1, EventData)
| extend LastThreeChars = substring(AttributeValue, strlen(AttributeValue) - 2, 3)
| extend AttributeValueDescription = case(
LastThreeChars == ";0", "Linked",
LastThreeChars == ";1", "Unlinked",
LastThreeChars == ";2", "Enforced",
"Unknown")
| extend ObjectClass = extract("<Data Name=\"ObjectClass\">([^<]+)</Data>", 1, EventData)
| extend Domain = extract("([^.]+\\.[^.]+)$", 1, Computer)
| project TimeGenerated
, ObjectName
, ObjectClass
, ActivityType = AttributeValueDescription
, AttributeLDAP
, Account
, Domain
, Computer
, Channel
, EventID
, EventData
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ObjectName
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Computer
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Account
customDetails:
ObjectName: ObjectName
ObjectClass: ObjectClass
ActivityType: ActivityType
AttributeLDAP: AttributeLDAP
Account: Account
Domain: Domain
Computer: Computer
Channel: Channel
EventID: EventID
EventData: EventData
eventGroupingSettings:
aggregationKind: AlertPerResult
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
alertDetailsOverride:
alertDisplayNameFormat: '(TM014.1) A GPO was {{ActivityType}} at the Active Directory Site Level'
alertDescriptionFormat: 'Site Level GPOs can overwrite Tier Model GPOs.'
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
SecurityEvent
Stage 2: where
| where EventID == 5136
and EventData matches regex @"(?i)CN=Sites"
and Activity has "modified"
and EventData has '"ObjectClass">site'
and EventData has '<Data Name="AttributeLDAPDisplayName">gPLink</Data>'
and (EventData has ';0]'
or EventData has ';1]'
or EventData has ';2]')
and EventData has '"OperationType">%%14674'
Stage 3: extend (7 consecutive steps)
| extend ObjectName = extract("<Data Name=\"ObjectDN\">((?i)CN=[^,]+(?:,(?i)CN=[^,]+)*),DC=", 1, EventData)
| extend AttributeLDAP = extract("<Data Name=\"AttributeLDAPDisplayName\">([^<]+)</Data>", 1, EventData)
| extend AttributeValue = extract("\\[([^\\]]+)\\]", 1, EventData)
| extend LastThreeChars = substring(AttributeValue, strlen(AttributeValue) - 2, 3)
| extend AttributeValueDescription = case(
LastThreeChars == ";0", "Linked",
LastThreeChars == ";1", "Unlinked",
LastThreeChars == ";2", "Enforced",
"Unknown")
| extend ObjectClass = extract("<Data Name=\"ObjectClass\">([^<]+)</Data>", 1, EventData)
| extend Domain = extract("([^.]+\\.[^.]+)$", 1, Computer)
Stage 4: project
| project TimeGenerated
, ObjectName
, ObjectClass
, ActivityType = AttributeValueDescription
, AttributeLDAP
, Account
, Domain
, Computer
, Channel
, EventID
, EventData
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Activity | match |
| field:"Activity" kind:match value:"modified" |
EventData | match |
| field:"EventData" kind:match |
EventData | regex_match |
| field:"EventData" kind:regex_match value:"(?i)CN=Sites" |
EventID | eq |
| field:"EventID" kind:eq value:"5136" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account | project |
ActivityType | project |
AttributeLDAP | project |
Channel | project |
Computer | project |
Domain | project |
EventData | project |
EventID | project |
ObjectClass | project |
ObjectName | project |
TimeGenerated | project |
Squid proxy events for ToR proxies
#'Check for Squid proxy events associated with common ToR proxies. This query presumes the default squid log format is being used. http://www.squid-cache.org/Doc/config/access_log/'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: 90d3f6ec-80fb-48e0-9937-2c70c9df9bad
name: Squid proxy events for ToR proxies
description: |
'Check for Squid proxy events associated with common ToR proxies. This query presumes the default squid log format is being used.
http://www.squid-cache.org/Doc/config/access_log/'
severity: Low
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1090
- T1008
query: |
let DomainList = dynamic(["tor2web.org", "tor2web.com", "torlink.co", "onion.to", "onion.ink", "onion.cab", "onion.nu", "onion.link",
"onion.it", "onion.city", "onion.direct", "onion.top", "onion.casa", "onion.plus", "onion.rip", "onion.dog", "tor2web.fi",
"tor2web.blutmagie.de", "onion.sh", "onion.lu", "onion.pet", "t2w.pw", "tor2web.ae.org", "tor2web.io", "tor2web.xyz", "onion.lt",
"s1.tor-gateways.de", "s2.tor-gateways.de", "s3.tor-gateways.de", "s4.tor-gateways.de", "s5.tor-gateways.de", "hiddenservice.net"]);
Syslog
| where ProcessName contains "squid"
| extend URL = extract("(([A-Z]+ [a-z]{4,5}:\\/\\/)|[A-Z]+ )([^ :]*)",3,SyslogMessage),
SourceIP = extract("([0-9]+ )(([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3}))",2,SyslogMessage),
Status = extract("(TCP_(([A-Z]+)(_[A-Z]+)*)|UDP_(([A-Z]+)(_[A-Z]+)*))",1,SyslogMessage),
HTTP_Status_Code = extract("(TCP_(([A-Z]+)(_[A-Z]+)*)|UDP_(([A-Z]+)(_[A-Z]+)*))/([0-9]{3})",8,SyslogMessage),
User = extract("(CONNECT |GET )([^ ]* )([^ ]+)",3,SyslogMessage),
RemotePort = extract("(CONNECT |GET )([^ ]*)(:)([0-9]*)",4,SyslogMessage),
Domain = extract("(([A-Z]+ [a-z]{4,5}:\\/\\/)|[A-Z]+ )([^ :\\/]*)",3,SyslogMessage),
Bytes = toint(extract("([A-Z]+\\/[0-9]{3} )([0-9]+)",2,SyslogMessage)),
contentType = extract("([a-z/]+$)",1,SyslogMessage)
| extend TLD = extract("\\.[a-z]*$",0,Domain)
| where HTTP_Status_Code == "200"
| where Domain contains "."
| where Domain has_any (DomainList)
| extend AccountName = tostring(split(User, "@")[0]), AccountUPNSuffix = tostring(split(User, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: User
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URL
version: 1.0.3
kind: Scheduled
Stages and Predicates
Let Binding: DomainList
let DomainList = dynamic(["tor2web.org", "tor2web.com", "torlink.co", "onion.to", "onion.ink", "onion.cab", "onion.nu", "onion.link",
"onion.it", "onion.city", "onion.direct", "onion.top", "onion.casa", "onion.plus", "onion.rip", "onion.dog", "tor2web.fi",
"tor2web.blutmagie.de", "onion.sh", "onion.lu", "onion.pet", "t2w.pw", "tor2web.ae.org", "tor2web.io", "tor2web.xyz", "onion.lt",
"s1.tor-gateways.de", "s2.tor-gateways.de", "s3.tor-gateways.de", "s4.tor-gateways.de", "s5.tor-gateways.de", "hiddenservice.net"]);
Stage 1: source
Syslog
Stage 2: where
| where ProcessName contains "squid"
Stage 3: extend
| extend URL = extract("(([A-Z]+ [a-z]{4,5}:\\/\\/)|[A-Z]+ )([^ :]*)",3,SyslogMessage),
SourceIP = extract("([0-9]+ )(([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3}))",2,SyslogMessage),
Status = extract("(TCP_(([A-Z]+)(_[A-Z]+)*)|UDP_(([A-Z]+)(_[A-Z]+)*))",1,SyslogMessage),
HTTP_Status_Code = extract("(TCP_(([A-Z]+)(_[A-Z]+)*)|UDP_(([A-Z]+)(_[A-Z]+)*))/([0-9]{3})",8,SyslogMessage),
User = extract("(CONNECT |GET )([^ ]* )([^ ]+)",3,SyslogMessage),
RemotePort = extract("(CONNECT |GET )([^ ]*)(:)([0-9]*)",4,SyslogMessage),
Domain = extract("(([A-Z]+ [a-z]{4,5}:\\/\\/)|[A-Z]+ )([^ :\\/]*)",3,SyslogMessage),
Bytes = toint(extract("([A-Z]+\\/[0-9]{3} )([0-9]+)",2,SyslogMessage)),
contentType = extract("([a-z/]+$)",1,SyslogMessage)
Stage 4: extend
| extend TLD = extract("\\.[a-z]*$",0,Domain)
Stage 5: where
| where HTTP_Status_Code == "200"
Stage 6: where
| where Domain contains "."
Stage 7: where
| where Domain has_any (DomainList)
Stage 8: extend
| extend AccountName = tostring(split(User, "@")[0]), AccountUPNSuffix = tostring(split(User, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Domain | contains |
| field:"Domain" kind:contains value:"." |
Domain | match |
| field:"Domain" kind:match |
HTTP_Status_Code | eq |
| field:"HTTP_Status_Code" kind:eq value:"200" |
ProcessName | contains |
| field:"process_name" kind:contains value:"squid" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Bytes | extend |
Domain | extend |
HTTP_Status_Code | extend |
RemotePort | extend |
SourceIP | extend |
Status | extend |
URL | extend |
User | extend |
contentType | extend |
TLD | extend |
AccountName | extend |
AccountUPNSuffix | extend |
Suspicious granting of permissions to an account
#Identifies IPs from which users grant access to other users on Azure resources and alerts when a previously unseen source IP address is used.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Telemetry Coverage
Rule Body
id: b2c15736-b9eb-4dae-8b02-3016b6a45a32
name: Suspicious granting of permissions to an account
description: |
'Identifies IPs from which users grant access to other users on Azure resources and alerts when a previously unseen source IP address is used.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AzureActivity
dataTypes:
- AzureActivity
- connectorId: BehaviorAnalytics
dataTypes:
- IdentityInfo
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1098
- T1548
query: |
let starttime = 14d;
let endtime = 1d;
// The number of operations above which an IP address is considered an unusual source of role assignment operations
let alertOperationThreshold = 5;
let AzureBuiltInRole = externaldata(Role:string,RoleDescription:string,ID:string) [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/AzureBuiltInRole.csv"] with (format="csv", ignoreFirstRecord=True);
let createRoleAssignmentActivity = AzureActivity
| where OperationNameValue =~ "microsoft.authorization/roleassignments/write";
let RoleAssignedActivity = createRoleAssignmentActivity
| where TimeGenerated between (ago(starttime) .. ago(endtime))
| summarize count() by CallerIpAddress, Caller, bin(TimeGenerated, 1d)
| where count_ >= alertOperationThreshold
// Returns all the records from the right side that don't have matches from the left.
| join kind = rightanti (
createRoleAssignmentActivity
| where TimeGenerated > ago(endtime)
| extend parsed_property = tostring(parse_json(Properties).requestbody)
| extend PrincipalId = case(parsed_property has_cs 'PrincipalId',parse_json(parsed_property).Properties.PrincipalId, parsed_property has_cs 'principalId',parse_json(parsed_property).properties.principalId,"")
| extend PrincipalType = case(parsed_property has_cs 'PrincipalType',parse_json(parsed_property).Properties.PrincipalType, parsed_property has_cs 'principalType',parse_json(parsed_property).properties.principalType, "")
| extend Scope = case(parsed_property has_cs 'Scope',parse_json(parsed_property).Properties.Scope, parsed_property has_cs 'scope',parse_json(parsed_property).properties.scope,"")
| extend RoleAddedDetails = case(parsed_property has_cs 'RoleDefinitionId',parse_json(parsed_property).Properties.RoleDefinitionId,parsed_property has_cs 'roleDefinitionId',parse_json(parsed_property).properties.roleDefinitionId,"")
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ActivityTimeStamp = make_set(TimeGenerated), ActivityStatusValue = make_set(ActivityStatusValue), CorrelationId = make_set(CorrelationId), ActivityCountByCallerIPAddress = count()
by ResourceId, CallerIpAddress, Caller, OperationNameValue, Resource, ResourceGroup, PrincipalId, PrincipalType, Scope, RoleAddedDetails
) on CallerIpAddress, Caller
| extend timestamp = StartTimeUtc, AccountCustomEntity = Caller, IPCustomEntity = CallerIpAddress;
let RoleAssignedActivitywithRoleDetails = RoleAssignedActivity
| extend RoleAssignedID = tostring(split(RoleAddedDetails, "/")[-1])
// Returns all matching records from left and right sides.
| join kind = inner (AzureBuiltInRole
) on $left.RoleAssignedID == $right.ID;
let CallerIPCountSummary = RoleAssignedActivitywithRoleDetails | summarize AssignmentCountbyCaller = count() by Caller, CallerIpAddress;
let RoleAssignedActivityWithCount = RoleAssignedActivitywithRoleDetails | join kind = inner (CallerIPCountSummary | project Caller, AssignmentCountbyCaller, CallerIpAddress) on Caller, CallerIpAddress;
RoleAssignedActivityWithCount
| summarize arg_max(StartTimeUtc, *) by PrincipalId, RoleAssignedID
// Returns all the records from the left side and only matching records from the right side.
| join kind = leftouter( IdentityInfo
| summarize arg_max(TimeGenerated, *) by AccountObjectId
) on $left.PrincipalId == $right.AccountObjectId
// Check if assignment count is greater than the threshold.
| where AssignmentCountbyCaller >= alertOperationThreshold
| project ActivityTimeStamp, OperationNameValue, Caller, CallerIpAddress, PrincipalId, RoleAssignedID, RoleAddedDetails, Role, RoleDescription, AccountUPN, AccountCreationTime, GroupMembership, UserType, ActivityStatusValue, ResourceGroup, PrincipalType, Scope, CorrelationId, timestamp, AccountCustomEntity, IPCustomEntity, AssignmentCountbyCaller
| extend Name = tostring(split(Caller,'@',0)[0]), UPNSuffix = tostring(split(Caller,'@',1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Caller
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: CallerIpAddress
version: 2.0.2
kind: Scheduled
Stages and Predicates
Parameters
let starttime = 14d;
let endtime = 1d;
let alertOperationThreshold = 5;
let createRoleAssignmentActivity, let RoleAssignedActivity, let RoleAssignedActivitywithRoleDetails and let RoleAssignedActivityWithCount are inlined into the numbered stages below.
Let Binding: AzureBuiltInRole
let AzureBuiltInRole = externaldata(Role:string,RoleDescription:string,ID:string) [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/AzureBuiltInRole.csv"] with (format="csv", ignoreFirstRecord=True);
Let Binding: CallerIPCountSummary
let CallerIPCountSummary = RoleAssignedActivitywithRoleDetails | summarize AssignmentCountbyCaller = count() by Caller, CallerIpAddress;
Stages 1 to 10 define let RoleAssignedActivityWithCount as the rule's main pipeline source. Stages 11 to 15 use it.
Stage 1: source
AzureActivity
Stage 2: where
where OperationNameValue =~ "microsoft.authorization/roleassignments/write"
Stage 3: where
where TimeGenerated between (ago(1209600s) .. ago(86400s))
Stage 4: summarize
summarize by CallerIpAddress, Caller
Stage 5: where
where count_ >= 5
Stage 6: join
| join kind = rightanti (
createRoleAssignmentActivity
| where TimeGenerated > ago(endtime)
| extend parsed_property = tostring(parse_json(Properties).requestbody)
| extend PrincipalId = case(parsed_property has_cs 'PrincipalId',parse_json(parsed_property).Properties.PrincipalId, parsed_property has_cs 'principalId',parse_json(parsed_property).properties.principalId,"")
| extend PrincipalType = case(parsed_property has_cs 'PrincipalType',parse_json(parsed_property).Properties.PrincipalType, parsed_property has_cs 'principalType',parse_json(parsed_property).properties.principalType, "")
| extend Scope = case(parsed_property has_cs 'Scope',parse_json(parsed_property).Properties.Scope, parsed_property has_cs 'scope',parse_json(parsed_property).properties.scope,"")
| extend RoleAddedDetails = case(parsed_property has_cs 'RoleDefinitionId',parse_json(parsed_property).Properties.RoleDefinitionId,parsed_property has_cs 'roleDefinitionId',parse_json(parsed_property).properties.roleDefinitionId,"")
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ActivityTimeStamp = make_set(TimeGenerated), ActivityStatusValue = make_set(ActivityStatusValue), CorrelationId = make_set(CorrelationId), ActivityCountByCallerIPAddress = count()
by ResourceId, CallerIpAddress, Caller, OperationNameValue, Resource, ResourceGroup, PrincipalId, PrincipalType, Scope, RoleAddedDetails
) on CallerIpAddress, Caller
Stage 7: extend
extend AccountCustomEntity, IPCustomEntity, timestamp
Stage 8: extend
extend RoleAssignedID
Stage 9: join
join kind=inner (AzureBuiltInRole) on $left.RoleAssignedID, $right.ID
Stage 10: join
join kind=inner (CallerIPCountSummary) on Caller, CallerIpAddress
Stage 11: summarize
summarize by PrincipalId, RoleAssignedID
Stage 12: join
| join kind = leftouter( IdentityInfo
| summarize arg_max(TimeGenerated, *) by AccountObjectId
) on $left.PrincipalId == $right.AccountObjectId
Stage 13: where
where AssignmentCountbyCaller >= 5
Stage 14: project
project AccountCreationTime, AccountCustomEntity, AccountUPN, ActivityStatusValue, ActivityTimeStamp, AssignmentCountbyCaller, Caller, CallerIpAddress, CorrelationId, GroupMembership, IPCustomEntity, OperationNameValue, PrincipalId, PrincipalType, ResourceGroup, Role, RoleAddedDetails, RoleAssignedID, RoleDescription, Scope, UserType, timestamp
Stage 15: extend
extend Name, UPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AssignmentCountbyCaller | ge |
| field:"AssignmentCountbyCaller" kind:ge value:"5" |
OperationNameValue | eq |
| field:"azure_ad::operation_name_value" kind:eq |
count_ | ge |
| field:"count_" kind:ge value:"5" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCreationTime | project |
AccountCustomEntity | project |
AccountUPN | project |
ActivityStatusValue | project |
ActivityTimeStamp | project |
AssignmentCountbyCaller | project |
Caller | project |
CallerIpAddress | project |
CorrelationId | project |
GroupMembership | project |
IPCustomEntity | project |
OperationNameValue | project |
PrincipalId | project |
PrincipalType | project |
ResourceGroup | project |
Role | project |
RoleAddedDetails | project |
RoleAssignedID | project |
RoleDescription | project |
Scope | project |
UserType | project |
timestamp | project |
Name | extend |
UPNSuffix | extend |
TIE Indicators of Exposures
#'Searches for triggered Indicators of Exposures.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: f6ae2eb2-97c9-4e0f-ae73-7420ef80d99d
name: TIE Indicators of Exposures
description: |
'Searches for triggered Indicators of Exposures.'
severity: Low
requiredDataConnectors:
- connectorId: TenableIE
dataTypes:
- Tenable_IE_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
// For the query to work properly, make sure you have imported the afad_parser.yaml parser into the workspace
// Retrieve the parser here: https://aka.ms/sentinel-TenableApp-afad-parser
// Then, create the Kusto Function with alias afad_parser
let SeverityTable=datatable(Severity:string,Level:int) [
"low", 1,
"medium", 2,
"high", 3,
"critical", 4
];
afad_parser
| where MessageType == 0
| lookup kind=leftouter SeverityTable on Severity
| order by Level
| extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
version: 1.0.1
kind: Scheduled
Stages and Predicates
Let Binding: SeverityTable
let SeverityTable = datatable(Severity:string,Level:int) [
"low", 1,
"medium", 2,
"high", 3,
"critical", 4
];
Stage 1: source
afad_parser
Stage 2: where
| where MessageType == 0
Stage 3: kusto:lookup
lookup kind=leftouter (SeverityTable) on Severity
Stage 4: sort
sort by Level
Stage 5: extend
extend DnsDomain, HostName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
MessageType | eq |
| field:"MessageType" kind:eq value:"0" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DnsDomain | extend |
HostName | extend |
Tomcat - Multiple server errors from single IP address
#'Detects multiple server errors from one source in short timeframe'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Impact |
Rule Body
id: de9df79c-4872-11ec-81d3-0242ac130003
name: Tomcat - Multiple server errors from single IP address
description: |
'Detects multiple server errors from one source in short timeframe'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
datatypes:
- Tomcat_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
- InitialAccess
relevantTechniques:
- T1498
- T1190
- T1133
query: |
let threshold = 100;
TomcatEvent
| where HttpStatusCode >= 500 and HttpStatusCode <= 599
| summarize MultipleServerErrors = count() by SrcIpAddr, bin(TimeGenerated, 5m)
| where MultipleServerErrors > threshold
| extend IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 100;
Stage 1: source
TomcatEvent
Stage 2: where
| where HttpStatusCode >= 500 and HttpStatusCode <= 599
Stage 3: summarize
| summarize MultipleServerErrors = count() by SrcIpAddr, bin(TimeGenerated, 5m)
Stage 4: where
| where MultipleServerErrors > threshold
Stage 5: extend
| extend IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MultipleServerErrors | summarize |
SrcIpAddr | summarize |
IPCustomEntity | extend |