Detection rules › Kusto
Kusto rules: 03
App Gateway WAF - Scanner Detection
#'Identifies a match for a Scanner detection user agent based attack in the App Gateway WAF logs. The threshold value in the query can be changed as per your infrastructure's requirements. References: https://owasp.org/www-community/Vulnerability_Scanning_Tools'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Initial Access | |
| Execution | |
| Privilege Escalation | |
| Discovery |
Rule Body
id: 9b8dd8fd-f192-42eb-84f6-541920400a7a
name: App Gateway WAF - Scanner Detection
description: |
'Identifies a match for a Scanner detection user agent based attack in the App Gateway WAF logs. The threshold value in the query can be changed as per your infrastructure's requirements.
References: https://owasp.org/www-community/Vulnerability_Scanning_Tools'
severity: High
status: Available
requiredDataConnectors:
- connectorId: WAF
dataTypes:
- AzureDiagnostics
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- Execution
- InitialAccess
- Reconnaissance
- Discovery
relevantTechniques:
- T1548
- T1203
- T1190
- T1595
- T1046
tags:
- Scanner Detection
query: |
let Threshold = 3;
AGWFirewallLogs
| where Action == "Matched"
| where Message contains "Found User-Agent associated with security scanner"
| project TransactionId, Hostname, RequestUri, TimeGenerated, ClientIp, Message, DetailedMessage, DetailedData
| join kind = inner(
AGWFirewallLogs
| where Action == "Blocked"
) on TransactionId
| extend Uri = strcat(Hostname,RequestUri)
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), TransactionID = make_set(TransactionId), Message = make_set(Message), Detail_Message = make_set(DetailedMessage), Detail_Data = make_set(DetailedData), Total_TransactionId = dcount(TransactionId) by ClientIp, Uri, Action
| where Total_TransactionId >= Threshold
# The Threshold value above can be changed as per your infrastructure's requirement
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Uri
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIp
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let Threshold = 3;
Stage 1: source
AGWFirewallLogs
Stage 2: where
| where Action == "Matched"
Stage 3: where
| where Message contains "Found User-Agent associated with security scanner"
Stage 4: project
| project TransactionId, Hostname, RequestUri, TimeGenerated, ClientIp, Message, DetailedMessage, DetailedData
Stage 5: join
| join kind = inner(
AGWFirewallLogs
| where Action == "Blocked"
) on TransactionId
Stage 6: extend
extend Uri
Stage 7: summarize
summarize Detail_Data, Detail_Message, EndTime, Message, StartTime, Total_TransactionId, TransactionID by ClientIp, Uri, Action
Stage 8: where
where Total_TransactionId >= 3
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | eq |
| field:"Action" kind:eq |
Message | contains |
| field:"Message" kind:contains value:"Found User-Agent associated with security scanner" |
Total_TransactionId | ge |
| field:"Total_TransactionId" kind:ge value:"3" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Action | summarize |
ClientIp | summarize |
Detail_Data | summarize |
Detail_Message | summarize |
EndTime | summarize |
Message | summarize |
StartTime | summarize |
Total_TransactionId | summarize |
TransactionID | summarize |
Uri | summarize |
AWS Security Hub - Detect EC2 Security groups allowing unrestricted high-risk ports
#This query detects EC2 Security Groups that allow unrestricted (0.0.0.0/0 or ::/0) ingress to high-risk ports using AWS Security Hub control EC2.19 findings. Publicly exposed management, database, and service ports (e.g., RDP 3389, SSH 22, SQL 1433/3306) significantly increase the risk of brute-force attacks, exploitation, and lateral movement.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Discovery | |
| Lateral Movement |
Rule Body
id: d2b6fa0f-6a4c-4c48-8c64-5e2e1ac4e7b9
name: AWS Security Hub - Detect EC2 Security groups allowing unrestricted high-risk ports
description: |
This query detects EC2 Security Groups that allow unrestricted (0.0.0.0/0 or ::/0) ingress to high-risk ports using AWS Security Hub control EC2.19 findings.
Publicly exposed management, database, and service ports (e.g., RDP 3389, SSH 22, SQL 1433/3306) significantly increase the risk of brute-force attacks, exploitation, and lateral movement.
severity: High
status: Available
requiredDataConnectors:
- connectorId: AWSSecurityHub
dataTypes:
- AWSSecurityHubFindings
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- LateralMovement
- Discovery
relevantTechniques:
- T1133
- T1021
- T1046
tags:
- AWS Foundational Security Best Practices v1.0.0
- NIST 800-53 r5
query: |
let HighRiskPorts = dynamic([3389,20,23,110,143,3306,8080,1433,9200,9300,25,445,135,21,1434,4333,5432,5500,5601,22,3000,5000,8088,8888]);
AWSSecurityHubFindings
| where RecordState == "ACTIVE" and ComplianceStatus == "FAILED"
| where tostring(AwsSecurityFindingGeneratorId) == "security-control/EC2.19"
or tostring(ComplianceSecurityControlId) == "EC2.19"
| mv-expand Resource = Resources
| where tostring(Resource.Type) == "AwsEc2SecurityGroup"
| extend SGDetails = Resource.Details.AwsEc2SecurityGroup
| extend IpPermissions = SGDetails.IpPermissions
| mv-expand Perm = IpPermissions
| where toint(Perm.FromPort) in (HighRiskPorts)
| mv-expand Range = Perm.IpRanges
| where tostring(Range.CidrIp) in ("0.0.0.0/0", "::/0")
| summarize TimeGenerated = max(TimeGenerated), OpenHighRiskPorts = make_set(tostring(Perm.FromPort))
by AwsAccountId, AwsRegion, AwsSecurityFindingTitle, AwsSecurityFindingDescription,
AwsSecurityFindingId, ComplianceSecurityControlId, SecurityGroupId = tostring(SGDetails.GroupId), SecurityGroupARN = tostring(Resource.Id)
| extend OpenHighRiskPorts = strcat_array(OpenHighRiskPorts, ", ")
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AwsAccountId
- identifier: CloudAppAccountId
columnName: AwsAccountId
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: SecurityGroupARN
customDetails:
ComplianceControlId: ComplianceSecurityControlId
Region: AwsRegion
FindingId: AwsSecurityFindingId
SecurityGroupId: SecurityGroupId
OpenHighRiskPorts: OpenHighRiskPorts
alertDetailsOverride:
alertDisplayNameFormat: "EC2 Security group {{SecurityGroupId}} allows unrestricted high-risk ports"
alertDescriptionFormat: |-
EC2 Security group {{SecurityGroupId}} allows unrestricted (0.0.0.0/0 or ::/0) ingress to high-risk ports: {{OpenHighRiskPorts}}. Restrict or remove the offending rules.
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let HighRiskPorts = dynamic([3389,20,23,110,143,3306,8080,1433,9200,9300,25,445,135,21,1434,4333,5432,5500,5601,22,3000,5000,8088,8888]);
Stage 1: source
AWSSecurityHubFindings
Stage 2: where
| where RecordState == "ACTIVE" and ComplianceStatus == "FAILED"
Stage 3: where
| where tostring(AwsSecurityFindingGeneratorId) == "security-control/EC2.19"
or tostring(ComplianceSecurityControlId) == "EC2.19"
Stage 4: mv-expand
| mv-expand Resource = Resources
Stage 5: where
| where tostring(Resource.Type) == "AwsEc2SecurityGroup"
Stage 6: extend
| extend SGDetails = Resource.Details.AwsEc2SecurityGroup
Stage 7: extend
| extend IpPermissions = SGDetails.IpPermissions
Stage 8: mv-expand
| mv-expand Perm = IpPermissions
Stage 9: where
| where toint(Perm.FromPort) in (HighRiskPorts)
Stage 10: mv-expand
| mv-expand Range = Perm.IpRanges
Stage 11: where
| where tostring(Range.CidrIp) in ("0.0.0.0/0", "::/0")
Stage 12: summarize
| summarize TimeGenerated = max(TimeGenerated), OpenHighRiskPorts = make_set(tostring(Perm.FromPort))
by AwsAccountId, AwsRegion, AwsSecurityFindingTitle, AwsSecurityFindingDescription,
AwsSecurityFindingId, ComplianceSecurityControlId, SecurityGroupId = tostring(SGDetails.GroupId), SecurityGroupARN = tostring(Resource.Id)
Stage 13: extend
| extend OpenHighRiskPorts = strcat_array(OpenHighRiskPorts, ", ")
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AwsSecurityFindingGeneratorId | eq |
| field:"AwsSecurityFindingGeneratorId" kind:eq value:"security-control/EC2.19" |
ComplianceSecurityControlId | eq |
| field:"ComplianceSecurityControlId" kind:eq value:"EC2.19" |
ComplianceStatus | eq |
| field:"ComplianceStatus" kind:eq value:"FAILED" |
Perm.FromPort | in |
| field:"Perm.FromPort" kind:in |
Range.CidrIp | in |
| field:"Range.CidrIp" kind:in |
RecordState | eq |
| field:"RecordState" kind:eq value:"ACTIVE" |
Resource.Type | eq |
| field:"Resource.Type" kind:eq value:"AwsEc2SecurityGroup" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AwsAccountId | summarize |
AwsRegion | summarize |
AwsSecurityFindingDescription | summarize |
AwsSecurityFindingId | summarize |
AwsSecurityFindingTitle | summarize |
ComplianceSecurityControlId | summarize |
OpenHighRiskPorts | extend |
SecurityGroupARN | summarize |
SecurityGroupId | summarize |
TimeGenerated | summarize |
AWSCloudTrail - EC2 Startup Shell Script Changed
#Identifies changes to the EC2 startup script. The shell script will be executed as root/SYSTEM every time the specific instances are booted up. ref : https://github.com/RhinoSecurityLabs/pacu/blob/master/pacu/modules/ec2__startup_shell_script/main.py
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
Rule Body
id: f8577e4d-8481-437b-a94e-06f615985668
name: AWSCloudTrail - EC2 Startup Shell Script Changed
description: |
Identifies changes to the EC2 startup script. The shell script will be executed as root/SYSTEM every time the specific instances are booted up. ref : https://github.com/RhinoSecurityLabs/pacu/blob/master/pacu/modules/ec2__startup_shell_script/main.py
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AWS
dataTypes:
- AWSCloudTrail
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
relevantTechniques:
- T1059
query: |
AWSCloudTrail
| where EventName in~ ("ModifyInstanceAttribute", "CreateLaunchTemplate")
| extend RequestParametersJson = parse_json(RequestParameters)
| where tostring(RequestParametersJson.userData) != ""
| extend userData_ = tostring(RequestParametersJson.userData)
| extend instanceId_ = tostring(RequestParametersJson.instanceId)
| project-away SourceSystem, Category, Type, TenantId, EventVersion, SessionIssuerAccountId
| extend UserName = substring(UserIdentityPrincipalid, indexof_regex(UserIdentityPrincipalid, ":") + 1)
| extend Name = split(UserName,'@')[0],UpnSuffix = split(UserName,'@')[1]
| project TimeGenerated, EventName, UserName, Name, UpnSuffix, instanceId_, AWSRegion, SourceIpAddress, EventSource, UserIdentityArn, EventTypeName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UpnSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIpAddress
customDetails:
InstanceId: instanceId_
AWSRegion: AWSRegion
EventName: EventName
EventSource: EventSource
EventType: EventTypeName
alertDetailsOverride:
alertDisplayNameFormat: 'EC2 startup script changed on {{instanceId_}} by {{UserName}}'
alertDescriptionFormat: 'EC2 startup script changed on {{instanceId_}} by {{UserName}} in {{AWSRegion}}.'
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
AWSCloudTrail
Stage 2: where
| where EventName in~ ("ModifyInstanceAttribute", "CreateLaunchTemplate")
Stage 3: extend
| extend RequestParametersJson = parse_json(RequestParameters)
Stage 4: where
| where tostring(RequestParametersJson.userData) != ""
Stage 5: extend
| extend userData_ = tostring(RequestParametersJson.userData)
Stage 6: extend
| extend instanceId_ = tostring(RequestParametersJson.instanceId)
Stage 7: project-away
| project-away SourceSystem, Category, Type, TenantId, EventVersion, SessionIssuerAccountId
Stage 8: extend
| extend UserName = substring(UserIdentityPrincipalid, indexof_regex(UserIdentityPrincipalid, ":") + 1)
Stage 9: extend
| extend Name = split(UserName,'@')[0],UpnSuffix = split(UserName,'@')[1]
Stage 10: project
| project TimeGenerated, EventName, UserName, Name, UpnSuffix, instanceId_, AWSRegion, SourceIpAddress, EventSource, UserIdentityArn, EventTypeName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventName | in |
| field:"aws::eventName" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AWSRegion | project |
EventName | project |
EventSource | project |
EventTypeName | project |
Name | project |
SourceIpAddress | project |
TimeGenerated | project |
UpnSuffix | project |
UserIdentityArn | project |
UserName | project |
instanceId_ | project |
BitSight - new alert found
#'Rule helps to detect a new alerts generated in BitSight.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Impact |
Rule Body
id: a1275c5e-0ff4-4d15-a7b7-96018cd979f5
name: BitSight - new alert found
description: |
'Rule helps to detect a new alerts generated in BitSight.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: BitSight
dataTypes:
- BitSightAlerts
queryFrequency: 1d
queryPeriod: 24h
triggerOperator: GreaterThan
triggerThreshold: 0
tactics:
- Impact
- InitialAccess
relevantTechniques:
- T1491
- T1190
query: |
let timeframe = 24h;
BitSightAlerts
| where ingestion_time() > ago(timeframe)
| extend Severity = case( Severity contains "INCREASE", "Low",
Severity contains "WARN" or Severity contains "DECREASE", "Medium",
Severity contains "CRITICAL", "High",
"Informational")
| extend CompanyURL = strcat("https://service.bitsighttech.com/app/spm",CompanyURL)
| project CompanyName, Severity, Trigger, CompanyURL, AlertDate, GUID
incidentConfiguration:
createIncident: false
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: 'BitSight: Alert for {{Trigger}} in {{CompanyName}} from bitsight.'
alertDescriptionFormat: 'Alert generated on {{AlertDate}} in BitSight.\n\nCompany URL: {{CompanyURL}}\nAlert GUID: {{GUID}}'
alertSeverityColumnName: Severity
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: CompanyURL
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let timeframe = 24h;
Stage 1: source
BitSightAlerts
Stage 2: where
| where ingestion_time() > ago(timeframe)
Stage 3: extend
| extend Severity = case( Severity contains "INCREASE", "Low",
Severity contains "WARN" or Severity contains "DECREASE", "Medium",
Severity contains "CRITICAL", "High",
"Informational")
Severity =Severity contains "INCREASE""Low"(Severity contains "WARN" or Severity contains "DECREASE")"Medium"Severity contains "CRITICAL""High""Informational"Stage 4: extend
| extend CompanyURL = strcat("https://service.bitsighttech.com/app/spm",CompanyURL)
Stage 5: project
| project CompanyName, Severity, Trigger, CompanyURL, AlertDate, GUID
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertDate | project |
CompanyName | project |
CompanyURL | project |
GUID | project |
Severity | project |
Trigger | project |
BloodHound Attack Path Finding - Add Key Credential Link Privileges on Tier Zero Objects
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
BloodHound Attack Path Finding - Add Key Credential Link Privileges on Tier Zero Objects
Rule Body
id: 34099af9-e79a-4d5a-a50c-c5e51f5bb965
name: BloodHound Attack Path Finding - Add Key Credential Link Privileges on Tier
Zero Objects
description: BloodHound Attack Path Finding - Add Key Credential Link Privileges on
Tier Zero Objects
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: BloodHoundEnterprise
dataTypes:
- BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: BHEAttackPathsData_CL | where Finding == "T0AddKeyCredentialLink"
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: domain_name
customDetails:
Description: ShortDescription
Remediation: ShortRemediation
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
BHEAttackPathsData_CL
Stage 2: where
| where Finding == "T0AddKeyCredentialLink"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"T0AddKeyCredentialLink" |
BloodHound Attack Path Finding - DCOM Users on Tier Zero Computers
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
BloodHound Attack Path Finding - DCOM Users on Tier Zero Computers
Rule Body
id: c322a41b-c759-4aca-ac9a-25b00791c397
name: BloodHound Attack Path Finding - DCOM Users on Tier Zero Computers
description: BloodHound Attack Path Finding - DCOM Users on Tier Zero Computers
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: BloodHoundEnterprise
dataTypes:
- BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: BHEAttackPathsData_CL | where Finding == "T0DCOM"
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: domain_name
customDetails:
Description: ShortDescription
Remediation: ShortRemediation
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
BHEAttackPathsData_CL
Stage 2: where
| where Finding == "T0DCOM"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"T0DCOM" |
Claroty - New Asset
#'Triggers when Claroty reports a new asset event in the environment, indicating that a previously unseen device or system has been discovered and should be reviewed for authorization, ownership, and expected network placement.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Discovery |
Rule Body
id: 6c29b611-ce69-4016-bf99-eca639fee1f5
name: Claroty - New Asset
description: |
'Triggers when Claroty reports a new asset event in the environment, indicating that a previously unseen device
or system has been discovered and should be reviewed for authorization, ownership, and expected network placement.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Discovery
relevantTechniques:
- T1190
- T1133
- T1082
query: |
ClarotyEvent
| where EventOriginalType has 'New Asset' or EventType has 'New Asset'
| extend IPCustomEntity = SrcIpAddr
| extend AlertTitle = strcat('Claroty new asset detected from ', coalesce(SrcIpAddr, 'unknown source'))
| extend AlertDescription = strcat('Claroty reported a new asset event from source IP ', coalesce(SrcIpAddr, 'unknown'), '.')
| project IPCustomEntity, AlertTitle, AlertDescription
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.4
kind: Scheduled
Stages and Predicates
Stage 1: source
ClarotyEvent
Stage 2: where
| where EventOriginalType has 'New Asset' or EventType has 'New Asset'
Stage 3: extend (3 consecutive steps)
| extend IPCustomEntity = SrcIpAddr
| extend AlertTitle = strcat('Claroty new asset detected from ', coalesce(SrcIpAddr, 'unknown source'))
| extend AlertDescription = strcat('Claroty reported a new asset event from source IP ', coalesce(SrcIpAddr, 'unknown'), '.')
Stage 4: project
| project IPCustomEntity, AlertTitle, AlertDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventOriginalType | match |
| field:"EventOriginalType" kind:match value:"New Asset" |
EventType | match |
| field:"EventType" kind:match value:"New Asset" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertDescription | project |
AlertTitle | project |
IPCustomEntity | project |
ClientDeniedAccess
#'Creates an incident in the event a Client has an excessive amounts of denied access requests.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: a9956d3a-07a9-44a6-a279-081a85020cae
name: ClientDeniedAccess
description: |
'Creates an incident in the event a Client has an excessive amounts of denied access requests.'
severity: Medium
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
let threshold = 15;
let rejectedAccess = SymantecVIP
| where isnotempty(RADIUSAuth)
| where RADIUSAuth =~ "Reject"
| summarize Total = count() by ClientIP, bin(TimeGenerated, 15m)
| where Total > threshold
| project ClientIP;
SymantecVIP
| where isnotempty(RADIUSAuth)
| where RADIUSAuth =~ "Reject"
| join kind=inner rejectedAccess on ClientIP
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by ClientIP, User
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: User
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIP
version: 1.0.4
status: Available
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 15;
Let Binding: rejectedAccess
let rejectedAccess = SymantecVIP
| where isnotempty(RADIUSAuth)
| where RADIUSAuth =~ "Reject"
| summarize Total = count() by ClientIP, bin(TimeGenerated, 15m)
| where Total > threshold
| project ClientIP;
Stage 1: source
SymantecVIP
Stage 2: where
| where isnotempty(RADIUSAuth)
Stage 3: where
| where RADIUSAuth =~ "Reject"
Stage 4: join
| join kind=inner rejectedAccess on ClientIP
Stage 5: summarize
summarize EndTime, StartTime by ClientIP, User
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
RADIUSAuth | eq |
| field:"RADIUSAuth" kind:eq value:"Reject" |
RADIUSAuth | is_not_null | field:"RADIUSAuth" kind:is_not_null | |
Total | gt |
| field:"Total" kind:gt value:"15" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ClientIP | summarize |
EndTime | summarize |
StartTime | summarize |
User | summarize |
Cognni Incidents for Low Sensitivity Governance Information
#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.
'Display incidents in which low sensitivity governance information] was placed at risk by user sharing.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection |
Rule Body
id: d2e40c79-fe8c-428e-8cb9-0e2282d4558c
name: Cognni Incidents for Low Sensitivity Governance Information
description: |
'Display incidents in which low sensitivity governance information] was placed at risk by user sharing.'
severity: Low
requiredDataConnectors:
- connectorId: CognniSentinelDataConnector
dataTypes:
- CognniIncidents_CL
queryFrequency: 5h
queryPeriod: 5h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- Collection
relevantTechniques:
- T1530
query: |
let lowRisk = 1;
let governance = 'Governance Information';
CognniIncidents_CL
| where Severity == lowRisk
| where informationType_s == governance
| where TimeGenerated >= ago(5h)
| extend AccountCustomEntity = userId_s
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let lowRisk = 1;
let governance = 'Governance Information';
Stage 1: source
CognniIncidents_CL
Stage 2: where
| where Severity == lowRisk
Stage 3: where
| where informationType_s == governance
Stage 4: where
| where TimeGenerated >= ago(5h)
Stage 5: extend
| extend AccountCustomEntity = userId_s
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Severity | eq |
| field:"Severity" kind:eq value:"1" |
informationType_s | eq |
| field:"informationType_s" kind:eq value:"Governance Information" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
Guardian- Input Output Relevance Policy Violation Detection
#'This alert creates an incident when Input Output Relevance Policy Violation detected from the Guardian.'
Rule Body
id: 76fd5899-0dd1-4336-9519-3c2c0d5d6ecb
name: Guardian- Input Output Relevance Policy Violation Detection
description: |
'This alert creates an incident when Input Output Relevance Policy Violation detected from the Guardian.'
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: BoschAIShield
dataTypes:
- Guardian
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
Guardian
| where PolicyViolatedControlFeature =~ 'Input Output Relevance'
| where Severity =~ 'Informational'
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: NTDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertDisplayNameFormat: Guardian- Input Output Relevance Policy Violation detection
alertDescriptionFormat: |
This query detects Input Output Relevance Policy Violation detected from the Guardian generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further.
alertTacticsColumnName: null
alertSeverityColumnName: Severity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Guardian
Stage 2: where
| where PolicyViolatedControlFeature =~ 'Input Output Relevance'
Stage 3: where
| where Severity =~ 'Informational'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PolicyViolatedControlFeature | eq |
| field:"PolicyViolatedControlFeature" kind:eq value:"Input Output Relevance" |
Severity | eq |
| field:"Severity" kind:eq value:"Informational" |
Illumio VEN Offline Detection Rule
#'Create Microsoft Sentinel Incident When Ven Goes Into Offline state'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Rule Body
id: ec07fcd3-724f-426d-9f53-041801ca5f6c
name: Illumio VEN Offline Detection Rule
description: |
'Create Microsoft Sentinel Incident When Ven Goes Into Offline state'
severity: High
status: Available
requiredDataConnectors:
- connectorId: IllumioSaaSDataConnector
dataTypes:
- Illumio_Auditable_Events_CL
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 60m
queryPeriod: 60m
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
query: |
Illumio_Auditable_Events_CL
| union IllumioSyslogAuditEvents
| where event_type has 'agent_offline_check'
| mv-expand resource_changes
| extend hostname = resource_changes['resource']['workload']['hostname'],
workload_href = resource_changes['resource']['workload']['href'],
workload_labels = resource_changes['resource']['workload']['labels']
| project-away resource_changes, version, notifications, action, severity, status // action field will have filtered ip addr, so no point of using IP entity
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: hostname
alertDetailsOverride:
alertDisplayNameFormat: |
Illumio VEN Offline Incident for {{hostname}}
alertDescriptionFormat: |
Illumio VEN Offline Incident for {{hostname}} generated at {{TimeGenerated}}
version: 1.0.7
kind: Scheduled
Stages and Predicates
Stage 1: source
Illumio_Auditable_Events_CL
Stage 2: union
union of 1 branches
Stage 3: source
IllumioSyslogAuditEvents
Stage 4: where
where event_type has "agent_offline_check"
Stage 5: mv-expand
mv-expand resource_changes
Stage 6: extend
extend hostname, workload_href, workload_labels
Stage 7: project-away
project-away action, notifications, resource_changes, severity, status, version
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event_type | match |
| field:"event_type" kind:match value:"agent_offline_check" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
hostname | extend |
workload_href | extend |
workload_labels | extend |
Jira - New site admin user
#'Detects new site admin user.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Rule Body
id: b894593a-2b4c-4573-bc47-78715224a6f5
name: Jira - New site admin user
description: |
'Detects new site admin user.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: JiraAuditAPI
dataTypes:
- JiraAudit
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1078
query: |
JiraAudit
| where EventMessage =~ 'User added to group'
| where ObjectItemName =~ 'site-admins'
| extend user = todynamic(AssociatedItems)[0]['name']
| extend AccountCustomEntity = user
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
JiraAudit
Stage 2: where
| where EventMessage =~ 'User added to group'
Stage 3: where
| where ObjectItemName =~ 'site-admins'
Stage 4: extend
| extend user = todynamic(AssociatedItems)[0]['name']
Stage 5: extend
| extend AccountCustomEntity = user
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventMessage | eq |
| field:"EventMessage" kind:eq value:"User added to group" |
ObjectItemName | eq |
| field:"ObjectItemName" kind:eq value:"site-admins" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
user | extend |
AccountCustomEntity | extend |
License Grace Period Started
#Detects when a Veeam license grace period starts. This might indicate potential licensing issues that need attention.
Rule Body
id: e4828d99-bb06-40b3-8f9d-0f68fb61e9ee
name: License Grace Period Started
description: Detects when a Veeam license grace period starts. This might indicate
potential licensing issues that need attention.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 3h
queryPeriod: 3h
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "let license_editions_lookup = union isfuzzy=true (datatable(Edition:string,\
\ EditionDescription:string)[]), (_GetWatchlist(\"license_editions_lookup\")); \n\
let license_types_lookup = union isfuzzy=true (datatable(Type:string, TypeDescription:string)[]),\
\ (_GetWatchlist(\"license_types_lookup\")); \nVeeam_GetSecurityEvents\n| where\
\ instanceId == 24060\n| extend Edition = extract(\"Edition=\\\"([^\\\"]*)\\\"\"\
, 1, SyslogMessage)\n| lookup kind=leftouter (license_editions_lookup) \n on\
\ $left.Edition == $right.Edition\n| extend Type = extract(\"Type=\\\"([^\\\"]*)\\\
\"\", 1, SyslogMessage)\n| lookup kind=leftouter (license_types_lookup) \n on\
\ $left.Type == $right.Type\n| extend TenantID = extract(\"TenantID=\\\"([^\\\"\
]*)\\\"\", 1, SyslogMessage)\n| extend DaysLeft = extract(\"DaysLeft=\\\"([^\\\"\
]*)\\\"\", 1, SyslogMessage)\n| extend SupportLeft = extract(\"SupportLeft=\\\"\
([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n Date = format_datetime(TimeGenerated,\
\ 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n EventId = instanceId,\n\
\ [\"License Edition\"] = EditionDescription,\n [\"License Type\"] = TypeDescription,\n\
\ [\"Days Left\"] = DaysLeft,\n [\"Days of Support Left\"] = SupportLeft,\n\
\ MessageDetails = Description,\n Severity = SeverityDescription\n"
version: 1.0.1
kind: Scheduled
customDetails:
Date: Date
VbrHostName: DataSource
EventId: EventId
MessageDetails: MessageDetails
Severity: Severity
Stages and Predicates
Parameters
let license_editions_lookup = union isfuzzy=true (datatable(Edition:string, EditionDescription:string)[]), (_GetWatchlist("license_editions_lookup"));
let license_types_lookup = union isfuzzy=true (datatable(Type:string, TypeDescription:string)[]), (_GetWatchlist("license_types_lookup"));
Stage 1: source
Veeam_GetSecurityEvents
Stage 2: where
| where instanceId == 24060
Stage 3: extend
| extend Edition = extract("Edition=\"([^\"]*)\"", 1, SyslogMessage)
Stage 4: kusto:lookup
lookup kind=leftouter (license_editions_lookup) on $left.Edition, $right.Edition
Stage 5: extend
extend Type
Stage 6: kusto:lookup
lookup kind=leftouter (license_types_lookup) on $left.Type, $right.Type
Stage 7: extend (3 consecutive steps)
extend DaysLeft, SupportLeft, TenantID
Stage 8: project
project DataSource, Date, Days Left, Days of Support Left, EventId, License Edition, License Type, MessageDetails, Severity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"24060" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
Days Left | project |
Days of Support Left | project |
EventId | project |
License Edition | project |
License Type | project |
MessageDetails | project |
Severity | project |
Office ASR rule triggered from browser spawned office process.
#The attacker sends a spearphishing email to a user. The email contains a link which points to a website that eventually presents the user a download of an MS Office document. This document contains a malicious macro. The macro triggers one of the ASR rules. This detection looks for Office ASR violations triggered by an Office document opened from a browser. Note: be aware that you need to have the proper ASR rules enabled for this detection to work.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceEvents action any: Defender event |
Rule Body
id: 30580043-2451-4d35-b49f-065728529f4a
name: Office ASR rule triggered from browser spawned office process.
description: |
The attacker sends a spearphishing email to a user. The email contains a link which points to a website that eventually presents the user a download of an MS Office document. This document contains a malicious macro. The macro triggers one of the ASR rules.
This detection looks for Office ASR violations triggered by an Office document opened from a browser.
Note: be aware that you need to have the proper ASR rules enabled for this detection to work.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceEvents
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1566.002
query: |
// Add your own browsers here as well.
let browsers = dynamic(["iexplore.exe", "chrome.exe", "firefox.exe", "msedge.exe"]);
DeviceEvents
| where ActionType contains "Office"
| where InitiatingProcessFileName in~ (browsers) or InitiatingProcessParentFileName in~ (browsers)
// Ignore docx, xlsx and pptx files. These don't contain macros.
| where not(FileName endswith ".docx" or FileName endswith ".xlsx" or FileName endswith ".pptx")
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Sid
columnName: AccountSid
- identifier: Name
columnName: AccountName
- identifier: NTDomain
columnName: AccountDomain
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- entityType: Process
fieldMappings:
- identifier: CommandLine
columnName: ProcessCommandLine
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let browsers = dynamic(["iexplore.exe", "chrome.exe", "firefox.exe", "msedge.exe"]);
Stage 1: source
DeviceEvents
Stage 2: where
| where ActionType contains "Office"
Stage 3: where
| where InitiatingProcessFileName in~ (browsers) or InitiatingProcessParentFileName in~ (browsers)
Stage 4: where
| where not(FileName endswith ".docx" or FileName endswith ".xlsx" or FileName endswith ".pptx")
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | contains |
| field:"ActionType" kind:contains value:"Office" |
InitiatingProcessFileName | in |
| field:"InitiatingProcessFileName" kind:in |
InitiatingProcessParentFileName | in |
| field:"InitiatingProcessParentFileName" kind:in |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
FileName | ends_with | .docx | excludes:FileName field:"FileName" value:".docx" |
FileName | ends_with | .pptx | excludes:FileName field:"FileName" value:".pptx" |
FileName | ends_with | .xlsx | excludes:FileName field:"FileName" value:".xlsx" |
Pathlock TDnR - J2EE Security Audit Events
#Detects events from the SAP J2EE (Java) security audit log, forwarded by Pathlock Threat Detection and Response. Security audit events from the Java stack may reveal authentication failures, authorization violations, or exploitation attempts targeting SAP NetWeaver Application Server Java.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c36
name: Pathlock TDnR - J2EE Security Audit Events
kind: Scheduled
description: >-
Detects events from the SAP J2EE (Java) security audit log, forwarded by Pathlock Threat Detection
and Response. Security audit events from the Java stack may reveal authentication failures,
authorization violations, or exploitation attempts targeting SAP NetWeaver Application Server Java.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1082
query: |
Pathlock_TDnR_CL
| where DataSource == "J2EE_SEC_AUD_LOG"
| 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 == "J2EE_SEC_AUD_LOG"
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:"J2EE_SEC_AUD_LOG" |
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 |
Pathlock TDnR - LDAP Synchronization Application Log Events
#Detects security events from the SAP LDAP synchronization application log (SLG1), forwarded by Pathlock Threat Detection and Response. LDAP sync anomalies may indicate unauthorized identity directory manipulation, credential harvesting via LDAP queries, or synchronization abuse to propagate unauthorized access.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c58
name: Pathlock TDnR - LDAP Synchronization Application Log Events
kind: Scheduled
description: >-
Detects security events from the SAP LDAP synchronization application log (SLG1),
forwarded by Pathlock Threat Detection and Response. LDAP sync anomalies may indicate
unauthorized identity directory manipulation, credential harvesting via LDAP queries,
or synchronization abuse to propagate unauthorized access.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1552
query: |
Pathlock_TDnR_CL
| where DataSource == "SLG1_LDAPSYNC"
| 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 == "SLG1_LDAPSYNC"
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:"SLG1_LDAPSYNC" |
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 |
Threat Essentials - Time series anomaly for data size transferred to public internet
#'Identifies anomalous data transfer to public networks. The query leverages built-in KQL anomaly detection algorithms that detects large deviations from a baseline pattern. A sudden increase in data transferred to unknown public networks is an indication of data exfiltration attempts and should be investigated. The higher the score, the further it is from the baseline value. The output is aggregated to provide summary view of unique source IP to destination IP address and port bytes sent traffic observed in the flagged anomaly hour. The source IP addresses which were sending less than bytessentperhourthreshold have been exluded whose value can be adjusted as needed . You may have to run queries for individual source IP addresses from SourceIPlist to determine if anything looks suspicious'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Rule Body
id: b49a1093-cbf6-4973-89ac-2eef98f533c6
name: Threat Essentials - Time series anomaly for data size transferred to public internet
description: |
'Identifies anomalous data transfer to public networks. The query leverages built-in KQL anomaly detection algorithms that detects large deviations from a baseline pattern.
A sudden increase in data transferred to unknown public networks is an indication of data exfiltration attempts and should be investigated.
The higher the score, the further it is from the baseline value.
The output is aggregated to provide summary view of unique source IP to destination IP address and port bytes sent traffic observed in the flagged anomaly hour.
The source IP addresses which were sending less than bytessentperhourthreshold have been exluded whose value can be adjusted as needed .
You may have to run queries for individual source IP addresses from SourceIPlist to determine if anything looks suspicious'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CiscoASA
dataTypes:
- CommonSecurityLog
- connectorId: CiscoAsaAma
dataTypes:
- CommonSecurityLog
- connectorId: PaloAltoNetworks
dataTypes:
- CommonSecurityLog
- connectorId: AzureMonitor(VMInsights)
dataTypes:
- VMConnection
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 1
tactics:
- Exfiltration
relevantTechniques:
- T1030
tags:
- DEV-0537
query: |
let starttime = 14d;
let endtime = 1d;
let timeframe = 1h;
let scorethreshold = 5;
let bytessentperhourthreshold = 10;
let PrivateIPregex = @'^127\.|^10\.|^172\.1[6-9]\.|^172\.2[0-9]\.|^172\.3[0-1]\.|^192\.168\.';
let TimeSeriesData = (union isfuzzy=true
(
VMConnection
| where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime)))
| where isnotempty(DestinationIp) and isnotempty(SourceIp)
| extend DestinationIpType = iff(DestinationIp matches regex PrivateIPregex,"private" ,"public" )
| where DestinationIpType == "public" | extend DeviceVendor = "VMConnection"
| project TimeGenerated, BytesSent, DeviceVendor
| make-series TotalBytesSent=sum(BytesSent) on TimeGenerated from startofday(ago(starttime)) to startofday(ago(endtime)) step timeframe by DeviceVendor
),
(
CommonSecurityLog
| where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime)))
| where isnotempty(DestinationIP) and isnotempty(SourceIP)
| extend DestinationIpType = iff(DestinationIP matches regex PrivateIPregex,"private" ,"public" )
| where DestinationIpType == "public"
| project TimeGenerated, SentBytes, DeviceVendor
| make-series TotalBytesSent=sum(SentBytes) on TimeGenerated from startofday(ago(starttime)) to startofday(ago(endtime)) step timeframe by DeviceVendor
)
);
//Filter anomolies against TimeSeriesData
let TimeSeriesAlerts = materialize(TimeSeriesData
| extend (anomalies, score, baseline) = series_decompose_anomalies(TotalBytesSent, scorethreshold, -1, 'linefit')
| mv-expand TotalBytesSent to typeof(double), TimeGenerated to typeof(datetime), anomalies to typeof(double),score to typeof(double), baseline to typeof(long)
| where anomalies > 0 | extend AnomalyHour = TimeGenerated
| extend TotalBytesSentinMBperHour = round(((TotalBytesSent / 1024)/1024),2), baselinebytessentperHour = round(((baseline / 1024)/1024),2), score = round(score,2)
| project DeviceVendor, AnomalyHour, TimeGenerated, TotalBytesSentinMBperHour, baselinebytessentperHour, anomalies, score);
let AnomalyHours = materialize(TimeSeriesAlerts | where TimeGenerated > ago(2d) | project TimeGenerated);
//Union of all BaseLogs aggregated per hour
let BaseLogs = (union isfuzzy=true
(
CommonSecurityLog
| where isnotempty(DestinationIP) and isnotempty(SourceIP)
| where TimeGenerated > ago(2d)
| extend DateHour = bin(TimeGenerated, 1h) // create a new column and round to hour
| where DateHour in ((AnomalyHours)) //filter the dataset to only selected anomaly hours
| extend DestinationIpType = iff(DestinationIP matches regex PrivateIPregex,"private" ,"public" )
| where DestinationIpType == "public"
| extend SentBytesinMB = ((SentBytes / 1024)/1024), ReceivedBytesinMB = ((ReceivedBytes / 1024)/1024)
| summarize HourlyCount = count(), TimeGeneratedMax=arg_max(TimeGenerated, *), DestinationIPList=make_set(DestinationIP, 100), DestinationPortList = make_set(DestinationPort,100), TotalSentBytesinMB = sum(SentBytesinMB), TotalReceivedBytesinMB = sum(ReceivedBytesinMB) by SourceIP, DeviceVendor, TimeGeneratedHour=bin(TimeGenerated,1h)
| where TotalSentBytesinMB > bytessentperhourthreshold
| sort by TimeGeneratedHour asc, TotalSentBytesinMB desc
| extend Rank=row_number(1, prev(TimeGeneratedHour) != TimeGeneratedHour) // Ranking the dataset per Hourly Partition
| where Rank < 10 // Selecting Top 10 records with Highest BytesSent in each Hour
| project DeviceVendor, TimeGeneratedHour, TimeGeneratedMax, SourceIP, DestinationIPList, DestinationPortList, TotalSentBytesinMB, TotalReceivedBytesinMB, Rank
),
(
VMConnection
| where isnotempty(DestinationIp) and isnotempty(SourceIp)
| where TimeGenerated > ago(2d)
| extend DateHour = bin(TimeGenerated, 1h) // create a new column and round to hour
| where DateHour in ((AnomalyHours)) //filter the dataset to only selected anomaly hours
| extend SourceIP = SourceIp, DestinationIP = DestinationIp
| extend DestinationIpType = iff(DestinationIp matches regex PrivateIPregex,"private" ,"public" )
| where DestinationIpType == "public" | extend DeviceVendor = "VMConnection"
| extend SentBytesinMB = ((BytesSent / 1024)/1024), ReceivedBytesinMB = ((BytesReceived / 1024)/1024)
| summarize HourlyCount = count(),TimeGeneratedMax=arg_max(TimeGenerated, *), DestinationIPList=make_set(DestinationIP, 100), DestinationPortList = make_set(DestinationPort, 100), TotalSentBytesinMB = sum(SentBytesinMB),TotalReceivedBytesinMB = sum(ReceivedBytesinMB) by SourceIP, DeviceVendor, TimeGeneratedHour=bin(TimeGenerated,1h)
| where TotalSentBytesinMB > bytessentperhourthreshold
| sort by TimeGeneratedHour asc, TotalSentBytesinMB desc
| extend Rank=row_number(1, prev(TimeGeneratedHour) != TimeGeneratedHour) // Ranking the dataset per Hourly Partition
| where Rank < 10 // Selecting Top 10 records with Highest BytesSent in each Hour
| project DeviceVendor, TimeGeneratedHour, TimeGeneratedMax, SourceIP, DestinationIPList, DestinationPortList, TotalSentBytesinMB, TotalReceivedBytesinMB, Rank
)
);
// Join against base logs to retrive records associated with the hour of anomoly
TimeSeriesAlerts
| where TimeGenerated > ago(2d)
| join (
BaseLogs | extend AnomalyHour = TimeGeneratedHour
) on DeviceVendor, AnomalyHour | sort by score desc
| project DeviceVendor, AnomalyHour,TimeGeneratedMax, SourceIP, DestinationIPList, DestinationPortList, TotalSentBytesinMB, TotalReceivedBytesinMB, TotalBytesSentinMBperHour, baselinebytessentperHour, score, anomalies
| summarize EventCount = count(), StartTimeUtc= min(TimeGeneratedMax), EndTimeUtc= max(TimeGeneratedMax), SourceIPMax= arg_max(SourceIP,*), TotalBytesSentinMB = sum(TotalSentBytesinMB), TotalBytesReceivedinMB = sum(TotalReceivedBytesinMB), SourceIPList = make_set(SourceIP, 100), DestinationIPList = make_set(DestinationIPList, 100) by AnomalyHour,TotalBytesSentinMBperHour, baselinebytessentperHour, score, anomalies
| project DeviceVendor, AnomalyHour, StartTimeUtc, EndTimeUtc, SourceIPMax, SourceIPList, DestinationIPList, DestinationPortList, TotalBytesSentinMB, TotalBytesReceivedinMB, TotalBytesSentinMBperHour, baselinebytessentperHour, score, anomalies, EventCount
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIPMax
version: 1.0.4
kind: Scheduled
Stages and Predicates
Parameters
let starttime = 14d;
let endtime = 1d;
let timeframe = 1h;
let scorethreshold = 5;
let bytessentperhourthreshold = 10;
let TimeSeriesData and let TimeSeriesAlerts are inlined into the numbered stages below.
Let Binding: PrivateIPregex
let PrivateIPregex = @'^127\.|^10\.|^172\.1[6-9]\.|^172\.2[0-9]\.|^172\.3[0-1]\.|^192\.168\.';
Let Binding: AnomalyHours
let AnomalyHours = materialize(TimeSeriesAlerts | where TimeGenerated > ago(2d) | project TimeGenerated);
Let Binding: BaseLogs
let BaseLogs = (union isfuzzy=true
(
CommonSecurityLog
| where isnotempty(DestinationIP) and isnotempty(SourceIP)
| where TimeGenerated > ago(2d)
| extend DateHour = bin(TimeGenerated, 1h)
| where DateHour in ((AnomalyHours))
| extend DestinationIpType = iff(DestinationIP matches regex PrivateIPregex,"private" ,"public" )
| where DestinationIpType == "public"
| extend SentBytesinMB = ((SentBytes / 1024)/1024), ReceivedBytesinMB = ((ReceivedBytes / 1024)/1024)
| summarize HourlyCount = count(), TimeGeneratedMax=arg_max(TimeGenerated, *), DestinationIPList=make_set(DestinationIP, 100), DestinationPortList = make_set(DestinationPort,100), TotalSentBytesinMB = sum(SentBytesinMB), TotalReceivedBytesinMB = sum(ReceivedBytesinMB) by SourceIP, DeviceVendor, TimeGeneratedHour=bin(TimeGenerated,1h)
| where TotalSentBytesinMB > bytessentperhourthreshold
| sort by TimeGeneratedHour asc, TotalSentBytesinMB desc
| extend Rank=row_number(1, prev(TimeGeneratedHour) != TimeGeneratedHour)
| where Rank < 10
| project DeviceVendor, TimeGeneratedHour, TimeGeneratedMax, SourceIP, DestinationIPList, DestinationPortList, TotalSentBytesinMB, TotalReceivedBytesinMB, Rank
),
(
VMConnection
| where isnotempty(DestinationIp) and isnotempty(SourceIp)
| where TimeGenerated > ago(2d)
| extend DateHour = bin(TimeGenerated, 1h)
| where DateHour in ((AnomalyHours))
| extend SourceIP = SourceIp, DestinationIP = DestinationIp
| extend DestinationIpType = iff(DestinationIp matches regex PrivateIPregex,"private" ,"public" )
| where DestinationIpType == "public" | extend DeviceVendor = "VMConnection"
| extend SentBytesinMB = ((BytesSent / 1024)/1024), ReceivedBytesinMB = ((BytesReceived / 1024)/1024)
| summarize HourlyCount = count(),TimeGeneratedMax=arg_max(TimeGenerated, *), DestinationIPList=make_set(DestinationIP, 100), DestinationPortList = make_set(DestinationPort, 100), TotalSentBytesinMB = sum(SentBytesinMB),TotalReceivedBytesinMB = sum(ReceivedBytesinMB) by SourceIP, DeviceVendor, TimeGeneratedHour=bin(TimeGenerated,1h)
| where TotalSentBytesinMB > bytessentperhourthreshold
| sort by TimeGeneratedHour asc, TotalSentBytesinMB desc
| extend Rank=row_number(1, prev(TimeGeneratedHour) != TimeGeneratedHour)
| where Rank < 10
| project DeviceVendor, TimeGeneratedHour, TimeGeneratedMax, SourceIP, DestinationIPList, DestinationPortList, TotalSentBytesinMB, TotalReceivedBytesinMB, Rank
)
);
Stages 1 to 22 define let TimeSeriesAlerts as the rule's main pipeline source. Stages 23 to 28 use it.
Stage 1: union
union of 2 branches
Stage 2: source
VMConnection
Stage 3: where
| where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime)))
Stage 4: where
| where isnotempty(DestinationIp) and isnotempty(SourceIp)
Stage 5: extend
| extend DestinationIpType = iff(DestinationIp matches regex PrivateIPregex,"private" ,"public" )
DestinationIpType =DestinationIp matches regex "PrivateIPregex""private""public"Stage 6: where
| where DestinationIpType == "public"
Stage 7: extend
| extend DeviceVendor = "VMConnection"
Stage 8: project
| project TimeGenerated, BytesSent, DeviceVendor
The stages below score time-series anomalies (make-series, series_decompose_anomalies).
Stage 9: make-series
| make-series TotalBytesSent=sum(BytesSent) on TimeGenerated from startofday(ago(starttime)) to startofday(ago(endtime)) step timeframe by DeviceVendor
Stage 10: source
CommonSecurityLog
Stage 11: where
| where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime)))
Stage 12: where
| where isnotempty(DestinationIP) and isnotempty(SourceIP)
Stage 13: extend
| extend DestinationIpType = iff(DestinationIP matches regex PrivateIPregex,"private" ,"public" )
DestinationIpType =DestinationIP matches regex "PrivateIPregex""private""public"Stage 14: where
| where DestinationIpType == "public"
Stage 15: project
| project TimeGenerated, SentBytes, DeviceVendor
Stage 16: make-series
| make-series TotalBytesSent=sum(SentBytes) on TimeGenerated from startofday(ago(starttime)) to startofday(ago(endtime)) step timeframe by DeviceVendor
Stage 17: extend
extend anomalies, baseline, score
Stage 18: mv-expand
mv-expand TotalBytesSent
Stage 19: where
where anomalies > 0
Stage 20: extend
extend AnomalyHour
Stage 21: extend
extend TotalBytesSentinMBperHour, baselinebytessentperHour, score
Stage 22: project
project AnomalyHour, DeviceVendor, TimeGenerated, TotalBytesSentinMBperHour, anomalies, baselinebytessentperHour, score
Stage 23: where
where TimeGenerated > ago(172800s)
Stage 24: join
join (BaseLogs) on DeviceVendor, AnomalyHour
Stage 25: sort
sort by score
Stage 26: project
project AnomalyHour, DestinationIPList, DestinationPortList, DeviceVendor, SourceIP, TimeGeneratedMax, TotalBytesSentinMBperHour, TotalReceivedBytesinMB, TotalSentBytesinMB, anomalies, baselinebytessentperHour, score
Stage 27: summarize
summarize DestinationIPList, EndTimeUtc, EventCount, SourceIPList, SourceIPMax, StartTimeUtc, TotalBytesReceivedinMB, TotalBytesSentinMB by AnomalyHour, TotalBytesSentinMBperHour, baselinebytessentperHour, score, anomalies
Stage 28: project
project AnomalyHour, DestinationIPList, DestinationPortList, DeviceVendor, EndTimeUtc, EventCount, SourceIPList, SourceIPMax, StartTimeUtc, TotalBytesReceivedinMB, TotalBytesSentinMB, TotalBytesSentinMBperHour, anomalies, baselinebytessentperHour, score
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AnomalyHour | project |
DestinationIPList | project |
DestinationPortList | project |
DeviceVendor | project |
EndTimeUtc | project |
EventCount | project |
SourceIPList | project |
SourceIPMax | project |
StartTimeUtc | project |
TotalBytesReceivedinMB | project |
TotalBytesSentinMB | project |
TotalBytesSentinMBperHour | project |
anomalies | project |
baselinebytessentperHour | project |
score | project |
TI map Email entity to SecurityEvent
#'Identifies a match in SecurityEvent table from any Email IOC from TI'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 2fc5d810-c9cc-491a-b564-841427ae0e50
name: TI map Email entity to SecurityEvent
description: |
'Identifies a match in SecurityEvent table from any Email IOC from TI'
severity: Medium
requiredDataConnectors:
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvents
- connectorId: WindowsForwardedEvents
dataTypes:
- WindowsEvent
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1566
query: |
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let emailregex = @'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$';
ThreatIntelligenceIndicator
//Filtering the table for Email related IOCs
| where isnotempty(EmailSenderAddress)
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now()
// using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
| join kind=innerunique (
(union isfuzzy=true
(SecurityEvent
| where TimeGenerated >= ago(dt_lookBack) and isnotempty(TargetUserName)
//Normalizing the column to lower case for exact match with EmailSenderAddress column
| extend TargetUserName = tolower(TargetUserName)
// renaming timestamp column so it is clear the log this came from SecurityEvent table
| extend SecurityEvent_TimeGenerated = TimeGenerated
),
(WindowsEvent
| where TimeGenerated >= ago(dt_lookBack)
| extend TargetUserName = tostring(EventData.TargetUserName)
| where isnotempty(TargetUserName)
//Normalizing the column to lower case for exact match with EmailSenderAddress column
| extend TargetUserName = tolower(TargetUserName)
// renaming timestamp column so it is clear the log this came from SecurityEvent table
| extend SecurityEvent_TimeGenerated = TimeGenerated
))
)
on $left.EmailSenderAddress == $right.TargetUserName
| where SecurityEvent_TimeGenerated < ExpirationDateTime
| summarize SecurityEvent_TimeGenerated = arg_max(SecurityEvent_TimeGenerated, *) by IndicatorId, TargetUserName
| project SecurityEvent_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore,
EmailSenderName, EmailRecipient, EmailSourceDomain, EmailSourceIpAddress, EmailSubject, FileHashValue, FileHashType, Computer, EventID, TargetUserName, Activity, IpAddress, AccountType,
LogonTypeName, LogonProcessName, Status, SubStatus
| extend HostName = tostring(split(Computer, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))
| extend timestamp = SecurityEvent_TimeGenerated
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: TargetUserName
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IpAddress
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
version: 1.3.6
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let emailregex = @'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$';
Stage 1: source
ThreatIntelligenceIndicator
Stage 2: where
| where isnotempty(EmailSenderAddress)
Stage 3: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 4: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
Stage 5: where
| where Active == true and ExpirationDateTime > now()
Stage 6: join
join kind=innerunique (...) on $left.EmailSenderAddress, $right.TargetUserName
Stage 7: where
where SecurityEvent_TimeGenerated < ExpirationDateTime
Stage 8: summarize
summarize SecurityEvent_TimeGenerated by IndicatorId, TargetUserName
Stage 9: project
project AccountType, Activity, ActivityGroupNames, Computer, ConfidenceScore, Description, EmailRecipient, EmailSenderName, EmailSourceDomain, EmailSourceIpAddress, EmailSubject, EventID, ExpirationDateTime, FileHashType, FileHashValue, IndicatorId, IpAddress, LogonProcessName, LogonTypeName, SecurityEvent_TimeGenerated, Status, SubStatus, TargetUserName, ThreatType, Url
Stage 10: extend
extend DnsDomain, HostName
Stage 11: extend
extend timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
EmailSenderAddress | is_not_null | field:"EmailSenderAddress" kind:is_not_null | |
TargetUserName | is_not_null | field:"TargetUserName" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountType | project |
Activity | project |
ActivityGroupNames | project |
Computer | project |
ConfidenceScore | project |
Description | project |
EmailRecipient | project |
EmailSenderName | project |
EmailSourceDomain | project |
EmailSourceIpAddress | project |
EmailSubject | project |
EventID | project |
ExpirationDateTime | project |
FileHashType | project |
FileHashValue | project |
IndicatorId | project |
IpAddress | project |
LogonProcessName | project |
LogonTypeName | project |
SecurityEvent_TimeGenerated | project |
Status | project |
SubStatus | project |
TargetUserName | project |
ThreatType | project |
Url | project |
DnsDomain | extend |
HostName | extend |
timestamp | extend |
TIE privileged accounts issues
#'Searches for triggered Indicators of Exposures related to privileged accounts issues.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 5c170c73-75ba-48ea-8dfc-e4e2d4f23979
name: TIE privileged accounts issues
description: |
'Searches for triggered Indicators of Exposures related to privileged accounts issues.'
severity: Low
requiredDataConnectors:
- connectorId: TenableIE
dataTypes:
- Tenable_IE_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
// For the query to work properly, make sure you have imported the afad_parser.yaml parser into the workspace
// Retrieve the parser here: https://aka.ms/sentinel-TenableApp-afad-parser
// Then, create the Kusto Function with alias afad_parser
let SeverityTable=datatable(Severity:string,Level:int) [
"low", 1,
"medium", 2,
"high", 3,
"critical", 4
];
let codeNameList = datatable(Codename:string)["C-PRIV-ACCOUNTS-SPN", "C-NATIVE-ADM-GROUP-MEMBERS", "C-KRBTGT-PASSWORD", "C-PROTECTED-USERS-GROUP-UNUSED", "C-ADMINCOUNT-ACCOUNT-PROPS", "C-ADM-ACC-USAGE", "C-LAPS-UNSECURE-CONFIG", "C-DISABLED-ACCOUNTS-PRIV-GROUPS"];
afad_parser
| where MessageType == 0 and Codename in~ (codeNameList)
| lookup kind=leftouter SeverityTable on Severity
| order by Level
| extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
version: 1.0.1
kind: Scheduled
Stages and Predicates
Let Binding: SeverityTable
let SeverityTable = datatable(Severity:string,Level:int) [
"low", 1,
"medium", 2,
"high", 3,
"critical", 4
];
Let Binding: codeNameList
let codeNameList = datatable(Codename:string)["C-PRIV-ACCOUNTS-SPN", "C-NATIVE-ADM-GROUP-MEMBERS", "C-KRBTGT-PASSWORD", "C-PROTECTED-USERS-GROUP-UNUSED", "C-ADMINCOUNT-ACCOUNT-PROPS", "C-ADM-ACC-USAGE", "C-LAPS-UNSECURE-CONFIG", "C-DISABLED-ACCOUNTS-PRIV-GROUPS"];
Stage 1: source
afad_parser
Stage 2: where
| where MessageType == 0 and Codename in~ (codeNameList)
Stage 3: kusto:lookup
lookup kind=leftouter (SeverityTable) on Severity
Stage 4: sort
sort by Level
Stage 5: extend
extend DnsDomain, HostName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Codename | in |
| field:"Codename" kind:in value:"codeNameList" |
MessageType | eq |
| field:"MessageType" kind:eq value:"0" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DnsDomain | extend |
HostName | extend |
User Account Created Using Incorrect Naming Format
#This query looks for accounts being created where the name does not match a defined pattern. Attackers may attempt to add accounts as a means of establishing persistant access to an environment, looking for anomalies in created accounts may help identify illegitimately created accounts. Created accounts should be investigated to ensure they were legitimated created. The user_regex field in the query needs to be populated with the expected pattern for the environment before deployment. Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts#accounts-not-following-naming-policies
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Authentication Method Changed for Privileged Account (Kusto)
- Account Created and Deleted in Short Timeframe (Kusto)
- Account Created And Deleted Within A Close Time Frame (Sigma)
- Account created from non-approved sources (Kusto)
- Account created or deleted by non-approved user (Kusto)
- Addition of a Temporary Access Pass to a Privileged Account (Kusto)
- Authentication Method Changed for Privileged Account (Kusto)
- Authentication Methods Changed for Privileged Account (Kusto)
Rule Body
id: ee55dc85-d2da-48c1-a6c0-3eaee62a8d56
name: User Account Created Using Incorrect Naming Format
description: |
'This query looks for accounts being created where the name does not match a defined pattern.
Attackers may attempt to add accounts as a means of establishing persistant access to an environment, looking for anomalies in created accounts may help identify illegitimately created accounts.
Created accounts should be investigated to ensure they were legitimated created.
The user_regex field in the query needs to be populated with the expected pattern for the environment before deployment.
Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts#accounts-not-following-naming-policies'
severity: Low
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1136.003
tags:
- AADSecOpsGuide
query: |
// Add the environments expected username format regex below before deploying
let user_regex = "";
AuditLogs
| where OperationName =~ "Add user"
| where Result =~ "success"
| extend userAgent = tostring(AdditionalDetails[0].value)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
| extend InitiatedBy = tostring(iff(isnotempty(InitiatingUserPrincipalName),InitiatingUserPrincipalName, InitiatingAppName))
| extend AddedUser = tostring(TargetResources[0].userPrincipalName)
| where AddedUser matches regex user_regex
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
| extend TargetAccountName = tostring(split(AddedUser, "@")[0]), TargetAccountUPNSuffix = tostring(split(AddedUser, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: InitiatingAppName
- identifier: AadUserId
columnName: InitiatingAppServicePrincipalId
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingUserPrincipalName
- identifier: Name
columnName: InitiatingAccountName
- identifier: UPNSuffix
columnName: InitiatingAccountUPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAadUserId
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AddedUser
- identifier: Name
columnName: TargetAccountName
- identifier: UPNSuffix
columnName: TargetAccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: InitiatingIPAddress
version: 1.1.0
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others" ]
Stages and Predicates
Parameters
let user_regex = "";
Stage 1: source
AuditLogs
Stage 2: where
| where OperationName =~ "Add user"
Stage 3: where
| where Result =~ "success"
Stage 4: extend (8 consecutive steps)
| extend userAgent = tostring(AdditionalDetails[0].value)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
| extend InitiatedBy = tostring(iff(isnotempty(InitiatingUserPrincipalName),InitiatingUserPrincipalName, InitiatingAppName))
| extend AddedUser = tostring(TargetResources[0].userPrincipalName)
Stage 5: where
| where AddedUser matches regex user_regex
Stage 6: extend
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
Stage 7: extend
| extend TargetAccountName = tostring(split(AddedUser, "@")[0]), TargetAccountUPNSuffix = tostring(split(AddedUser, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
OperationName | eq |
| field:"OperationName" kind:eq value:"Add user" |
Result | eq |
| field:"Result" kind:eq value:"success" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
userAgent | extend |
InitiatingAppName | extend |
InitiatingAppServicePrincipalId | extend |
InitiatingUserPrincipalName | extend |
InitiatingAadUserId | extend |
InitiatingIPAddress | extend |
InitiatedBy | extend |
AddedUser | extend |
InitiatingAccountName | extend |
InitiatingAccountUPNSuffix | extend |
TargetAccountName | extend |
TargetAccountUPNSuffix | extend |
Vectra Create Detection Alert for Hosts
#This analytic rule is looking for new attacker behaviors observed by the Vectra Platform. The intent is to create entries in the SecurityAlert table for every new detection attached to an entity monitored by the Vectra Platform
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Rule Body
id: fb861539-da19-4266-831f-99459b8e7605
name: Vectra Create Detection Alert for Hosts
version: 1.0.1
kind: Scheduled
description: This analytic rule is looking for new attacker behaviors observed by the Vectra Platform. The intent is to create entries in the SecurityAlert table for every new detection attached to an entity monitored by the Vectra Platform
status: Available
requiredDataConnectors:
- connectorId: VectraXDR
dataTypes:
- Detections_Data_CL
severity: Medium
queryFrequency: 10m
queryPeriod: 10m
suppressionDuration: PT1H
suppressionEnabled: false
triggerOperator: GreaterThan
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1546
query: |
VectraDetections
| where Type == "host"
| extend
entity_uid = ['Entity UID'],
entity_id = ['Entity ID'],
entity_type = ['Entity Type'],
detection_id = ['Detection ID'],
detection = ['Detection Name'],
category = ['Detection Category'],
detection_url = ['Vectra Pivot'],
mitre = Mitre,
tags = Tags
| summarize arg_max(TimeGenerated, *) by ['Detection ID'], entity_uid
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: entity_uid
incidentConfiguration:
createIncident: false
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
customDetails:
entity_id: entity_id
tags: tags
entity_type: entity_type
detection_id: detection_id
mitre_techniques: mitre
alertDetailsOverride:
alertDisplayNameFormat: Vectra AI Detection- {{detection}}
alertDescriptionFormat: Vectra AI has detected {{category}} - {{detection}} on entity {{entity_uid}}.
alertDynamicProperties:
- alertProperty: AlertLink
value: detection_url
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Stage 1: source
VectraDetections
Stage 2: where
| where Type == "host"
Stage 3: extend
| extend
entity_uid = ['Entity UID'],
entity_id = ['Entity ID'],
entity_type = ['Entity Type'],
detection_id = ['Detection ID'],
detection = ['Detection Name'],
category = ['Detection Category'],
detection_url = ['Vectra Pivot'],
mitre = Mitre,
tags = Tags
Stage 4: summarize
| summarize arg_max(TimeGenerated, *) by ['Detection ID'], entity_uid
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Type | eq |
| field:"Type" kind:eq value:"host" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
entity_uid | summarize |