Detection rules › Kusto
Kusto rules: 36
AD account with Don't Expire Password
#Identifies whenever a user account has the setting "Password Never Expires" in the user account properties selected. This is indicated in Security event 4738 in the EventData item labeled UserAccountControl with an included value of %%2089. %%2089 resolves to "Don't Expire Password - Enabled".
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 4738: A user account was changed. |
Rule Body
id: 6c360107-f3ee-4b91-9f43-f4cfd90441cf
name: AD account with Don't Expire Password
description: |
'Identifies whenever a user account has the setting "Password Never Expires" in the user account properties selected.
This is indicated in Security event 4738 in the EventData item labeled UserAccountControl with an included value of %%2089.
%%2089 resolves to "Don't Expire Password - Enabled".'
severity: Low
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsForwardedEvents
dataTypes:
- WindowsEvent
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1098
query: |
union isfuzzy=true
(
SecurityEvent
| where EventID == 4738
// 2089 value indicates the Don't Expire Password value has been set
| where UserAccountControl has "%%2089"
| extend Value_2089 = iff(UserAccountControl has "%%2089","'Don't Expire Password' - Enabled", "Not Changed")
// 2050 indicates that the Password Not Required value is NOT set, this often shows up at the same time as a 2089 and is the recommended value. This value may not be in the event.
| extend Value_2050 = iff(UserAccountControl has "%%2050","'Password Not Required' - Disabled", "Not Changed")
// If value %%2082 is present in the 4738 event, this indicates the account has been configured to logon WITHOUT a password. Generally you should only see this value when an account is created and only in Event 4720: Account Creation Event.
| extend Value_2082 = iff(UserAccountControl has "%%2082","'Password Not Required' - Enabled", "Not Changed")
| project StartTime = TimeGenerated, EventID, Activity, Computer, TargetAccount, TargetUserName, TargetDomainName, TargetSid,
AccountType, UserAccountControl, Value_2089, Value_2050, Value_2082, SubjectAccount, SubjectUserName, SubjectDomainName, SubjectUserSid
),
(
WindowsEvent
| where EventID == 4738 and EventData has '2089'
// 2089 value indicates the Don't Expire Password value has been set
| extend UserAccountControl = tostring(EventData.UserAccountControl)
| where UserAccountControl has "%%2089"
| extend Value_2089 = iff(UserAccountControl has "%%2089","'Don't Expire Password' - Enabled", "Not Changed")
// 2050 indicates that the Password Not Required value is NOT set, this often shows up at the same time as a 2089 and is the recommended value. This value may not be in the event.
| extend Value_2050 = iff(UserAccountControl has "%%2050","'Password Not Required' - Disabled", "Not Changed")
// If value %%2082 is present in the 4738 event, this indicates the account has been configured to logon WITHOUT a password. Generally you should only see this value when an account is created and only in Event 4720: Account Creation Event.
| extend Value_2082 = iff(UserAccountControl has "%%2082","'Password Not Required' - Enabled", "Not Changed")
| extend Activity="4738 - A user account was changed."
| extend TargetAccount = strcat(EventData.TargetDomainName,"\\", EventData.TargetUserName)
| extend TargetSid = tostring(EventData.TargetSid)
| extend SubjectAccount = strcat(EventData.SubjectDomainName,"\\", EventData.SubjectUserName)
| extend SubjectUserSid = tostring(EventData.SubjectUserSid)
| extend AccountType=case(SubjectAccount endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
| project StartTime = TimeGenerated, EventID, Activity, Computer, TargetAccount, TargetUserName = tostring(EventData.TargetUserName), TargetDomainName = tostring(EventData.TargetDomainName), TargetSid,
AccountType, UserAccountControl, Value_2089, Value_2050, Value_2082, SubjectAccount, SubjectDomainName = tostring(EventData.SubjectDomainName), SubjectUserName = tostring(EventData.SubjectUserName), SubjectUserSid = tostring(EventData.SubjectUserSid)
)
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| project-away DomainIndex
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: TargetAccount
- identifier: Name
columnName: TargetUserName
- identifier: NTDomain
columnName: TargetDomainName
- entityType: Account
fieldMappings:
- identifier: Sid
columnName: TargetSid
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: SubjectAccount
- identifier: Name
columnName: SubjectUserName
- identifier: NTDomain
columnName: SubjectDomainName
- entityType: Account
fieldMappings:
- identifier: Sid
columnName: SubjectUserSid
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: HostNameDomain
version: 1.2.2
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others", "Identity" ]
Stages and Predicates
Union isfuzzy=true (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: SecurityEvent, WindowsEvent
Leg 1: SecurityEvent
SecurityEvent
| where EventID == 4738
| where UserAccountControl has "%%2089"
| extend Value_2089 = iff(UserAccountControl has "%%2089","'Don't Expire Password' - Enabled", "Not Changed")
| extend Value_2050 = iff(UserAccountControl has "%%2050","'Password Not Required' - Disabled", "Not Changed")
| extend Value_2082 = iff(UserAccountControl has "%%2082","'Password Not Required' - Enabled", "Not Changed")
| project StartTime = TimeGenerated, EventID, Activity, Computer, TargetAccount, TargetUserName, TargetDomainName, TargetSid,
AccountType, UserAccountControl, Value_2089, Value_2050, Value_2082, SubjectAccount, SubjectUserName, SubjectDomainName, SubjectUserSid
Leg 2: WindowsEvent
WindowsEvent
| where EventID == 4738 and EventData has '2089'
| extend UserAccountControl = tostring(EventData.UserAccountControl)
| where UserAccountControl has "%%2089"
| extend Value_2089 = iff(UserAccountControl has "%%2089","'Don't Expire Password' - Enabled", "Not Changed")
| extend Value_2050 = iff(UserAccountControl has "%%2050","'Password Not Required' - Disabled", "Not Changed")
| extend Value_2082 = iff(UserAccountControl has "%%2082","'Password Not Required' - Enabled", "Not Changed")
| extend Activity="4738 - A user account was changed."
| extend TargetAccount = strcat(EventData.TargetDomainName,"\\", EventData.TargetUserName)
| extend TargetSid = tostring(EventData.TargetSid)
| extend SubjectAccount = strcat(EventData.SubjectDomainName,"\\", EventData.SubjectUserName)
| extend SubjectUserSid = tostring(EventData.SubjectUserSid)
| extend AccountType=case(SubjectAccount endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
| project StartTime = TimeGenerated, EventID, Activity, Computer, TargetAccount, TargetUserName = tostring(EventData.TargetUserName), TargetDomainName = tostring(EventData.TargetDomainName), TargetSid,
AccountType, UserAccountControl, Value_2089, Value_2050, Value_2082, SubjectAccount, SubjectDomainName = tostring(EventData.SubjectDomainName), SubjectUserName = tostring(EventData.SubjectUserName), SubjectUserSid = tostring(EventData.SubjectUserSid)
Applied to the Combined Result
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| project-away DomainIndex
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventData | match |
| field:"EventData" kind:match value:"2089" |
EventID | eq |
| field:"EventID" kind:eq value:"4738" |
UserAccountControl | match |
| field:"UserAccountControl" kind:match value:"%%2089" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountType | project |
Activity | project |
Computer | project |
EventID | project |
StartTime | project |
SubjectAccount | project |
SubjectDomainName | project |
SubjectUserName | project |
SubjectUserSid | project |
TargetAccount | project |
TargetDomainName | project |
TargetSid | project |
TargetUserName | project |
UserAccountControl | project |
Value_2050 | project |
Value_2082 | project |
Value_2089 | project |
HostName | extend |
HostNameDomain | extend |
AWSCloudTrail - SSM document is publicly exposed
#Detects an AWS Systems Manager (SSM) document that has been made publicly accessible, which could lead to sensitive information exposure. Verify the document configurations and confirm the change was authorized.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Telemetry Coverage
Rule Body
id: 75647b58-bcc8-4eb5-9658-46698d3fa153
name: AWSCloudTrail - SSM document is publicly exposed
description: |
Detects an AWS Systems Manager (SSM) document that has been made publicly accessible, which could lead to sensitive information exposure. Verify the document configurations and confirm the change was authorized.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AWS
dataTypes:
- AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1526
query: |
AWSCloudTrail
| where EventName == "ModifyDocumentPermission" and isempty(ErrorCode) and isempty(ErrorMessage)
| where todynamic(parse_json(RequestParameters).["accountIdsToAdd"]) == '["all"]'
| 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]), "")
| project TimeGenerated, EventName, EventTypeName, UserIdentityAccountId, UserIdentityPrincipalid, UserAgent,UserIdentityUserName, SessionMfaAuthenticated, RecipientAccountId, AccountName, AccountUPNSuffix, SourceIpAddress, AWSRegion, EventSource, AdditionalEventData, RequestParameters, ResponseElements, UserIdentityArn
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- identifier: CloudAppAccountId
columnName: RecipientAccountId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIpAddress
customDetails:
EventName: EventName
AWSRegion: AWSRegion
UserAgent: UserAgent
RequestParameters: RequestParameters
alertDetailsOverride:
alertDisplayNameFormat: SSM document publicly exposed by {{AccountName}} in {{AWSRegion}}
alertDescriptionFormat: User {{AccountName}} modified SSM document permissions to expose it publicly from {{SourceIpAddress}} in region {{AWSRegion}}.
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
AWSCloudTrail
Stage 2: where
| where EventName == "ModifyDocumentPermission" and isempty(ErrorCode) and isempty(ErrorMessage)
Stage 3: where
| where todynamic(parse_json(RequestParameters).["accountIdsToAdd"]) == '["all"]'
Stage 4: 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 5: project
| project TimeGenerated, EventName, EventTypeName, UserIdentityAccountId, UserIdentityPrincipalid, UserAgent,UserIdentityUserName, SessionMfaAuthenticated, RecipientAccountId, AccountName, AccountUPNSuffix, SourceIpAddress, AWSRegion, EventSource, AdditionalEventData, RequestParameters, ResponseElements, UserIdentityArn
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ErrorCode | is_null | field:"aws::errorCode" kind:is_null | |
ErrorMessage | is_null | field:"aws::errorMessage" kind:is_null | |
EventName | eq |
| field:"aws::eventName" kind:eq value:"ModifyDocumentPermission" |
RequestParameters.accountIdsToAdd | eq |
| field:"RequestParameters.accountIdsToAdd" kind:eq |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AWSRegion | project |
AccountName | project |
AccountUPNSuffix | project |
AdditionalEventData | project |
EventName | project |
EventSource | project |
EventTypeName | project |
RecipientAccountId | project |
RequestParameters | project |
ResponseElements | project |
SessionMfaAuthenticated | project |
SourceIpAddress | project |
TimeGenerated | project |
UserAgent | project |
UserIdentityAccountId | project |
UserIdentityArn | project |
UserIdentityPrincipalid | project |
UserIdentityUserName | project |
Certified Pre-Owned - TGTs requested with certificate authentication
#This query identifies someone using machine certificates to request Kerberos Ticket Granting Tickets (TGTs).
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 4768: A Kerberos authentication ticket (TGT) was requested. |
Rule Body
id: b838a13c-052e-45b8-a5ac-7d3eb62efa11
name: Certified Pre-Owned - TGTs requested with certificate authentication
description: |
This query identifies someone using machine certificates to request Kerberos Ticket Granting Tickets (TGTs).
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1036
query: |
let timeframe=1h;
SecurityEvent
| where TimeGenerated >= ago(timeframe)
| where EventID == 4768
| project TimeGenerated, Computer, TargetAccount, EventData=parse_xml(EventData)
| mv-apply d=EventData.EventData.Data on
(
where d["@Name"]=="CertIssuerName"
| project CIN=tostring(d["#text"])
)
| where not(isempty(CIN))
// <DECISION - 1>
// In some environments, we see a lot of certs starting with a sid and containing live.com. Comment out the next line if you have that as well.
//| where not(CIN startswith "S-1-")
// <DECISION - 2>
// If you're seeing a significant number of machine accounts, it might be due to 802.1X or SCCM. https://twitter.com/MagnusMOD/status/1407800853088591872?s=20.
// The following line allows you to filter out all endpoints. This does introduce a blindspot, and you need a custom function which provides data about (on-prem) AD machines.
// Alternatively, you can use DeviceInfo, if you're ingesting that data from MDE.
| parse CIN with "CN=" MachineName
//| join kind=leftouter MyCustomLookupFunction on $left.MachineName == $right.CN
//| where not(OperatingSystem startswith "Windows 10")
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: TargetAccount
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let timeframe = 1h;
Stage 1: source
SecurityEvent
Stage 2: where
| where TimeGenerated >= ago(timeframe)
Stage 3: where
| where EventID == 4768
Stage 4: project
| project TimeGenerated, Computer, TargetAccount, EventData=parse_xml(EventData)
Stage 5: kusto:mv-apply
| mv-apply d=EventData.EventData.Data on
(
where d["@Name"]=="CertIssuerName"
| project CIN=tostring(d["#text"])
)
Stage 6: where
| where not(isempty(CIN))
Stage 7: parse
| parse CIN with "CN=" MachineName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventID | eq |
| field:"EventID" kind:eq value:"4768" |
d["@Name"] | eq |
|
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
CIN | is_null | excludes:CIN |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Computer | project |
EventData | project |
TargetAccount | project |
TimeGenerated | project |
Contrast ADR - WAF Alert Correlation
#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.
'Correlates Contrast ADR security alerts with WAF logs to identify confirmed attack attempts that were either exploited or blocked. This rule helps security teams prioritize incidents by focusing on attacks that have been validated by application security monitoring.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Stealth | |
| Command & Control |
Rule Body
id: 93641436-afb3-4921-8828-ceab0d15aaab
name: Contrast ADR - WAF Alert Correlation
description: |
'Correlates Contrast ADR security alerts with WAF logs to identify confirmed attack attempts that were either exploited or blocked. This rule helps security teams prioritize incidents by focusing on attacks that have been validated by application security monitoring.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: ContrastADRCCF
dataTypes:
- ContrastADRAttackEvents_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- DefenseEvasion
- CommandAndControl
relevantTechniques:
- T1190
- T1211
- T1008
query: |
ContrastADRAttackEvents_CL
| where result =~ "exploited" or result =~ "blocked"
| project-rename ip=sourceIp
//please add you WAF table in place of ContrastWAFLogs_CL and WAF tables source IP or target IP column's inplace of ip and uncomment the queries below
//| join kind=inner (ContrastWAFLogs_CL | where TimeGenerated >= ago(5m)) on ip
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ip
alertDetailsOverride:
alertDisplayNameFormat: 'WAF Alert Confirmed {{result}} by Contrast ADR on {{request_headers_referer}} endpoint of {{application_name}} '
alertDescriptionFormat: 'WAF Alert Confirmed {{result}} by Contrast ADR on {{request_headers_referer}} endpoint of {{application_name}} '
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: PT1H
matchingMethod: Selected
groupByEntities:
- IP
eventGroupingSettings:
aggregationKind: AlertPerResult
kind: Scheduled
version: 1.0.1
Stages and Predicates
Stage 1: source
ContrastADRAttackEvents_CL
Stage 2: where
| where result =~ "exploited" or result =~ "blocked"
Stage 3: project-rename
| project-rename ip=sourceIp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
result | eq |
| field:"result" kind:eq |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ip | project-rename |
Cyble Vision Alerts TOR Links
#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.
'Detects Tor marketplace, darkweb link, and onion domain alerts from CybleVision. Uses the Alerts_TorLinks parser to extract onion URLs, search engines, search keywords, content, and identifiers. Incidents are grouped by service.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Resource Development |
Rule Body
id: b9df1ec4-a572-4448-8da1-1bc4b7e1687f
name: Cyble Vision Alerts TOR Links
description: |
'Detects Tor marketplace, darkweb link, and onion domain alerts from CybleVision. Uses the Alerts_TorLinks parser to extract onion URLs, search engines, search keywords, content, and identifiers. Incidents are grouped by service.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
dataTypes:
- CybleVisionAlerts_CL
enabled: true
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: PT5H
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics:
- ResourceDevelopment
- Reconnaissance
relevantTechniques:
- T1583
- T1590
- T1595
subTechniques: []
query: |
Alerts_tor_links
| where Service == "tor_links"
| extend MappedSeverity = Severity
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
alertDetailsOverride:
alertDisplayNameFormat: CybleVision Tor Link Alert ({{KeywordName}})
alertDescriptionFormat: |
Source {{TL_SearchEngine}}. Tor URL {{TL_TorUrl}}. Description {{Description}}"
alertDynamicProperties: []
customDetails:
MappedSeverity: Severity
Status: Status
AlertID: AlertID
Service: Service
SearchEngine: Search_Engine
SearchKeyword: TL_SearchKeyword
TOR_Doc: TL_Doc_Data
DocId: TL_Doc_Id
UpdatedOn: TL_UpdatedOn
Tags: Tags
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: TL_TorUrl
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: Domain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IP
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Alerts_tor_links
Stage 2: where
| where Service == "tor_links"
Stage 3: extend
| extend MappedSeverity = Severity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Service | eq |
| field:"Service" kind:eq value:"tor_links" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MappedSeverity | extend |
CYFIRMA - Attack Surface - Open Ports Medium Rule
#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.
"This rule is triggered when CYFIRMA identifies open and publicly accessible ports on internet-facing assets. Exposed services may include SSH, RDP, HTTP, or other potentially sensitive ports, increasing the risk of unauthorized access, exploitation, or reconnaissance by threat actors. Monitoring open ports is critical to reducing the external attack surface and preventing misuse through brute force, service vulnerabilities, or protocol exploitation."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Persistence | |
| Command & Control |
Rule Body
id: 9e18b6c3-d172-4bc6-a7d9-cc7b0a03a69e
name: CYFIRMA - Attack Surface - Open Ports Medium Rule
description: |
"This rule is triggered when CYFIRMA identifies open and publicly accessible ports on internet-facing assets. Exposed services may include SSH, RDP, HTTP, or other potentially sensitive ports, increasing the risk of unauthorized access, exploitation, or reconnaissance by threat actors. Monitoring open ports is critical to reducing the external attack surface and preventing misuse through brute force, service vulnerabilities, or protocol exploitation."
version: 1.0.1
kind: Scheduled
severity: Medium
requiredDataConnectors:
- connectorId: CyfirmaAttackSurfaceAlertsConnector
dataTypes:
- CyfirmaASOpenPortsAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- InitialAccess
- CommandAndControl
- Discovery
- DefenseEvasion
- Persistence
relevantTechniques:
- T1566
- T1071
- T1505
query: |
// Medium Severity - Open Ports Exposure Detected
let timeFrame = 5m;
CyfirmaASOpenPortsAlerts_CL
| where severity == 'High' and TimeGenerated between (ago(timeFrame) .. now())
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
RiskScore=risk_score,
Domain=sub_domain,
TopDomain=top_domain,
NetworkIP=ip,
AlertUID=alert_uid,
UID=uid,
WebServer=web_server,
WebServerVersion=web_server_version,
OpenPorts=open_ports,
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT'
| project
TimeGenerated,
Description,
Domain,
TopDomain,
RiskScore,
FirstSeen,
LastSeen,
NetworkIP,
AlertUID,
UID,
WebServer,
WebServerVersion,
OpenPorts,
ProductName,
ProviderName
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: Domain
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: TopDomain
- identifier: DnsDomain
columnName: Domain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: NetworkIP
customDetails:
TimeGenerated: TimeGenerated
RiskScore: RiskScore
FirstSeen: FirstSeen
LastSeen: LastSeen
AlertUID: AlertUID
UID: UID
WebServer: WebServer
WebServerVersion: WebServerVersion
OpenPorts: OpenPorts
Description: Description
alertDetailsOverride:
alertDisplayNameFormat: "CYFIRMA - Medium Severity Open Ports Exposure Detected on Assets - Domain: {{Domain}}, IP: {{NetworkIP}}"
alertDescriptionFormat: "CYFIRMA - Medium Severity Open Ports Exposure Detected on Assets - {{Description}}"
alertDynamicProperties:
- alertProperty: ProductName
value: "DeCYFIR/DeTCT"
- alertProperty: ProviderName
value: "CYFIRMA"
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaASOpenPortsAlerts_CL
Stage 2: where
| where severity == 'High' and TimeGenerated between (ago(timeFrame) .. now())
Stage 3: extend
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
RiskScore=risk_score,
Domain=sub_domain,
TopDomain=top_domain,
NetworkIP=ip,
AlertUID=alert_uid,
UID=uid,
WebServer=web_server,
WebServerVersion=web_server_version,
OpenPorts=open_ports,
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT'
Stage 4: project
| project
TimeGenerated,
Description,
Domain,
TopDomain,
RiskScore,
FirstSeen,
LastSeen,
NetworkIP,
AlertUID,
UID,
WebServer,
WebServerVersion,
OpenPorts,
ProductName,
ProviderName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
severity | eq |
| field:"severity" kind:eq value:"High" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertUID | project |
Description | project |
Domain | project |
FirstSeen | project |
LastSeen | project |
NetworkIP | project |
OpenPorts | project |
ProductName | project |
ProviderName | project |
RiskScore | project |
TimeGenerated | project |
TopDomain | project |
UID | project |
WebServer | project |
WebServerVersion | project |
CYFIRMA - Brand Intelligence - Executive/People Impersonation Medium Rule
#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.
"This rule detects potential impersonation of executive or high-profile individuals across digital platforms such as social media. Such impersonation can be used to mislead stakeholders, perform social engineering attacks, or cause reputational damage to the organization. Timely detection is crucial to assess risk and take down malicious profiles to protect brand and executive identity."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Resource Development | |
| Initial Access |
Rule Body
id: 59aa22f2-5b4f-4679-b289-003228255413
name: CYFIRMA - Brand Intelligence - Executive/People Impersonation Medium Rule
description: |
"This rule detects potential impersonation of executive or high-profile individuals across digital platforms such as social media.
Such impersonation can be used to mislead stakeholders, perform social engineering attacks, or cause reputational damage to the organization.
Timely detection is crucial to assess risk and take down malicious profiles to protect brand and executive identity."
version: 1.0.1
kind: Scheduled
severity: Medium
requiredDataConnectors:
- connectorId: CyfirmaBrandIntelligenceAlertsDC
dataTypes:
- CyfirmaBIExecutivePeopleAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- Reconnaissance
- ResourceDevelopment
- InitialAccess
relevantTechniques:
- T1589.003
- T1585.001
- T1566.002
query: |
// Medium severity - Executive/People Impersonation
let timeFrame = 5m;
CyfirmaBIExecutivePeopleAlerts_CL
| where severity == 'High' and TimeGenerated between (ago(timeFrame) .. now())
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
RiskScore=risk_score,
AlertUID=alert_uid,
UID=uid,
AssetType=asset_type,
AssetValue=asset_value,
Impact=impact,
Recommendation=recommendation,
PostedDate=posted_date,
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT'
| project
TimeGenerated,
Description,
RiskScore,
FirstSeen,
LastSeen,
AlertUID,
UID,
AssetType,
AssetValue,
Impact,
Recommendation,
PostedDate,
ProductName,
ProviderName
customDetails:
AssetType: AssetType
AssetValue: AssetValue
Description: Description
TimeGenerated: TimeGenerated
FirstSeen: FirstSeen
LastSeen: LastSeen
AlertUID: AlertUID
UID: UID
Impact: Impact
Recommendation: Recommendation
PostedDate: PostedDate
RiskScore: RiskScore
alertDetailsOverride:
alertDisplayNameFormat: "CYFIRMA - Medium Severity Alert - Executive Impersonation - Suspicious Social Media Account Detected - {{AssetValue}} "
alertDescriptionFormat: "{{Description}} "
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaBIExecutivePeopleAlerts_CL
Stage 2: where
| where severity == 'High' and TimeGenerated between (ago(timeFrame) .. now())
Stage 3: extend
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
RiskScore=risk_score,
AlertUID=alert_uid,
UID=uid,
AssetType=asset_type,
AssetValue=asset_value,
Impact=impact,
Recommendation=recommendation,
PostedDate=posted_date,
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT'
Stage 4: project
| project
TimeGenerated,
Description,
RiskScore,
FirstSeen,
LastSeen,
AlertUID,
UID,
AssetType,
AssetValue,
Impact,
Recommendation,
PostedDate,
ProductName,
ProviderName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
severity | eq |
| field:"severity" kind:eq value:"High" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertUID | project |
AssetType | project |
AssetValue | project |
Description | project |
FirstSeen | project |
Impact | project |
LastSeen | project |
PostedDate | project |
ProductName | project |
ProviderName | project |
Recommendation | project |
RiskScore | project |
TimeGenerated | project |
UID | project |
CYFIRMA - Medium severity TOR Node Network Indicators - Monitor Recommended Rule
#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.
"This KQL query identifies network-based indicators from CYFIRMA intelligence that are associated with the role 'TOR'. These indicators may include IP addresses, domains, and URLs related to Tor network activity. Threat actors often use Tor for anonymous communication, command and control, data exfiltration, and evasion of network defenses."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Initial Access | |
| Persistence | |
| Command & Control | |
| Exfiltration |
Rule Body
id: e41b7640-9ba6-42d6-a4c9-1ab6932a0b14
name: CYFIRMA - Medium severity TOR Node Network Indicators - Monitor Recommended Rule
description: |
"This KQL query identifies network-based indicators from CYFIRMA intelligence that are associated with the role 'TOR'.
These indicators may include IP addresses, domains, and URLs related to Tor network activity.
Threat actors often use Tor for anonymous communication, command and control, data exfiltration, and evasion of network defenses."
version: 1.0.1
kind: Scheduled
severity: Medium
enabled: false
requiredDataConnectors:
- connectorId: CyfirmaCyberIntelligenceDC
dataTypes:
- CyfirmaIndicators_CL
query: |
//TOR Node Network Indicators - Monitor Recommended
let timeFrame= 5m;
CyfirmaIndicators_CL
| where (ConfidenceScore < 80 and ConfidenceScore >= 50)
and TimeGenerated between (ago(timeFrame) .. now())
and pattern !contains 'file:hashes' and RecommendedActions has 'Monitor' and Roles has 'TOR'
| extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
| extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)
| mv-expand extensionKeys
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
extension_id = extensionKeyStr,
ASN_Owner = props.asn_owner,
ASN = props.asn,
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
| project
IPv4,
IPv6,
URL,
Domain,
ThreatActors,
RecommendedActions,
Sources,
Roles,
Country,
IPAbuse,
name,
Description,
ConfidenceScore,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
SecurityVendors,
ProductName,
ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
- CommandAndControl
- Exfiltration
- InitialAccess
- Persistence
- Reconnaissance
relevantTechniques:
- T1090
- T1572
- T1048
- T1071
- T1189
- T1505
- T1595
- T1090.003
- T1048.002
- T1071.001
- T1505.003
- T1595.002
alertDetailsOverride:
alertDisplayNameFormat: "High-Confidence TOR Node Network Indicators - Monitor Recommended - {{name}} "
alertDescriptionFormat: "{{Description}} - {{name}} "
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
customDetails:
ThreatActors: ThreatActors
Sources: Sources
RecommendedActions: RecommendedActions
Roles: Roles
Country: Country
Description: Description
ConfidenceScore: ConfidenceScore
SecurityVendors: SecurityVendors
IndicatorID: IndicatorID
Created: created
Modified: modified
ValidFrom: valid_from
Tags: Tags
ThreatType: ThreatType
TimeGenerated: TimeGenerated
IPAbuse: IPAbuse
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPv4
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPv6
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: Domain
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URL
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaIndicators_CL
Stage 2: where
| where (ConfidenceScore < 80 and ConfidenceScore >= 50)
and TimeGenerated between (ago(timeFrame) .. now())
and pattern !contains 'file:hashes' and RecommendedActions has 'Monitor' and Roles has 'TOR'
Stage 3: extend (6 consecutive steps)
| extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
| extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)
Stage 4: mv-expand
| mv-expand extensionKeys
Stage 5: extend (4 consecutive steps)
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
extension_id = extensionKeyStr,
ASN_Owner = props.asn_owner,
ASN = props.asn,
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
Stage 6: project
| project
IPv4,
IPv6,
URL,
Domain,
ThreatActors,
RecommendedActions,
Sources,
Roles,
Country,
IPAbuse,
name,
Description,
ConfidenceScore,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
SecurityVendors,
ProductName,
ProviderName
Indicators
These rows show field, operator, and value matches.
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
pattern | contains | file:hashes | excludes:pattern field:"pattern" value:"file:hashes" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ConfidenceScore | project |
Country | project |
Description | project |
Domain | project |
IPAbuse | project |
IPv4 | project |
IPv6 | project |
IndicatorID | project |
ProductName | project |
ProviderName | project |
RecommendedActions | project |
Roles | project |
SecurityVendors | project |
Sources | project |
Tags | project |
ThreatActors | project |
ThreatType | project |
TimeGenerated | project |
URL | project |
created | project |
modified | project |
name | project |
valid_from | project |
DSRM Account Abuse
#This query detects an abuse of the DSRM account in order to maintain persistence and access to the organization's Active Directory. Ref: https://adsecurity.org/?p=1785
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Sysmon | Event ID 13: RegistryEvent (Value Set) |
Rule Body
id: 979c42dd-533e-4ede-b18b-31a84ba8b3d6
name: DSRM Account Abuse
description: |
'This query detects an abuse of the DSRM account in order to maintain persistence and access to the organization's Active Directory.
Ref: https://adsecurity.org/?p=1785'
severity: High
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1098
query: |
Event
| where EventLog == "Microsoft-Windows-Sysmon/Operational" and EventID in (13)
| parse EventData with * 'ProcessId">' ProcessId "<"* 'Image">' Image "<" * 'TargetObject">' TargetObject "<" * 'Details">' Details "<" * 'User">' User "<" *
| where TargetObject has ("HKLM\\System\\CurrentControlSet\\Control\\Lsa\\DsrmAdminLogonBehavior") and Details == "DWORD (0x00000002)"
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventID, Computer, User, ProcessId, Image, TargetObject, Details, _ResourceId
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend AccountName = tostring(split(User, "\\")[1]), AccountNTDomain = tostring(split(User, "\\")[0])
| extend ImageFileName = tostring(split(Image, "\\")[-1])
| extend ImageDirectory = replace_string(Image, ImageFileName, "")
| project-away DomainIndex
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: HostNameDomain
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: User
- identifier: Name
columnName: AccountName
- identifier: NTDomain
columnName: AccountNTDomain
- entityType: Process
fieldMappings:
- identifier: ProcessId
columnName: ProcessId
- entityType: File
fieldMappings:
- identifier: Name
columnName: ImageFileName
- identifier: Directory
columnName: ImageDirectory
- entityType: RegistryKey
fieldMappings:
- identifier: Key
columnName: TargetObject
version: 1.0.3
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Vasileios Paschalidis
support:
tier: Community
categories:
domains: [ "Security - Others" ]
Stages and Predicates
Stage 1: source
Event
Stage 2: where
| where EventLog == "Microsoft-Windows-Sysmon/Operational" and EventID in (13)
Stage 3: parse
| parse EventData with * 'ProcessId">' ProcessId "<"* 'Image">' Image "<" * 'TargetObject">' TargetObject "<" * 'Details">' Details "<" * 'User">' User "<" *
Stage 4: where
| where TargetObject has ("HKLM\\System\\CurrentControlSet\\Control\\Lsa\\DsrmAdminLogonBehavior") and Details == "DWORD (0x00000002)"
Stage 5: summarize
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventID, Computer, User, ProcessId, Image, TargetObject, Details, _ResourceId
Stage 6: extend (5 consecutive steps)
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend AccountName = tostring(split(User, "\\")[1]), AccountNTDomain = tostring(split(User, "\\")[0])
| extend ImageFileName = tostring(split(Image, "\\")[-1])
| extend ImageDirectory = replace_string(Image, ImageFileName, "")
Stage 7: project-away
| project-away DomainIndex
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Details | eq |
| field:"Details" kind:eq value:"DWORD (0x00000002)" |
EventID | in |
| field:"EventID" kind:in value:"13" |
EventLog | eq |
| field:"EventLog" kind:eq value:"Microsoft-Windows-Sysmon/Operational" |
TargetObject | match |
| field:"TargetObject" kind:match value:"HKLM\System\CurrentControlSet\Control\Lsa\DsrmAdminLogonBehavior" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Computer | summarize |
Details | summarize |
EndTime | summarize |
EventID | summarize |
Image | summarize |
ProcessId | summarize |
StartTime | summarize |
TargetObject | summarize |
User | summarize |
_ResourceId | summarize |
HostName | extend |
HostNameDomain | extend |
AccountNTDomain | extend |
AccountName | extend |
ImageFileName | extend |
ImageDirectory | extend |
GCP Audit Logs - Detect Bulk VM Snapshot Deletion
#Detects bulk deletion of Google Cloud VM snapshots within a short time period, which may indicate data destruction or defense evasion activities. VM snapshots are critical for backup and disaster recovery. Bulk deletion of snapshots can prevent recovery from incidents and may indicate malicious activity such as ransomware, data destruction, or an attempt to cover tracks after a security breach. Adversaries may delete snapshots to maximize damage, prevent forensic investigation, or hinder recovery efforts. This rule triggers when multiple snapshots are deleted by the same user within a 1-minute window.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment | |
| Impact |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| GCP | compute.snapshots.delete: delete |
Rules Detecting the Same Action
These rules filter on the same operation.
Rule Body
id: dfdffdc7-929f-4c7e-8f48-30e5ffddb067
name: GCP Audit Logs - Detect Bulk VM Snapshot Deletion
description: |
'Detects bulk deletion of Google Cloud VM snapshots within a short time period, which may indicate data destruction or defense evasion activities.
VM snapshots are critical for backup and disaster recovery. Bulk deletion of snapshots can prevent recovery from incidents and may indicate
malicious activity such as ransomware, data destruction, or an attempt to cover tracks after a security breach.
Adversaries may delete snapshots to maximize damage, prevent forensic investigation, or hinder recovery efforts.
This rule triggers when multiple snapshots are deleted by the same user within a 1-minute window.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: GCPAuditLogsDefinition
dataTypes:
- GCPAuditLogs
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
- DefenseEvasion
relevantTechniques:
- T1485
- T1490
- T1562.001
tags:
- GCP
- Compute
- Data Destruction
- Cloud Security
query: |
// Update these thresholds if noisy in your environment
let SnapshotDeletionThreshold = 10;
let TimeWindow = 1m;
GCPAuditLogs
| where ServiceName == "compute.googleapis.com"
| where MethodName has "compute.snapshots.delete"
| where GCPResourceType == "gce_snapshot" and Severity == "NOTICE"
| extend
AuthzInfoJson = parse_json(AuthorizationInfo),
RequestMetadataJson = parse_json(RequestMetadata),
ResponseJson = parse_json(Response)
| extend PermissionType = tostring(AuthzInfoJson[0].permissionType)
| where PermissionType == "ADMIN_WRITE"
| extend
CallerIpAddress = tostring(RequestMetadataJson.callerIp),
UserAgent = tostring(RequestMetadataJson.callerSuppliedUserAgent),
SnapshotName = extract(@"snapshots/([^/]+)", 1, GCPResourceName),
OperationType = tostring(ResponseJson.operationType),
OperationId = tostring(ResponseJson.id)
| summarize
SnapshotCount = count(),
SnapshotList = make_set(SnapshotName, 100),
FirstDeletion = min(TimeGenerated),
LastDeletion = max(TimeGenerated),
OperationIds = make_set(OperationId, 100),
CallerIPs = make_set(CallerIpAddress, 10)
by PrincipalEmail, ProjectId, UserAgent
| where SnapshotCount >= SnapshotDeletionThreshold
| extend DeletionTimeSpan = LastDeletion - FirstDeletion
| where DeletionTimeSpan <= TimeWindow
| extend
AccountName = tostring(split(PrincipalEmail, "@")[0]),
AccountUPNSuffix = tostring(split(PrincipalEmail, "@")[1])
| project
TimeGenerated = FirstDeletion,
PrincipalEmail,
ProjectId,
SnapshotCount,
SnapshotList,
FirstDeletion,
LastDeletion,
DeletionTimeSpan,
CallerIPs,
UserAgent,
OperationIds,
AccountName,
AccountUPNSuffix
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: PrincipalEmail
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: ProjectId
customDetails:
ProjectId: ProjectId
SnapshotCount: SnapshotCount
SnapshotList: SnapshotList
FirstDeletion: FirstDeletion
LastDeletion: LastDeletion
DeletionTimeSpan: DeletionTimeSpan
CallerIPs: CallerIPs
UserAgent: UserAgent
alertDetailsOverride:
alertDisplayNameFormat: "Bulk VM Snapshot Deletion: {{SnapshotCount}} snapshots deleted by {{PrincipalEmail}} in {{ProjectId}}"
alertDescriptionFormat: |-
User {{PrincipalEmail}} deleted {{SnapshotCount}} VM snapshots in project {{ProjectId}} within a short time period.
This may indicate ransomware, data destruction, or defense evasion activity. Verify authorization, check remaining snapshots, review IP addresses, and investigate for compromise.
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let SnapshotDeletionThreshold = 10;
let TimeWindow = 1m;
Stage 1: source
GCPAuditLogs
Stage 2: where
| where ServiceName == "compute.googleapis.com"
Stage 3: where
| where MethodName has "compute.snapshots.delete"
Stage 4: where
| where GCPResourceType == "gce_snapshot" and Severity == "NOTICE"
Stage 5: extend
| extend
AuthzInfoJson = parse_json(AuthorizationInfo),
RequestMetadataJson = parse_json(RequestMetadata),
ResponseJson = parse_json(Response)
Stage 6: extend
| extend PermissionType = tostring(AuthzInfoJson[0].permissionType)
Stage 7: where
| where PermissionType == "ADMIN_WRITE"
Stage 8: extend
| extend
CallerIpAddress = tostring(RequestMetadataJson.callerIp),
UserAgent = tostring(RequestMetadataJson.callerSuppliedUserAgent),
SnapshotName = extract(@"snapshots/([^/]+)", 1, GCPResourceName),
OperationType = tostring(ResponseJson.operationType),
OperationId = tostring(ResponseJson.id)
Stage 9: summarize
| summarize
SnapshotCount = count(),
SnapshotList = make_set(SnapshotName, 100),
FirstDeletion = min(TimeGenerated),
LastDeletion = max(TimeGenerated),
OperationIds = make_set(OperationId, 100),
CallerIPs = make_set(CallerIpAddress, 10)
by PrincipalEmail, ProjectId, UserAgent
Stage 10: where
| where SnapshotCount >= SnapshotDeletionThreshold
Stage 11: extend
| extend DeletionTimeSpan = LastDeletion - FirstDeletion
Stage 12: where
| where DeletionTimeSpan <= TimeWindow
Stage 13: extend
| extend
AccountName = tostring(split(PrincipalEmail, "@")[0]),
AccountUPNSuffix = tostring(split(PrincipalEmail, "@")[1])
Stage 14: project
| project
TimeGenerated = FirstDeletion,
PrincipalEmail,
ProjectId,
SnapshotCount,
SnapshotList,
FirstDeletion,
LastDeletion,
DeletionTimeSpan,
CallerIPs,
UserAgent,
OperationIds,
AccountName,
AccountUPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DeletionTimeSpan | le |
| field:"DeletionTimeSpan" kind:le value:"1m" |
GCPResourceType | eq |
| field:"gcp::resource_type" kind:eq value:"gce_snapshot" |
MethodName | match |
| field:"gcp::method_name" kind:match value:"compute.snapshots.delete" |
PermissionType | eq |
| field:"PermissionType" kind:eq value:"ADMIN_WRITE" |
ServiceName | eq |
| field:"ServiceName" kind:eq value:"compute.googleapis.com" |
Severity | eq |
| field:"Severity" kind:eq value:"NOTICE" |
SnapshotCount | ge |
| field:"SnapshotCount" kind:ge value:"10" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
AccountUPNSuffix | project |
CallerIPs | project |
DeletionTimeSpan | project |
FirstDeletion | project |
LastDeletion | project |
OperationIds | project |
PrincipalEmail | project |
ProjectId | project |
SnapshotCount | project |
SnapshotList | project |
TimeGenerated | project |
UserAgent | project |
Multi-Factor Authentication Disabled
#Detects when multi-factor authentication is disabled for all users.
Rule Body
id: 6881f4f5-f58e-43d7-a05e-b5d05a4c09f9
name: Multi-Factor Authentication Disabled
description: Detects when multi-factor authentication is disabled for all users.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 40201\n| project\n Date\
\ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n\
\ EventId = instanceId,\n [\"User Name\"] = user,\n MessageDetails = Description,\n\
\ Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
Date: Date
VbrHostName: DataSource
EventId: EventId
MessageDetails: MessageDetails
Severity: Severity
Stages and Predicates
Stage 1: source
Veeam_GetSecurityEvents
Stage 2: where
| where instanceId == 40201
Stage 3: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
["User Name"] = user,
MessageDetails = Description,
Severity = SeverityDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"40201" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Severity | project |
User Name | project |
Multiple Password Reset by user
#This query will determine multiple password resets by user across multiple data sources. Account manipulation including password reset may aid adversaries in maintaining access to credentials and certain permission levels within an environment.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Credential Access |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
Rule Body
id: 0b9ae89d-8cad-461c-808f-0494f70ad5c4
name: Multiple Password Reset by user
description: |
'This query will determine multiple password resets by user across multiple data sources.
Account manipulation including password reset may aid adversaries in maintaining access to credentials and certain permission levels within an environment.'
severity: Low
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: Office365
dataTypes:
- OfficeActivity
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvents
- connectorId: WindowsForwardedEvents
dataTypes:
- WindowsEvent
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- CredentialAccess
relevantTechniques:
- T1078
- T1110
query: |
let selfServicePasswordReset = dynamic(["Self-service password reset flow activity progress", "Change password (self-service)", "Reset password (self-service)"]);
//Self-service password reset flow activity progress is typically caused by a password policy which requires users to rotate passwords. This operation already implies the user has signed in successfully and therefore the password reset is non-malicious.
let PerUserThreshold = 5;
let TotalThreshold = 100;
let action = dynamic(["change", "changed", "reset"]);
let pWord = dynamic(["password", "credentials"]);
let PasswordResetMultiDataSource =
(union isfuzzy=true
(//Password reset events
//4723: An attempt was made to change an account's password
//4724: An attempt was made to reset an accounts password
SecurityEvent
| where EventID in ("4723","4724")
| project TimeGenerated, Computer, AccountType, Account, Type, TargetUserName),
(//Password reset events
//4723: An attempt was made to change an account's password
//4724: An attempt was made to reset an accounts password
WindowsEvent
| where EventID in ("4723","4724")
| extend SubjectUserSid = tostring(EventData.SubjectUserSid)
| extend TargetUserName = tostring(EventData.TargetUserName)
| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend AccountType=case(Account endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
| project TimeGenerated, Computer, AccountType, Account, Type, TargetUserName),
(//Azure Active Directory Password reset events
AuditLogs
| where OperationName has_any (pWord) and OperationName has_any (action) and Result =~ "success"
| where OperationName !in (selfServicePasswordReset)
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "User"
| extend AccountType = tostring(TargetResource.type),
Account = tostring(InitiatedBy.user.userPrincipalName),
TargetUserName = tolower(tostring(TargetResource.userPrincipalName))
)
| project TimeGenerated, AccountType, Account, TargetUserName, Computer = "", Type),
(//OfficeActive ActiveDirectory Password reset events
OfficeActivity
| where OfficeWorkload == "AzureActiveDirectory"
| where (ExtendedProperties has_any (pWord) or ModifiedProperties has_any (pWord)) and (ExtendedProperties has_any (action) or ModifiedProperties has_any (action))
| extend AccountType = UserType, Account = OfficeObjectId
| project TimeGenerated, AccountType, Account, Type, Computer = ""),
(// Unix syslog password reset events
Syslog
| where Facility in ("auth","authpriv")
| where SyslogMessage has_any (pWord) and SyslogMessage has_any (action)
| extend AccountType = iif(SyslogMessage contains "root", "Root", "Non-Root")
| where SyslogMessage matches regex ".*password changed for.*"
| parse SyslogMessage with * "password changed for" Account
| project TimeGenerated, AccountType, Account, Computer = HostName, Type)
);
let pwrmd = PasswordResetMultiDataSource
| project TimeGenerated, Computer, AccountType, Account, Type, TargetUserName;
(union isfuzzy=true
(pwrmd
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), Computerlist = make_set(Computer, 25), AccountType = make_set(AccountType, 25), Computer = arg_max(Computer , TimeGenerated), TargetUserList = make_set(TargetUserName, 25), TargetUserName = arg_max(TargetUserName, TimeGenerated), Total=count() by Account, Type
| where Total > PerUserThreshold
| extend ResetPivot = "PerUserReset"),
(pwrmd
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ComputerList = make_set(Computer, 25), AccountList = make_set(Account, 25), AccountType = make_set(AccountType, 25), Computer = arg_max(Computer , TimeGenerated), TargetUserList = make_set(TargetUserName, 25), TargetUserName = arg_max(TargetUserName, TimeGenerated), Total=count() by Type
| where Total > TotalThreshold
| extend ResetPivot = "TotalUserReset")
)
| extend timestamp = StartTimeUtc, HostName = tostring(split(Computer, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.')), Name = tostring(split(Account, '@', 0)[0]), UPNSuffix = tostring(split(Account, '@', 1)[0]), TargetName = tostring(split(TargetUserName,'@',0)[0]), TargetUPNSuffix = tostring(split(TargetUserName,'@',1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Account
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: TargetUserName
- identifier: Name
columnName: TargetName
- identifier: UPNSuffix
columnName: TargetUPNSuffix
version: 2.1.7
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others", "Identity" ]
Stages and Predicates
Parameters
let selfServicePasswordReset = dynamic(["Self-service password reset flow activity progress", "Change password (self-service)", "Reset password (self-service)"]);
let PerUserThreshold = 5;
let TotalThreshold = 100;
let action = dynamic(["change", "changed", "reset"]);
let pWord = dynamic(["password", "credentials"]);
Union isfuzzy=true (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: pwrmd, pwrmd
Leg 1: pwrmd
pwrmd
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), Computerlist = make_set(Computer, 25), AccountType = make_set(AccountType, 25), Computer = arg_max(Computer , TimeGenerated), TargetUserList = make_set(TargetUserName, 25), TargetUserName = arg_max(TargetUserName, TimeGenerated), Total=count() by Account, Type
| where Total > PerUserThreshold
| extend ResetPivot = "PerUserReset"
Leg 2: pwrmd
pwrmd
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ComputerList = make_set(Computer, 25), AccountList = make_set(Account, 25), AccountType = make_set(AccountType, 25), Computer = arg_max(Computer , TimeGenerated), TargetUserList = make_set(TargetUserName, 25), TargetUserName = arg_max(TargetUserName, TimeGenerated), Total=count() by Type
| where Total > TotalThreshold
| extend ResetPivot = "TotalUserReset"
Applied to the Combined Result
| extend timestamp = StartTimeUtc, HostName = tostring(split(Computer, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.')), Name = tostring(split(Account, '@', 0)[0]), UPNSuffix = tostring(split(Account, '@', 1)[0]), TargetName = tostring(split(TargetUserName,'@',0)[0]), TargetUPNSuffix = tostring(split(TargetUserName,'@',1)[0])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventID | in |
| field:"EventID" kind:in |
ExtendedProperties | match |
| field:"ExtendedProperties" kind:match |
Facility | in |
| field:"Facility" kind:in |
ModifiedProperties | match |
| field:"ModifiedProperties" kind:match |
OfficeWorkload | eq |
| field:"m365::Workload" kind:eq |
OperationName | match |
| field:"OperationName" kind:match |
Result | eq |
| field:"Result" kind:eq value:"success" |
SyslogMessage | match |
| field:"SyslogMessage" kind:match |
SyslogMessage | regex_match |
| field:"SyslogMessage" kind:regex_match value:".*password changed for.*" |
TargetResource.type | eq |
| field:"TargetResource.type" kind:eq value:"User" |
Total | gt |
| field:"Total" kind:gt |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
OperationName | in | Change password (self-service), Reset password (self-service), Self-service password reset flow activity progress | excludes:OperationName field:"OperationName" value:"Change password (self-service)" field:"OperationName" value:"Reset password (self-service)" field:"OperationName" value:"Self-service password reset flow activity progress" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountList | summarize |
AccountType | summarize |
Computer | summarize |
ComputerList | summarize |
EndTimeUtc | summarize |
StartTimeUtc | summarize |
TargetUserList | summarize |
TargetUserName | summarize |
Total | summarize |
Type | summarize |
ResetPivot | extend |
DnsDomain | extend |
HostName | extend |
Name | extend |
TargetName | extend |
TargetUPNSuffix | extend |
UPNSuffix | extend |
timestamp | extend |
NGINX - Core Dump
#'Detects a core dump of a crashing Nginx worker process, which could be a signal of a serious problem or exploitation attempts.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: 9a7f5a97-354b-4eac-b407-a1cc7fc4b4ec
name: NGINX - Core Dump
description: |
'Detects a core dump of a crashing Nginx worker process, which could be a signal of a serious problem or exploitation attempts.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
dataTypes:
- NGINX_CL
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1499
query: |
NGINXHTTPServer
| where EventType =~ "ErrorLog"
| where EventMessage contains 'exited on signal 6 (core dumped)'
| extend ProcessIdCustomEntity = ProcessId
entityMappings:
- entityType: Process
fieldMappings:
- identifier: ProcessId
columnName: ProcessIdCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Stage 1: source
NGINXHTTPServer
Stage 2: where
| where EventType =~ "ErrorLog"
Stage 3: where
| where EventMessage contains 'exited on signal 6 (core dumped)'
Stage 4: extend
| extend ProcessIdCustomEntity = ProcessId
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventMessage | contains |
| field:"EventMessage" kind:contains value:"exited on signal 6 (core dumped)" |
EventType | eq |
| field:"EventType" kind:eq value:"ErrorLog" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ProcessIdCustomEntity | extend |
Pathlock TDnR - SAP Client Configuration Changes
#Detects changes to SAP client configuration settings, forwarded by Pathlock Threat Detection and Response. Changes to client settings such as enabling client-independent changes or transport settings may indicate attempts to bypass change management controls.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Defense Impairment |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c20
name: Pathlock TDnR - SAP Client Configuration Changes
kind: Scheduled
description: >-
Detects changes to SAP client configuration settings, forwarded by Pathlock Threat Detection
and Response. Changes to client settings such as enabling client-independent changes or transport
settings may indicate attempts to bypass change management controls.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- Persistence
relevantTechniques:
- T1562
- T1098
query: |
Pathlock_TDnR_CL
| where DataSource == "CLIENT_CHANGES"
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Bname
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Hostname
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIp
eventGroupingSettings:
aggregationKind: SingleAlert
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AnyAlert
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0
Stages and Predicates
Stage 1: source
Pathlock_TDnR_CL
Stage 2: where
| where DataSource == "CLIENT_CHANGES"
Stage 3: project
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DataSource | eq |
| field:"DataSource" kind:eq value:"CLIENT_CHANGES" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AffectedUser | project |
Area | project |
Bname | project |
CentralTs | project |
DataSource | project |
DestIp | project |
Eventid | project |
Hostname | project |
Instance | project |
LogLine | project |
MessageV1 | project |
MessageV2 | project |
MessageV3 | project |
MessageV4 | project |
MsgId | project |
MsgNo | project |
MsgType | project |
Report | project |
SrcIp | project |
Subid | project |
Sysid | project |
Tcode | project |
TimeGenerated | project |
Spearphishing Attachment: ISO Images (Microsoft Defender for Endpoint)
#ISO images are often meant to be used offline and they are often used by IT Admins and/or used on Servers.
Installation from an iso file don't require network connection most of the time. Activities deviating from these situations can be considered as highly suspicious. Below queries detects opening a mounted image, process creation under a mounted image, and network connection from a process created under a mounted image.
All detections can be used seperately or combined together to generate a higher fidelity alert. Detect opening of a mounted image:
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | No specific technique |
Telemetry Coverage
Rule Body
// Author: Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://mergene.medium.com)
// Link to original post:
// Part-1: https://mergene.medium.com/detecting-initial-access-html-smuggling-and-iso-images-part-1-c4f953edd13f?source=friends_link&sk=e685d7d44928edd142972a4041463f10
// Part-2: https://mergene.medium.com/detecting-initial-access-html-smuggling-and-iso-images-part-2-f8dd600430e2?source=friends_link&sk=38b7cd310a4929c25d3eefc545683d5f
//
//
// Query parameters:
let lookback = 1h;
// Get ISO mount events
DeviceFileEvents
| where Timestamp > ago(lookback)
| where FileName endswith ".iso.lnk" or FileName endswith ".img.lnk"
// Exclude servers and workstation used by IT admins if needed.
// Author: Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://mergene.medium.com)
// Link to original post:
// Part-1: https://mergene.medium.com/detecting-initial-access-html-smuggling-and-iso-images-part-1-c4f953edd13f?source=friends_link&sk=e685d7d44928edd142972a4041463f10
// Part-2: https://mergene.medium.com/detecting-initial-access-html-smuggling-and-iso-images-part-2-f8dd600430e2?source=friends_link&sk=38b7cd310a4929c25d3eefc545683d5f
//
//
// Query parameters:
let lookback = 1h;
// Get mounted devices and extract the folder name
DeviceRegistryEvents
| where Timestamp > ago(lookback)
| where ActionType == "RegistryValueSet" and RegistryKey == @"HKEY_LOCAL_MACHINE\SYSTEM\MountedDevices" and RegistryValueName startswith @"\DosDevices\"
| extend Folder = toupper(replace(@'\\DosDevices\\(\w:)',@'\1',RegistryValueName)) // Extract the folder name
// Get process creations that have the mounted image as the FolderPath
| join kind=inner
(
DeviceProcessEvents
| where Timestamp > ago(lookback)
| extend Folder = toupper(replace(@'(\w:)\\.*',@'\1',FolderPath))
) on DeviceId, Folder
// If needed, exclude servers from the results.
// Author: Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://mergene.medium.com)
// Link to original post:
// Part-1: https://mergene.medium.com/detecting-initial-access-html-smuggling-and-iso-images-part-1-c4f953edd13f?source=friends_link&sk=e685d7d44928edd142972a4041463f10
// Part-2: https://mergene.medium.com/detecting-initial-access-html-smuggling-and-iso-images-part-2-f8dd600430e2?source=friends_link&sk=38b7cd310a4929c25d3eefc545683d5f
//
//
// Query parameters:
let lookback = 1h;
// Get mounted devices and extract the folder name
DeviceRegistryEvents
| where Timestamp > ago(lookback)
| where ActionType == "RegistryValueSet" and RegistryKey == @"HKEY_LOCAL_MACHINE\SYSTEM\MountedDevices" and RegistryValueName startswith @"\DosDevices\"
| extend Folder = toupper(replace(@'\\DosDevices\\(\w:)',@'\1',RegistryValueName)) // Extract the folder name
// Get network connections of processes that have the mounted image as the InitiatingProcessFolderPath
| join kind=inner
(
DeviceNetworkEvents
| where Timestamp > ago(lookback)
| extend Folder = toupper(replace(@'(\w:)\\.*',@'\1',InitiatingProcessFolderPath))
) on DeviceId, Folder
// If needed, exclude the legitimate activity and servers
Stages and Predicates
Parameters
let lookback = 1h;
Stage 1: source
DeviceFileEvents
Stage 2: where
where Timestamp > ago(3600s)
Stage 3: where
where (FileName endswith ".img.lnk" or FileName endswith ".iso.lnk")
Stage 4: source
DeviceRegistryEvents
Stage 5: where
where Timestamp > ago(3600s)
Stage 6: where
where ActionType == "RegistryValueSet" and RegistryKey == @"HKEY_LOCAL_MACHINE\SYSTEM\MountedDevices" and RegistryValueName startswith @"\DosDevices\"
Stage 7: extend
extend Folder
Stage 8: join
join kind=inner (DeviceProcessEvents) on DeviceId, Folder
Stage 9: source
DeviceRegistryEvents
Stage 10: where
where Timestamp > ago(3600s)
Stage 11: where
where ActionType == "RegistryValueSet" and RegistryKey == @"HKEY_LOCAL_MACHINE\SYSTEM\MountedDevices" and RegistryValueName startswith @"\DosDevices\"
Stage 12: extend
extend Folder
Stage 13: join
join kind=inner (DeviceNetworkEvents) on DeviceId, Folder
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"RegistryValueSet" |
FileName | ends_with |
| field:"FileName" kind:ends_with |
RegistryKey | eq |
| field:"RegistryKey" kind:eq value:"HKEY_LOCAL_MACHINE\SYSTEM\MountedDevices" |
RegistryValueName | starts_with |
| field:"RegistryValueName" kind:starts_with value:"\DosDevices\" |
TI Map Domain entity to Cloud App Events
#Identifies compromises and attacks and detect malicious activities in one's domain entity from TI.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | CloudAppEvents action any: Cloud app activity |
Rule Body
id: a7d2b1e4-dd9c-40fd-9651-1a136eb8f0df
name: TI Map Domain entity to Cloud App Events
description: |
'Identifies compromises and attacks and detect malicious activities in one's domain entity from TI.'
severity: Medium
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- CloudAppEvents
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelIndicators
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let list_tlds =
ThreatIntelIndicators
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
//extract key part of kv pair
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
| where IndicatorType == "domain-name"
| extend DomainName = tolower(IndicatorType)
| extend parts = split(DomainName, '.')
| extend tld = parts[(array_length(parts) - 1)]
| extend IndicatorId = tostring(split(Id, "--")[2])
| summarize count() by tostring(tld)
| summarize make_set(tld);
let Domain_Indicators =
ThreatIntelIndicators
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
| where IndicatorType == "domain-name"
| extend DomainName = tolower(ObservableValue)
| extend IndicatorId = tostring(split(Id, "--")[2])
| project-reorder *, IndicatorType, DomainName, Type;
Domain_Indicators
| join kind=innerunique (
CloudAppEvents
| extend IngestionTime = ingestion_time()
| where IngestionTime > ago(dt_lookBack)
| extend PAUrl = columnifexists("RequestURL", "None")
| extend Domain = tolower(trim('"', tostring(parseurl(PAUrl).Host)))
| extend parts = split(Domain, '.')
| extend tld = parts[(array_length(parts) - 1)]
| extend CloudAppEvents_TimeGenerated = TimeGenerated) on $left.DomainName == $right.Domain
| where CloudAppEvents_TimeGenerated < ValidUntil
| summarize CloudAppEvents_TimeGenerated = argmax(CloudAppEvents_TimeGenerated, *) by IndicatorId
| extend
Description = tostring(parse_json(max_CloudAppEvents_TimeGenerated_Data).description),
ActivityGroupNames = column_ifexists("max_CloudAppEvents_TimeGenerated_ActivityGroupNames", ""),
ThreatType = column_ifexists("max_CloudAppEvents_TimeGenerated_ThreatType", ""),
ExpirationDateTime = column_ifexists("max_CloudAppEvents_TimeGenerated_ExpirationDateTime", ""),
ConfidenceScore = column_ifexists("max_CloudAppEvents_TimeGenerated_ConfidenceScore", ""),
DomainName = column_ifexists("max_CloudAppEvents_TimeGenerated_DomainName", ""),
ProviderName = column_ifexists("max_CloudAppEvents_TimeGenerated_IndicatorProvider", ""),
AlertSeverity = column_ifexists("max_CloudAppEvents_TimeGenerated_ThreatSeverity", ""),
IPAddress = column_ifexists("max_CloudAppEvents_TimeGenerated_IPAddress", "")
| project CloudAppEvents_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, DomainName, ProviderName, AlertSeverity, IPAddress
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: DomainName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
version: 1.0.7
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let Domain_Indicators is inlined into the numbered stages below.
Let Binding: list_tlds
let list_tlds = ThreatIntelIndicators
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
| where IndicatorType == "domain-name"
| extend DomainName = tolower(IndicatorType)
| extend parts = split(DomainName, '.')
| extend tld = parts[(array_length(parts) - 1)]
| extend IndicatorId = tostring(split(Id, "--")[2])
| summarize count() by tostring(tld)
| summarize make_set(tld);
Stages 1 to 9 define let Domain_Indicators as the rule's main pipeline source. Stages 10 to 14 use it.
Stage 1: source
ThreatIntelIndicators
Stage 2: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 3: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
Stage 4: where
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
Stage 5: extend
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
Stage 6: where
| where IndicatorType == "domain-name"
Stage 7: extend
| extend DomainName = tolower(ObservableValue)
Stage 8: extend
| extend IndicatorId = tostring(split(Id, "--")[2])
Stage 9: project-reorder
| project-reorder *, IndicatorType, DomainName, Type
Stage 10: join
Domain_Indicators
| join kind=innerunique (
CloudAppEvents
| extend IngestionTime = ingestion_time()
| where IngestionTime > ago(dt_lookBack)
| extend PAUrl = columnifexists("RequestURL", "None")
| extend Domain = tolower(trim('"', tostring(parseurl(PAUrl).Host)))
| extend parts = split(Domain, '.')
| extend tld = parts[(array_length(parts) - 1)]
| extend CloudAppEvents_TimeGenerated = TimeGenerated) on $left.DomainName == $right.Domain
Stage 11: where
where CloudAppEvents_TimeGenerated < ValidUntil
Stage 12: summarize
summarize CloudAppEvents_TimeGenerated by IndicatorId
Stage 13: extend
extend ActivityGroupNames, AlertSeverity, ConfidenceScore, Description, DomainName, ExpirationDateTime, IPAddress, ProviderName, ThreatType
Stage 14: project
project ActivityGroupNames, AlertSeverity, CloudAppEvents_TimeGenerated, ConfidenceScore, Description, DomainName, ExpirationDateTime, IPAddress, IndicatorId, ProviderName, ThreatType
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
IndicatorType | eq |
| field:"IndicatorType" kind:eq value:"domain-name" |
IsActive | eq |
| field:"IsActive" kind:eq value:"true" |
ValidUntil | is_null | field:"ValidUntil" kind:is_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
AlertSeverity | project |
CloudAppEvents_TimeGenerated | project |
ConfidenceScore | project |
Description | project |
DomainName | project |
ExpirationDateTime | project |
IPAddress | project |
IndicatorId | project |
ProviderName | project |
ThreatType | project |
TI Map IP Entity to DnsEvents
#This query maps any IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in DnsEvents.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: 6418fd33-92f2-407b-bd61-91c0d4bbcb8a
name: TI Map IP Entity to DnsEvents
description: |
This query maps any IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in DnsEvents.
severity: Medium
requiredDataConnectors:
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelIndicators
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelIndicators
- connectorId: DNS
dataTypes:
- DnsEvents
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelIndicators
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
let dt_lookBack = 1h; // Look back 1 hour for DNS events
let ioc_lookBack = 14d; // Look back 14 days for threat intelligence indicators
// Fetch threat intelligence indicators related to IP addresses
let IP_Indicators = ThreatIntelIndicators
//extract key part of kv pair
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
| where IndicatorType in ("ipv4-addr", "ipv6-addr", "network-traffic")
| extend NetworkSourceIP = toupper(ObservableValue)
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
| where TimeGenerated >= ago(ioc_lookBack)
| extend TI_ipEntity = iff(isnotempty(NetworkSourceIP), NetworkSourceIP, NetworkSourceIP)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
| extend Url = iff(ObservableKey == "url:value", ObservableValue, "")
| where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith "fe80" and TI_ipEntity !startswith "::" and TI_ipEntity !startswith "127."
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
| where IsActive and (ValidUntil > now() or isempty(ValidUntil));
// Perform a join between IP indicators and DNS events
IP_Indicators
| project-reorder *, Tags, TrafficLightProtocolLevel, NetworkSourceIP, Type, TI_ipEntity
// Use innerunique to keep performance fast and result set low, as we only need one match to indicate potential malicious activity that needs investigation
| join kind=innerunique (
DnsEvents
| where TimeGenerated >= ago(dt_lookBack)
| where SubType =~ "LookupQuery" and isnotempty(IPAddresses)
| mv-expand SingleIP = split(IPAddresses, ", ") to typeof(string)
| extend DNS_TimeGenerated = TimeGenerated
)
on $left.TI_ipEntity == $right.SingleIP
// Filter out DNS events that occurred after the expiration of the corresponding indicator
| where DNS_TimeGenerated < ValidUntil
// Group the results by IndicatorId and SingleIP, and keep the DNS event with the latest timestamp
| summarize DNS_TimeGenerated = arg_max(DNS_TimeGenerated, *) by Id, SingleIP
// Select the desired output fields
| extend Description = tostring(parse_json(Data).description)
| extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
| project DNS_TimeGenerated, Description, ActivityGroupNames, Id, ValidUntil, Confidence, TI_ipEntity, Computer, EventId, SubType, ClientIP, Name, IPAddresses, NetworkSourceIP, Type, Url
| extend timestamp = DNS_TimeGenerated, HostName = tostring(split(Computer, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIP
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
version: 1.4.7
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let IP_Indicators is inlined into the numbered stages below.
Stages 1 to 10 define let IP_Indicators as the rule's main pipeline source. Stages 11 to 18 use it.
Stage 1: source
ThreatIntelIndicators
Stage 2: extend
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
Stage 3: where
| where IndicatorType in ("ipv4-addr", "ipv6-addr", "network-traffic")
Stage 4: extend
| extend NetworkSourceIP = toupper(ObservableValue)
Stage 5: extend
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
Stage 6: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 7: extend (3 consecutive steps)
| extend TI_ipEntity = iff(isnotempty(NetworkSourceIP), NetworkSourceIP, NetworkSourceIP)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
| extend Url = iff(ObservableKey == "url:value", ObservableValue, "")
TI_ipEntity =isnotempty(NetworkSourceIP)NetworkSourceIPNetworkSourceIPStage 8: where
| where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith "fe80" and TI_ipEntity !startswith "::" and TI_ipEntity !startswith "127."
Stage 9: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
Stage 10: where
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
Stage 11: project-reorder
IP_Indicators
| project-reorder *, Tags, TrafficLightProtocolLevel, NetworkSourceIP, Type, TI_ipEntity
Stage 12: join
| join kind=innerunique (
DnsEvents
| where TimeGenerated >= ago(dt_lookBack)
| where SubType =~ "LookupQuery" and isnotempty(IPAddresses)
| mv-expand SingleIP = split(IPAddresses, ", ") to typeof(string)
| extend DNS_TimeGenerated = TimeGenerated
)
on $left.TI_ipEntity == $right.SingleIP
Stage 13: where
where DNS_TimeGenerated < ValidUntil
Stage 14: summarize
summarize DNS_TimeGenerated by Id, SingleIP
Stage 15: extend
extend Description
Stage 16: extend
extend ActivityGroupNames
Stage 17: project
project ActivityGroupNames, ClientIP, Computer, Confidence, DNS_TimeGenerated, Description, EventId, IPAddresses, Id, Name, NetworkSourceIP, SubType, TI_ipEntity, Type, Url, ValidUntil
Stage 18: extend
extend DnsDomain, HostName, timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
IPAddresses | is_not_null | field:"IPAddresses" kind:is_not_null | |
IndicatorType | in |
| field:"IndicatorType" kind:in |
IsActive | eq |
| field:"IsActive" kind:eq value:"true" |
SubType | eq |
| field:"SubType" kind:eq value:"LookupQuery" |
ValidUntil | is_null | field:"ValidUntil" kind:is_null |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
TI_ipEntity | cidr_match | 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8 | excludes:TI_ipEntity |
TI_ipEntity | starts_with | 127. | excludes:TI_ipEntity field:"TI_ipEntity" value:"127." |
TI_ipEntity | starts_with | :: | excludes:TI_ipEntity field:"TI_ipEntity" value:"::" |
TI_ipEntity | starts_with | fe80 | excludes:TI_ipEntity field:"TI_ipEntity" value:"fe80" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
ClientIP | project |
Computer | project |
Confidence | project |
DNS_TimeGenerated | project |
Description | project |
EventId | project |
IPAddresses | project |
Id | project |
Name | project |
NetworkSourceIP | project |
SubType | project |
TI_ipEntity | project |
Type | project |
Url | project |
ValidUntil | project |
DnsDomain | extend |
HostName | extend |
timestamp | extend |
Uniqkey - Security policy change
#Detects modifications to Uniqkey organization security policies, such as authentication, password or sharing policy settings. Weakening a policy is a common preparatory step before credential abuse, and even legitimate changes deserve a review trail, so each policy change is raised as an alert with the acting administrator attached.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Rule Body
id: ac008972-e70f-4040-96ad-bdce1c642839
name: Uniqkey - Security policy change
version: 1.0.0
kind: Scheduled
description: |-
Detects modifications to Uniqkey organization security policies, such as authentication, password or sharing policy settings. Weakening a policy is a common preparatory step before credential abuse, and even legitimate changes deserve a review trail, so each policy change is raised as an alert with the acting administrator attached.
severity: Medium
requiredDataConnectors:
- connectorId: UniqkeyEventsConnector
dataTypes:
- UniqkeyEvents_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- Persistence
relevantTechniques:
- T1562
query: |-
UniqkeyEvents_CL
| where Category == "policy_management"
| project TimeGenerated, ActorEmail, ActorType, Action, ActionId, TargetType, TargetName, ClientSystem, SrcIpAddr
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorEmail
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
suppressionEnabled: false
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AllEntities
suppressionDuration: 5h
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Stage 1: source
UniqkeyEvents_CL
Stage 2: where
| where Category == "policy_management"
Stage 3: project
| project TimeGenerated, ActorEmail, ActorType, Action, ActionId, TargetType, TargetName, ClientSystem, SrcIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | eq |
| field:"Category" kind:eq value:"policy_management" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Action | project |
ActionId | project |
ActorEmail | project |
ActorType | project |
ClientSystem | project |
SrcIpAddr | project |
TargetName | project |
TargetType | project |
TimeGenerated | project |
Users searching for VIP user activity
#This query monitors for users running Log Analytics queries that contain filters for specific, defined VIP user accounts or the VIPUser watchlist template. Use this detection to alert for users specifically searching for activity of sensitive users.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection | |
| Exfiltration |
Rule Body
id: f7f4a77e-f68f-4b56-9aaf-a0c9d87d7a8e
name: Users searching for VIP user activity
description: |
This query monitors for users running Log Analytics queries that contain filters for specific, defined VIP user accounts or the VIPUser watchlist template.
Use this detection to alert for users specifically searching for activity of sensitive users.
severity: Low
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
- Exfiltration
relevantTechniques:
- T1530
- T1213
- T1020
query: |
// Replace these with the username or emails of your VIP users you wish to monitor for.
let vips = dynamic(['vip1@email.com','vip2@email.com']);
// Add users who are allowed to conduct these searches - this could be specific SOC team members
let allowed_users = dynamic([]);
LAQueryLogs
| where QueryText has_any (vips) or QueryText has_any ('_GetWatchlist("VIPUsers")', "_GetWatchlist('VIPUsers')")
| where AADEmail !in (allowed_users)
| project TimeGenerated, AADEmail, RequestClientApp, QueryText, ResponseRowCount, RequestTarget
| extend timestamp = TimeGenerated, AccountName = tostring(split(AADEmail, "@")[0]), AccountUPNSuffix = tostring(split(AADEmail, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: AzureResource
fieldMappings:
- identifier: ResourceId
columnName: RequestTarget
version: 1.1.4
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others" ]
Stages and Predicates
Parameters
let vips = dynamic(['vip1@email.com','vip2@email.com']);
let allowed_users = dynamic([]);
Stage 1: source
LAQueryLogs
Stage 2: where
| where QueryText has_any (vips) or QueryText has_any ('_GetWatchlist("VIPUsers")', "_GetWatchlist('VIPUsers')")
Stage 3: where
| where AADEmail !in (allowed_users)
Stage 4: project
| project TimeGenerated, AADEmail, RequestClientApp, QueryText, ResponseRowCount, RequestTarget
Stage 5: extend
| extend timestamp = TimeGenerated, AccountName = tostring(split(AADEmail, "@")[0]), AccountUPNSuffix = tostring(split(AADEmail, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
QueryText | match |
| field:"QueryText" kind:match |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
AADEmail | eq | [] | excludes:AADEmail field:"AADEmail" value:"[]" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AADEmail | project |
QueryText | project |
RequestClientApp | project |
RequestTarget | project |
ResponseRowCount | project |
TimeGenerated | project |
AccountName | extend |
AccountUPNSuffix | extend |
timestamp | extend |
vCenter - Root impersonation
#'Detects when root impersonation occurs.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Rule Body
id: f1fcb22c-b459-42f2-a7ee-7276b5f1309c
name: vCenter - Root impersonation
description: |
'Detects when root impersonation occurs.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
dataTypes:
- vcenter_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
relevantTechniques:
- T1078
query: |
vCenter
| where Message has_all ('ImpersonateUser', 'VcIntegrity', 'root')
| extend user = 'root'
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: user
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
vCenter
Stage 2: where
| where Message has_all ('ImpersonateUser', 'VcIntegrity', 'root')
Stage 3: extend
| extend user = 'root'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Message | match |
| field:"Message" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
user | extend |