Detection rules › Kusto

Kusto rules: 0c

ApexOne - Commands in Url

#
Status
available
Severity
high
Time window
1h
Source
github.com/Azure/Azure-Sentinel

'Detects commands in Url.'

MITRE ATT&CK Coverage

Rule Body

id: 4a9a5900-67b7-11ec-90d6-0242ac120003
name: ApexOne - Commands in Url
description: |
  'Detects commands in Url.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
  - T1133
query: |
  TMApexOneEvent
  | where EventSubType in ("WB:7", "CnC:Block", "VAD", "700106") 
  | where UrlOriginal contains 'cat%20/etc/passwd' or UrlOriginal contains '/etc/passwd' or UrlOriginal contains 'ping -i' or UrlOriginal contains '/usr/bin/id(' or UrlOriginal contains '%2f%75%73%72%2f%62%69%6e%2f%69%64' or UrlOriginal contains 'phpinfo()' or UrlOriginal contains '%70%68%70%69%6e%66%6f%28%29' or UrlOriginal contains ';id' or UrlOriginal contains '%3b%69%64' or UrlOriginal contains '/bin/bash -c' or UrlOriginal contains '%2f%62%69%6e%2f%62%61%73%68%20%2d%63%27' or UrlOriginal contains '/bin/bash' or UrlOriginal contains '%2f%62%69%6e%2f%62%61%73%68' or UrlOriginal contains 'sleep(' or UrlOriginal contains '%73%6c%65%65%70%28' or UrlOriginal contains 'curl' or UrlOriginal contains '%63%75%72%6c' or UrlOriginal contains '&dir' or UrlOriginal contains '%26%64%69%72' or UrlOriginal contains '& dir' or UrlOriginal =~ '%26%20%64%69%72' or UrlOriginal contains '<script>' or UrlOriginal  contains '%3c%73%63%72%69%70%74%3e' or UrlOriginal contains 'eval(' or UrlOriginal contains '%65%76%61%6c%28' or UrlOriginal contains 'exec(' or UrlOriginal contains '%65%78%65%63%28' or UrlOriginal contains 'whoami' or UrlOriginal contains '%77%68%6f%61%6d%69' or UrlOriginal contains 'wget' or UrlOriginal contains 'python' or UrlOriginal contains 'gcc' or UrlOriginal contains 'uname' or UrlOriginal contains 'systeminfo' or UrlOriginal contains '%77%67%65%74' or UrlOriginal contains '%70%79%74%68%6f%6e' or UrlOriginal contains '%75%6e%61%6d%65' or UrlOriginal =~ '%73%79%73%74%65%6d%69%6e%66%6f' 
  | extend UrlCustomEntity = UrlOriginal
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: UrlCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Stage 1: source

TMApexOneEvent

Stage 2: where

| where EventSubType in ("WB:7", "CnC:Block", "VAD", "700106")

Stage 3: where

| where UrlOriginal contains 'cat%20/etc/passwd' or UrlOriginal contains '/etc/passwd' or UrlOriginal contains 'ping -i' or UrlOriginal contains '/usr/bin/id(' or UrlOriginal contains '%2f%75%73%72%2f%62%69%6e%2f%69%64' or UrlOriginal contains 'phpinfo()' or UrlOriginal contains '%70%68%70%69%6e%66%6f%28%29' or UrlOriginal contains ';id' or UrlOriginal contains '%3b%69%64' or UrlOriginal contains '/bin/bash -c' or UrlOriginal contains '%2f%62%69%6e%2f%62%61%73%68%20%2d%63%27' or UrlOriginal contains '/bin/bash' or UrlOriginal contains '%2f%62%69%6e%2f%62%61%73%68' or UrlOriginal contains 'sleep(' or UrlOriginal contains '%73%6c%65%65%70%28' or UrlOriginal contains 'curl' or UrlOriginal contains '%63%75%72%6c' or UrlOriginal contains '&dir' or UrlOriginal contains '%26%64%69%72' or UrlOriginal contains '& dir' or UrlOriginal =~ '%26%20%64%69%72' or UrlOriginal contains '<script>' or UrlOriginal  contains '%3c%73%63%72%69%70%74%3e' or UrlOriginal contains 'eval(' or UrlOriginal contains '%65%76%61%6c%28' or UrlOriginal contains 'exec(' or UrlOriginal contains '%65%78%65%63%28' or UrlOriginal contains 'whoami' or UrlOriginal contains '%77%68%6f%61%6d%69' or UrlOriginal contains 'wget' or UrlOriginal contains 'python' or UrlOriginal contains 'gcc' or UrlOriginal contains 'uname' or UrlOriginal contains 'systeminfo' or UrlOriginal contains '%77%67%65%74' or UrlOriginal contains '%70%79%74%68%6f%6e' or UrlOriginal contains '%75%6e%61%6d%65' or UrlOriginal =~ '%73%79%73%74%65%6d%69%6e%66%6f'

Stage 4: extend

| extend UrlCustomEntity = UrlOriginal

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventSubTypein
  • 700106
  • CnC:Block
  • VAD
  • WB:7
field:"EventSubType" kind:in
UrlOriginalcontains
  • %26%64%69%72
  • %2f%62%69%6e%2f%62%61%73%68
  • %2f%62%69%6e%2f%62%61%73%68%20%2d%63%27
  • %2f%75%73%72%2f%62%69%6e%2f%69%64
  • %3b%69%64
  • %3c%73%63%72%69%70%74%3e
  • %63%75%72%6c
  • %65%76%61%6c%28
  • %65%78%65%63%28
  • %70%68%70%69%6e%66%6f%28%29
  • %70%79%74%68%6f%6e
  • %73%6c%65%65%70%28
  • %75%6e%61%6d%65
  • %77%67%65%74
  • %77%68%6f%61%6d%69
  • & dir
  • &dir
  • /bin/bash
  • /bin/bash -c
  • /etc/passwd
  • /usr/bin/id(
  • ;id
  • <script>
  • cat%20/etc/passwd
  • curl
  • eval(
  • exec(
  • gcc
  • phpinfo()
  • ping -i
  • python
  • sleep(
  • systeminfo
  • uname
  • wget
  • whoami
field:"UrlOriginal" kind:contains
UrlOriginaleq
  • %26%20%64%69%72
  • %73%79%73%74%65%6d%69%6e%66%6f
field:"UrlOriginal" kind:eq

Output Fields

These fields are emitted when the rule matches.

FieldSource
UrlCustomEntityextend

AWSCloudTrail - AWS GuardDuty detector disabled or suspended

#
Status
available
Severity
high
Time window
1d
Source
github.com/Azure/Azure-Sentinel

Identifies AWS GuardDuty detectors being disabled or suspended. This behavior can indicate defense evasion and should be validated with the initiating identity and approved administrative activity.

MITRE ATT&CK Coverage

TacticTechniques
Defense Impairment

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 9da99021-d318-4711-a78a-6dea76129b3a
name: AWSCloudTrail - AWS GuardDuty detector disabled or suspended
description: |
  Identifies AWS GuardDuty detectors being disabled or suspended. This behavior can indicate defense evasion and should be validated with the initiating identity and approved administrative activity.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: AWS
    dataTypes:
      - AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1562
query: |
    AWSCloudTrail
    | where (EventName == "DeleteDetector" and isempty(ErrorCode) and isempty( ErrorMessage)) or (EventName == "UpdateDetector" and tostring(parse_json(RequestParameters).enable) == "false" and isempty(ErrorCode) and isempty( ErrorMessage))
    | 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]), "")
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
  UserIdentityArn: UserIdentityArn
  UserName: UserName
  SourceIpAddress: SourceIpAddress
alertDetailsOverride:
  alertDisplayNameFormat: 'AWS GuardDuty detector disabled or suspended by {{AccountName}}'
  alertDescriptionFormat: 'AWS GuardDuty detector activity {{EventName}} was detected for {{AccountName}} from {{SourceIpAddress}}.'
version: 1.0.3
kind: Scheduled

Stages and Predicates

Stage 1: source

AWSCloudTrail

Stage 2: where

| where (EventName == "DeleteDetector" and isempty(ErrorCode) and isempty( ErrorMessage)) or (EventName == "UpdateDetector" and tostring(parse_json(RequestParameters).enable) == "false" and isempty(ErrorCode) and isempty( ErrorMessage))

Stage 3: 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]), "")

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ErrorCodeis_null
  • (no value, null check)
field:"aws::errorCode" kind:is_null
ErrorMessageis_null
  • (no value, null check)
field:"aws::errorMessage" kind:is_null
EventNameeq
  • DeleteDetector
  • UpdateDetector corpus 2 (kusto 1, panther 1)
field:"aws::eventName" kind:eq
RequestParameters.enableeq
  • false transforms: tostring, kusto_accessor_expr:{"expression":"tostring(parse_json(RequestParameters).enable)","field":"RequestParameters.enable"}, kusto_dynamic_path
field:"RequestParameters.enable" kind:eq value:"false"

Output Fields

These fields are emitted when the rule matches.

FieldSource
UserIdentityArnextend
UserNameextend
AccountNameextend
AccountUPNSuffixextend

AWSCloudTrail - Creation of EC2 policy and then privilege escalation

#
Status
available
Severity
high
Time window
1d
Group by
AccountName, AccountUPNSuffix, EventName, EventSource, PolicyName, RecipientAccountId, SourceIpAddress, UserIdentityArn, UserIdentityType
Source
github.com/Azure/Azure-Sentinel

Identifies creation of IAM policies that grant broad EC2 permissions or EC2 actions combined with IAM pass-role capability, followed by policy attachment activity. This pattern can indicate attempts to gain elevated cloud execution privileges and should be validated as authorized change activity.

MITRE ATT&CK Coverage

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: a694e977-740c-4578-9f8f-5e39029f1d23
name: AWSCloudTrail - Creation of EC2 policy and then privilege escalation
description: |
  Identifies creation of IAM policies that grant broad EC2 permissions or EC2 actions combined with IAM pass-role
  capability, followed by policy attachment activity. This pattern can indicate attempts to gain elevated cloud
  execution privileges and should be validated as authorized change activity.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: AWS
    dataTypes:
      - AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
  - PrivilegeEscalation
  - Persistence
relevantTechniques:
  - T1484
  - T1098.003
query: |
  let EventNameList = dynamic(["AttachUserPolicy","AttachRolePolicy","AttachGroupPolicy"]);
  let createPolicy =  dynamic(["CreatePolicy", "CreatePolicyVersion"]);
  let timeframe = 1d;
  let lookback = 14d;
  // Creating Master table with all the events to use with materialize for better performance
  let EventInfo = AWSCloudTrail
  | where TimeGenerated >= ago(lookback)
  | where EventName in (EventNameList) or EventName in (createPolicy)
  | 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]), "");
  //Checking for Policy creation event with Full Admin Privileges since lookback period.
  let FullAdminPolicyEvents =  materialize(  EventInfo
  | where TimeGenerated >= ago(lookback)
  | where EventName in (createPolicy)
  | extend PolicyName = tostring(parse_json(RequestParameters).policyName)
  | extend Statement = parse_json(tostring((parse_json(RequestParameters).policyDocument))).Statement
  | mvexpand Statement
  | extend Action = parse_json(Statement).Action , Effect = tostring(parse_json(Statement).Effect), Resource = tostring(parse_json(Statement).Resource), Condition = tostring(parse_json(Statement).Condition)
  | extend Action = tostring(Action)
  | where Effect =~ "Allow" and ((((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "ec2:*") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "ec2:RunInstances") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "ec2:Run*")) or (Action contains "ec2:*") or (Action contains "ec2:ListInstances" and Action contains "ec2:StartInstance" and Action contains "ec2:ModifyInstanceAttribute") or (Action contains "ec2:List*" and Action contains "ec2:Start*" and Action contains "ec2:Modify*")) and Resource == "*" and Condition == ""
  | distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, UserIdentityArn, RecipientAccountId, AccountName, AccountUPNSuffix
  | project-rename StartTime = TimeGenerated  );
  let PolicyAttach = materialize(  EventInfo
  | where TimeGenerated >= ago(timeframe)
  | where EventName in (EventNameList) and isempty(ErrorCode) and isempty(ErrorMessage)
  | extend PolicyName = tostring(split(tostring(parse_json(RequestParameters).policyArn),"/")[1])
  | summarize AttachEventCount=count(), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventSource, EventName,   UserIdentityType , UserIdentityArn, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, PolicyName
  | extend AttachEvent = pack("StartTime", StartTime, "EndTime", EndTime, "EventName", EventName, "UserIdentityType",   UserIdentityType,  "AccountName", AccountName, "AccountUPNSuffix", AccountUPNSuffix, "RecipientAccountId", RecipientAccountId, "UserIdentityArn", UserIdentityArn, "SourceIpAddress", SourceIpAddress)
  | project EventSource, PolicyName, AttachEvent, RecipientAccountId, AccountName, AccountUPNSuffix, AttachEventCount
  );
  // Joining the list of PolicyNames and checking if it has been attached to any Roles/Users/Groups.
  // These Roles/Users/Groups will be Privileged and can be used by adversaries as pivot point for privilege escalation via multiple ways.
  FullAdminPolicyEvents
  | join kind=leftouter
  (
      PolicyAttach
  )
  on PolicyName
  | project-away PolicyName1
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
      - identifier: CloudAppAccountId
        columnName: RecipientAccountId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIpAddress
customDetails:
  PolicyName: PolicyName
  EventName: EventName
  RecipientAccountId: RecipientAccountId
  UserIdentityArn: UserIdentityArn
alertDetailsOverride:
  alertDisplayNameFormat: 'AWS EC2 privilege escalation policy activity: {{PolicyName}} by {{AccountName}}'
  alertDescriptionFormat: 'Detected {{EventName}} for policy {{PolicyName}} in account {{RecipientAccountId}}.'
version: 1.0.4
kind: Scheduled

Stages and Predicates

Parameters

let EventNameList = dynamic(["AttachUserPolicy","AttachRolePolicy","AttachGroupPolicy"]);
let createPolicy = dynamic(["CreatePolicy", "CreatePolicyVersion"]);
let timeframe = 1d;
let lookback = 14d;

let EventInfo and let FullAdminPolicyEvents are inlined into the numbered stages below.

Let Binding: PolicyAttach used in Stage 15

let PolicyAttach = materialize(  EventInfo
| where TimeGenerated >= ago(timeframe)
| where EventName in (EventNameList) and isempty(ErrorCode) and isempty(ErrorMessage)
| extend PolicyName = tostring(split(tostring(parse_json(RequestParameters).policyArn),"/")[1])
| summarize AttachEventCount=count(), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventSource, EventName,   UserIdentityType , UserIdentityArn, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, PolicyName
| extend AttachEvent = pack("StartTime", StartTime, "EndTime", EndTime, "EventName", EventName, "UserIdentityType",   UserIdentityType,  "AccountName", AccountName, "AccountUPNSuffix", AccountUPNSuffix, "RecipientAccountId", RecipientAccountId, "UserIdentityArn", UserIdentityArn, "SourceIpAddress", SourceIpAddress)
| project EventSource, PolicyName, AttachEvent, RecipientAccountId, AccountName, AccountUPNSuffix, AttachEventCount
);

Stages 1 to 14 define let FullAdminPolicyEvents as the rule's main pipeline source. Stages 15 to 17 use it.

Stage 1: source

AWSCloudTrail

Stage 2: where

| where TimeGenerated >= ago(lookback)

Stage 3: where

| where EventName in (EventNameList) or EventName in (createPolicy)

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: where

| where TimeGenerated >= ago(lookback)

Stage 6: where

| where EventName in (createPolicy)

Stage 7: extend

| extend PolicyName = tostring(parse_json(RequestParameters).policyName)

Stage 8: extend

| extend Statement = parse_json(tostring((parse_json(RequestParameters).policyDocument))).Statement

Stage 9: mv-expand

| mvexpand Statement

Stage 10: extend

| extend Action = parse_json(Statement).Action , Effect = tostring(parse_json(Statement).Effect), Resource = tostring(parse_json(Statement).Resource), Condition = tostring(parse_json(Statement).Condition)

Stage 11: extend

| extend Action = tostring(Action)

Stage 12: where

| where Effect =~ "Allow" and ((((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "ec2:*") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "ec2:RunInstances") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "ec2:Run*")) or (Action contains "ec2:*") or (Action contains "ec2:ListInstances" and Action contains "ec2:StartInstance" and Action contains "ec2:ModifyInstanceAttribute") or (Action contains "ec2:List*" and Action contains "ec2:Start*" and Action contains "ec2:Modify*")) and Resource == "*" and Condition == ""

Stage 13: distinct

| distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, UserIdentityArn, RecipientAccountId, AccountName, AccountUPNSuffix

Stage 14: project-rename

| project-rename StartTime = TimeGenerated

Stage 15: join

FullAdminPolicyEvents
| join kind=leftouter
(
    PolicyAttach
)
on PolicyName

Stage 16: project-away

project-away PolicyName1

Stage 17: summarize aggregation inside the join branch

summarize by EventSource, EventName, UserIdentityType, UserIdentityArn, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, PolicyName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actioncontains
  • ec2:*
  • ec2:List*
  • ec2:ListInstances
  • ec2:Modify*
  • ec2:ModifyInstanceAttribute
  • ec2:Run*
  • ec2:RunInstances
  • ec2:Start*
  • ec2:StartInstance
  • iam:*
  • iam:PassRole
field:"Action" kind:contains
Effecteq
  • Allow
field:"Effect" kind:eq value:"Allow"
ErrorCodeis_null
  • (no value, null check)
field:"ErrorCode" kind:is_null
ErrorMessageis_null
  • (no value, null check)
field:"ErrorMessage" kind:is_null
EventNamein
  • AttachGroupPolicy corpus 17 (kusto 15, panther 2)
  • AttachGroupPolicy
  • AttachRolePolicy corpus 17 (kusto 15, panther 2)
  • AttachRolePolicy
  • AttachUserPolicy corpus 17 (kusto 15, panther 2)
  • AttachUserPolicy
  • CreatePolicy corpus 14 (kusto 12, panther 2)
  • CreatePolicy
  • CreatePolicyVersion corpus 14 (kusto 12, panther 2)
  • CreatePolicyVersion
field:"aws::eventName" kind:in
Resourceeq
  • *
field:"Resource" kind:eq value:"*"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNamesummarize
AccountUPNSuffixsummarize
EventNamesummarize
EventSourcesummarize
PolicyNamesummarize
RecipientAccountIdsummarize
SourceIpAddresssummarize
UserIdentityArnsummarize
UserIdentityTypesummarize

CYFIRMA - Attack Surface - Weak Certificate Exposure - 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.

Status
available
Severity
medium
Time window
5m
Source
github.com/Azure/Azure-Sentinel

'This alert indicates that a weak or insecure SSL/TLS certificate has been detected on a public-facing asset monitored by Cyfirma. Such certificates do not meet modern encryption standards and are considered insecure, especially for handling sensitive transactions. This exposure increases the risk of man-in-the-middle attacks and loss of data integrity or confidentiality. Immediate remediation is advised by replacing weak certificates with strong, industry-compliant ones.'

MITRE ATT&CK Coverage

TacticTechniques
Reconnaissance
Resource Development
Initial Access
Defense Impairment
Credential Access

Rule Body

id: 5a617ff2-3e3d-44e7-b761-9f0d542ae191
name: CYFIRMA - Attack Surface - Weak Certificate Exposure - Medium Rule
description: |
  'This alert indicates that a weak or insecure SSL/TLS certificate has been detected on a public-facing asset monitored by Cyfirma. 
  Such certificates do not meet modern encryption standards and are considered insecure, especially for handling sensitive transactions. 
  This exposure increases the risk of man-in-the-middle attacks and loss of data integrity or confidentiality. 
  Immediate remediation is advised by replacing weak certificates with strong, industry-compliant ones.'
version: 1.0.1
kind: Scheduled
severity: Medium
requiredDataConnectors:
  - connectorId: CyfirmaAttackSurfaceAlertsConnector
    dataTypes:
      - CyfirmaASCertificatesAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - DefenseEvasion
  - ResourceDevelopment
  - Reconnaissance
  - InitialAccess
  - CredentialAccess
relevantTechniques:
  - T1553
  - T1588
  - T1595
  - T1190
  - T1552
query: |
  // Medium Severity Weak Certificate Exposure Detected
  let timeFrame = 5m;
  CyfirmaASCertificatesAlerts_CL
  | where severity == 'High' and TimeGenerated between (ago(timeFrame) .. now())
  | extend
      Description=description,
      FirstSeen=first_seen,
      LastSeen=last_seen,
      ValidFrom=valid_from,
      ValidTo=valid_to,
      RiskScore=risk_score,
      Domain=sub_domain,
      TopDomain=top_domain,
      Protocols=protocols,
      SelfSigned=self_signed,
      AlertUID=alert_uid,
      UID=uid,
      CertificateData= cert_data,
      CertificateHash=cert_hash,
      IssuedBy=issued_by,
      IssuedTo=issued_to,
      ProviderName='CYFIRMA',
      ProductName='DeCYFIR/DeTCT'
  | project
      TimeGenerated,
      Description,
      Domain,
      TopDomain,
      RiskScore,
      FirstSeen,
      LastSeen,
      ValidFrom,
      ValidTo,
      Protocols,
      SelfSigned,
      CertificateData,
      CertificateHash,
      IssuedBy,
      IssuedTo,
      ProviderName,
      ProductName
entityMappings:
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: HostName
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: TopDomain
      - identifier: DnsDomain
        columnName: Domain
customDetails:
  TimeGenerated: TimeGenerated
  CertificateData: CertificateData
  CertificateHash: CertificateHash
  FirstSeen: FirstSeen
  LastSeen: LastSeen
  ValidFrom: ValidFrom
  ValidTo: ValidTo
  Protocols: Protocols
  SelfSigned: SelfSigned
  IssuedBy: IssuedBy
  IssuedTo: IssuedTo
  Description: Description
  RiskScore: RiskScore
  TopDomain: TopDomain
  Domain: Domain
alertDetailsOverride:
  alertDisplayNameFormat: CYFIRMA - Medium Severity Weak Certificate Exposure Detected for this Domain  - {{Domain}}
  alertDescriptionFormat: CYFIRMA - Medium Severity Weak Certificate Exposure Detected  - {{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

CyfirmaASCertificatesAlerts_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,
    ValidFrom=valid_from,
    ValidTo=valid_to,
    RiskScore=risk_score,
    Domain=sub_domain,
    TopDomain=top_domain,
    Protocols=protocols,
    SelfSigned=self_signed,
    AlertUID=alert_uid,
    UID=uid,
    CertificateData= cert_data,
    CertificateHash=cert_hash,
    IssuedBy=issued_by,
    IssuedTo=issued_to,
    ProviderName='CYFIRMA',
    ProductName='DeCYFIR/DeTCT'

Stage 4: project

| project
    TimeGenerated,
    Description,
    Domain,
    TopDomain,
    RiskScore,
    FirstSeen,
    LastSeen,
    ValidFrom,
    ValidTo,
    Protocols,
    SelfSigned,
    CertificateData,
    CertificateHash,
    IssuedBy,
    IssuedTo,
    ProviderName,
    ProductName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
CertificateDataproject
CertificateHashproject
Descriptionproject
Domainproject
FirstSeenproject
IssuedByproject
IssuedToproject
LastSeenproject
ProductNameproject
Protocolsproject
ProviderNameproject
RiskScoreproject
SelfSignedproject
TimeGeneratedproject
TopDomainproject
ValidFromproject
ValidToproject

DomainTools New Domain Discovery Indicators Ingested

#
Severity
medium
Time window
1h
Source
github.com/Azure/Azure-Sentinel

Creates an incident whenever DomainTools Domain Discovery new indicators (domains) are ingested in the last 1 hour.

Rule Body

id: f8c223c1-1a28-4b28-8e9f-5e33b610916b
name: DomainTools New Domain Discovery Indicators Ingested
description: Creates an incident whenever DomainTools Domain Discovery new indicators (domains) are ingested in the last 1 hour.
kind: Scheduled
severity: Medium
requiredDataConnectors:
  - connectorId: DomainTools
    dataTypes:
      - DomainToolsThreatIntelDomains_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: "DomainToolsThreatIntelDomains(1h,\"domaindiscovery\")"
entityMappings:
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: DomainCustomEntity
eventGroupingSettings:
  aggregationKind: SingleAlert
version: 1.0.0

Stages and Predicates

Stage 1: source

DomainToolsThreatIntelDomains(1h,"domaindiscovery")

Firmware Updates (Microsoft Defender for IoT)

#
Status
available
Severity
medium
Time window
1h
Source
github.com/Azure/Azure-Sentinel

'This alert leverages Defender for IoT to detect unauthorized firmware updates that may indicate malicious activity on the network such as a cyber threat that attempts to manipulate PLC firmware to compromise PLC function.'

MITRE ATT&CK Coverage

TacticTechniques
Persistence
Inhibit Response Function

Rule Body

id: 7cad4b66-5e83-4756-8de4-f21315ab1e77
name: Firmware Updates (Microsoft Defender for IoT)
description: |
  'This alert leverages Defender for IoT to detect unauthorized firmware updates that may indicate malicious activity on the network such as a cyber threat that attempts to manipulate PLC firmware to compromise PLC function.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: IoT
    dataTypes:
      - SecurityAlert (ASC for IoT)
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T0857
query: |
  SecurityAlert
  | where ProviderName == "IoTSecurity"
  | where AlertName has_any ("Beckhoff Software Changed", "Firmware Version Changed", "Firmware Change", "Firmware Changed", "Firmware Update")
  | extend ExtendedProperties = parse_json(ExtendedProperties)
  | where tostring(ExtendedProperties.isNew) == "True"
  | extend DeviceId = tostring(ExtendedProperties.DeviceId), 
           SourceDeviceAddress = tostring(ExtendedProperties.SourceDeviceAddress), 
           DestDeviceAddress = tostring(ExtendedProperties.DestinationDeviceAddress), 
           RemediationSteps = tostring(parse_json(RemediationSteps)[0]), 
           Protocol = tostring(ExtendedProperties.Protocol), 
           AlertManagementUri = tostring(ExtendedProperties.AlertManagementUri)
  | project
    TimeGenerated,
    DeviceId,
    ProductName,
    ProductComponentName,
    AlertSeverity,
    AlertName,
    Description,
    Protocol,
    SourceDeviceAddress,
    DestDeviceAddress,
    RemediationSteps,
    Tactics,
    Entities,
    VendorOriginalId,
    AlertLink,
    AlertManagementUri,
    Techniques
entityMappings:
sentinelEntitiesMappings:
  - columnName: Entities
eventGroupingSettings:
  aggregationKind: AlertPerResult
customDetails:
  Sensor: DeviceId
  Protocol: Protocol
  VendorOriginalId: VendorOriginalId
  AlertManagementUri: AlertManagementUri
alertDetailsOverride:
  alertDisplayNameFormat: (MDIoT) {{AlertName}}
  alertDescriptionFormat: (MDIoT) {{Description}}
  alertTacticsColumnName: Tactics
  alertSeverityColumnName: AlertSeverity
  alertDynamicProperties: 
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: RemediationSteps
      value: RemediationSteps
    - alertProperty: Techniques
      value: Techniques
    - alertProperty: ProductComponentName
      value: ProductComponentName
    - alertProperty: AlertLink
      value: AlertLink
version: 1.0.3
kind: Scheduled

Stages and Predicates

Stage 1: source

SecurityAlert

Stage 2: where

| where ProviderName == "IoTSecurity"

Stage 3: where

| where AlertName has_any ("Beckhoff Software Changed", "Firmware Version Changed", "Firmware Change", "Firmware Changed", "Firmware Update")

Stage 4: extend

| extend ExtendedProperties = parse_json(ExtendedProperties)

Stage 5: where

| where tostring(ExtendedProperties.isNew) == "True"

Stage 6: extend

| extend DeviceId = tostring(ExtendedProperties.DeviceId), 
         SourceDeviceAddress = tostring(ExtendedProperties.SourceDeviceAddress), 
         DestDeviceAddress = tostring(ExtendedProperties.DestinationDeviceAddress), 
         RemediationSteps = tostring(parse_json(RemediationSteps)[0]), 
         Protocol = tostring(ExtendedProperties.Protocol), 
         AlertManagementUri = tostring(ExtendedProperties.AlertManagementUri)

Stage 7: project

| project
  TimeGenerated,
  DeviceId,
  ProductName,
  ProductComponentName,
  AlertSeverity,
  AlertName,
  Description,
  Protocol,
  SourceDeviceAddress,
  DestDeviceAddress,
  RemediationSteps,
  Tactics,
  Entities,
  VendorOriginalId,
  AlertLink,
  AlertManagementUri,
  Techniques

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AlertNamematch
  • Beckhoff Software Changed transforms: term
  • Firmware Change transforms: term
  • Firmware Changed transforms: term
  • Firmware Update transforms: term
  • Firmware Version Changed transforms: term
field:"AlertName" kind:match
ExtendedProperties.isNeweq
  • True transforms: tostring, kusto_dynamic_path corpus 15 (kusto 15)
field:"ExtendedProperties.isNew" kind:eq value:"True"
ProviderNameeq
  • IoTSecurity corpus 15 (kusto 15)
field:"ProviderName" kind:eq value:"IoTSecurity"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertLinkproject
AlertManagementUriproject
AlertNameproject
AlertSeverityproject
Descriptionproject
DestDeviceAddressproject
DeviceIdproject
Entitiesproject
ProductComponentNameproject
ProductNameproject
Protocolproject
RemediationStepsproject
SourceDeviceAddressproject
Tacticsproject
Techniquesproject
TimeGeneratedproject
VendorOriginalIdproject

M2131_EventLogManagementPostureChanged_EL1

#
Status
available
Severity
medium
Time window
7d
Group by
AssessedResourceId, MaturityLevel, RecommendationDisplayName
Source
github.com/Azure/Azure-Sentinel

'This alert is desinged to monitor Azure policies aligned with the Maturity Model for Event Log Management (M-21-31) standard. The alert triggers when EL1 policy compliance falls below 70% within a 1 week timeframe.'

MITRE ATT&CK Coverage

TacticTechniques
Discovery

Rule Body

id: 036ce0a8-a1ff-4731-a078-02b3207fa4f3
name: M2131_EventLogManagementPostureChanged_EL1
description: |
  'This alert is desinged to monitor Azure policies aligned with the Maturity Model for Event Log Management (M-21-31) standard. The alert triggers when EL1 policy compliance falls below 70% within a 1 week timeframe.'
severity: Medium
status: Available
requiredDataConnectors: []
queryFrequency: 7d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
relevantTechniques:
  - T1082
query: |
  SecurityRecommendation
  | where RecommendationDisplayName <> ""
  | extend MaturityLevel=iff(RecommendationDisplayName has_any("agent","extension","retention","logs encryption","collect","retained","log profile","CloudTrail","metric","AWS","GCP","DNS","Auditing","Flow","logging","usage"), "Event Logging (EL0)",
  iff(RecommendationDisplayName has_any("container registries","logic apps","Enhanced monitoring","IoT Hub","Event Hub","App Service", "Kubernetes","updates","email", "automation", "adaptive"), "Advanced Event Logging (EL3)",
  iff(RecommendationDisplayName has_any("signatures","CMK","CMEK","double encryption","managed key","KMS","container","Watcher"), "Intermediate Event Logging (EL2)",
  iff(RecommendationDisplayName has_any("Exploit Guard","endpoint protection","Antimalware","health","VPC","Defender","Vulnerabilities","vulnerability","diagnostic","Key","activity log alert",""), "Basic Event Logging (EL1)","Other"))))
  | summarize arg_max(TimeGenerated, *) by RecommendationDisplayName, AssessedResourceId, MaturityLevel
  | summarize Failed = countif(RecommendationState == "Unhealthy"), Passed = countif(RecommendationState == "Healthy"), Total = countif(RecommendationState == "Healthy" or RecommendationState == "Unhealthy") by MaturityLevel
  | extend PassedControls = (Passed/todouble(Total))*100
  | extend RemediationLink = strcat('https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/22')
  | project MaturityLevel, Total, PassedControls, Passed, Failed, RemediationLink, LastObserved=now()
  | where MaturityLevel <> ''
  | where MaturityLevel == "Basic Event Logging (EL1)"
  | where PassedControls < 70 
  //Adjust Either FailedRatePercentage or PasedRatePercentage Thresholds within   Organizational Needs
  | sort by PassedControls desc
  | extend URLCustomEntity = RemediationLink
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: URLCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

SecurityRecommendation

Stage 2: where

| where RecommendationDisplayName <> ""

Stage 3: extend

| extend MaturityLevel=iff(RecommendationDisplayName has_any("agent","extension","retention","logs encryption","collect","retained","log profile","CloudTrail","metric","AWS","GCP","DNS","Auditing","Flow","logging","usage"), "Event Logging (EL0)",
iff(RecommendationDisplayName has_any("container registries","logic apps","Enhanced monitoring","IoT Hub","Event Hub","App Service", "Kubernetes","updates","email", "automation", "adaptive"), "Advanced Event Logging (EL3)",
iff(RecommendationDisplayName has_any("signatures","CMK","CMEK","double encryption","managed key","KMS","container","Watcher"), "Intermediate Event Logging (EL2)",
iff(RecommendationDisplayName has_any("Exploit Guard","endpoint protection","Antimalware","health","VPC","Defender","Vulnerabilities","vulnerability","diagnostic","Key","activity log alert",""), "Basic Event Logging (EL1)","Other"))))
MaturityLevel =
if(RecommendationDisplayName has "agent" or RecommendationDisplayName has "extension" or RecommendationDisplayName has "retention" or RecommendationDisplayName has "logs encryption" or RecommendationDisplayName has "collect" or RecommendationDisplayName has "retained" or RecommendationDisplayName has "log profile" or RecommendationDisplayName has "CloudTrail" or RecommendationDisplayName has "metric" or RecommendationDisplayName has "AWS" or RecommendationDisplayName has "GCP" or RecommendationDisplayName has "DNS" or RecommendationDisplayName has "Auditing" or RecommendationDisplayName has "Flow" or RecommendationDisplayName has "logging" or RecommendationDisplayName has "usage")"Event Logging (EL0)"
elseiff((RecommendationDisplayName has_any ("container registries", "logic apps", "Enhanced monitoring", "IoT Hub", "Event Hub", "App Service", "Kubernetes", "updates", "email", "automation", "adaptive")), "Advanced Event Logging (EL3)", iff((RecommendationDisplayName has_any ("signatures", "CMK", "CMEK", "double encryption", "managed key", "KMS", "container", "Watcher")), "Intermediate Event Logging (EL2)", iff((RecommendationDisplayName has_any ("Exploit Guard", "endpoint protection", "Antimalware", "health", "VPC", "Defender", "Vulnerabilities", "vulnerability", "diagnostic", "Key", "activity log alert", "")), "Basic Event Logging (EL1)", "Other")))

Stage 4: summarize

| summarize arg_max(TimeGenerated, *) by RecommendationDisplayName, AssessedResourceId, MaturityLevel

Stage 5: summarize

| summarize Failed = countif(RecommendationState == "Unhealthy"), Passed = countif(RecommendationState == "Healthy"), Total = countif(RecommendationState == "Healthy" or RecommendationState == "Unhealthy") by MaturityLevel

Stage 6: extend

| extend PassedControls = (Passed/todouble(Total))*100

Stage 7: extend

| extend RemediationLink = strcat('https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/22')

Stage 8: project

| project MaturityLevel, Total, PassedControls, Passed, Failed, RemediationLink, LastObserved=now()

Stage 9: where

| where MaturityLevel <> ''

Stage 10: where

| where MaturityLevel == "Basic Event Logging (EL1)"

Stage 11: where

| where PassedControls < 70

Stage 12: sort

| sort by PassedControls desc

Stage 13: extend

| extend URLCustomEntity = RemediationLink

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Failedproject
LastObservedproject
MaturityLevelproject
Passedproject
PassedControlsproject
RemediationLinkproject
Totalproject
URLCustomEntityextend

Office Apps Launching Wscipt

#
Status
available
Severity
medium
Time window
1h
Source
github.com/Azure/Azure-Sentinel

This query was originally published in the threat analytics report, Trickbot: Pervasive & underestimated. Trickbot is a very prevalent piece of malware with an array of malicious capabilities. Originally designed to steal banking credentials, it has since evolved into a modular trojan that can deploy other malware, disable security software, and perform command-and-control (C2) operations. Trickbot is frequently spread through email. An attacker will send a target a message with an attachment containing a malicious macro. If the target enables the macro, it will write a JScript Encoded (JSE) file to disk (JScript is a Microsoft dialect of ECMAScript). The JSE file will then be launched using wscript.exe to perform a variety of malicious tasks, particularly reconnaissance. The following query detects when Office applications have launched wscript.exe to run a JSE file. See Detect rundll.exe being used for reconnaissance and command-and-control for another query related to Trickbot activity. Reference - https://attack.mitre.org/software/S0266/

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: 174de33b-107b-4cd8-a85d-b4025a35453f
name: Office Apps Launching Wscipt
description: |
  This query was originally published in the threat analytics report, Trickbot: Pervasive & underestimated.
  Trickbot is a very prevalent piece of malware with an array of malicious capabilities. Originally designed to steal banking credentials, it has since evolved into a modular trojan that can deploy other malware, disable security software, and perform command-and-control (C2) operations.
  Trickbot is frequently spread through email. An attacker will send a target a message with an attachment containing a malicious macro. If the target enables the macro, it will write a JScript Encoded (JSE) file to disk (JScript is a Microsoft dialect of ECMAScript). The JSE file will then be launched using wscript.exe to perform a variety of malicious tasks, particularly reconnaissance.
  The following query detects when Office applications have launched wscript.exe to run a JSE file.
  See Detect rundll.exe being used for reconnaissance and command-and-control for another query related to Trickbot activity.
  Reference - https://attack.mitre.org/software/S0266/
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceProcessEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
  - Collection
  - CommandAndControl
relevantTechniques:
  - T1059
  - T1105
  - T1203
tags:
  - Trickbot
  - JSE
  - Office Applications
query: |
  DeviceProcessEvents 
  | where InitiatingProcessFileName in~ ('winword.exe', 'excel.exe', 'outlook.exe') 
  | where FileName =~ "wscript.exe" and ProcessCommandLine has ".jse"
  | extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
  | extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
  - entityType: Process
    fieldMappings:
      - identifier: ProcessId
        columnName: ProcessId
      - identifier: CommandLine
        columnName: ProcessCommandLine
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

DeviceProcessEvents

Stage 2: where

| where InitiatingProcessFileName in~ ('winword.exe', 'excel.exe', 'outlook.exe')

Stage 3: where

| where FileName =~ "wscript.exe" and ProcessCommandLine has ".jse"

Stage 4: extend

| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
HostName =
ifDeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))
elseDeviceName

Stage 5: extend

| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
DnsDomain =
ifDeviceName has "."substring(DeviceName, (indexof(DeviceName, '.') + 1))
else""

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
FileNameeq
  • wscript.exe corpus 84 (elastic 82, splunk 1, kusto 1)
field:"process_basename" kind:eq value:"wscript.exe"
InitiatingProcessFileNamein
  • excel.exe corpus 7 (splunk 5, kusto 2)
  • outlook.exe corpus 6 (splunk 5, kusto 1)
  • winword.exe corpus 7 (splunk 5, kusto 2)
field:"parent_process_basename" kind:in
ProcessCommandLinematch
  • .jse transforms: term
field:"CommandLine" kind:match value:".jse"

Output Fields

These fields are emitted when the rule matches.

FieldSource
HostNameextend
DnsDomainextend

Regsvr32 Rundll32 Image Loads Abnormal Extension

#
Status
available
Severity
high
Time window
1h
Group by
InitiatingProcessCommandLine, InitiatingProcessCreationTime, InitiatingProcessFileName, InitiatingProcessId
Source
github.com/Azure/Azure-Sentinel

This query is looking for regsvr32.exe or rundll32.exe loading DLL images with other extensions than .dll. Joins the data to public network events. References: https://threathunt.blog/running-live-malware-for-threat-hunting-purposes/

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: 36fbd4e7-5630-4414-aa42-702a7fdded21
name: Regsvr32 Rundll32 Image Loads Abnormal Extension
description: |
  This query is looking for regsvr32.exe or rundll32.exe loading DLL images with other extensions than .dll.
  Joins the data to public network events.
  References:
  https://threathunt.blog/running-live-malware-for-threat-hunting-purposes/
severity: High
status: Available
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceProcessEvents
      - DeviceNetworkEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1218.010
  - T1218.011
tags:
  - Defense Evasion
  - Image Load
  - Regsvr32 Abuse
  - Rundll32 Abuse
query: |
  DeviceImageLoadEvents 
  | where InitiatingProcessFileName has_any ("rundll32.exe","regsvr32.exe")
  | where FileName !endswith ".dll"
  | join (
  DeviceNetworkEvents
  | where InitiatingProcessFileName has_any ("rundll32.exe","regsvr32.exe")
  | where RemoteIPType == "Public"
  ) on InitiatingProcessFileName, InitiatingProcessId, InitiatingProcessCreationTime, InitiatingProcessCommandLine
  | project TimeGenerated, DeviceName, FileName, FolderPath, SHA1, InitiatingProcessFileName, InitiatingProcessCommandLine, LocalIP, LocalPort, RemoteIP, RemoteUrl, RemotePort, InitiatingProcessParentFileName
  | extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
  | extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: LocalIP
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: RemoteIP
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: RemoteUrl
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

DeviceImageLoadEvents

Stage 2: where

| where InitiatingProcessFileName has_any ("rundll32.exe","regsvr32.exe")

Stage 3: where

| where FileName !endswith ".dll"

Stage 4: join

| join (
DeviceNetworkEvents
| where InitiatingProcessFileName has_any ("rundll32.exe","regsvr32.exe")
| where RemoteIPType == "Public"
) on InitiatingProcessFileName, InitiatingProcessId, InitiatingProcessCreationTime, InitiatingProcessCommandLine

Stage 5: project

project DeviceName, FileName, FolderPath, InitiatingProcessCommandLine, InitiatingProcessFileName, InitiatingProcessParentFileName, LocalIP, LocalPort, RemoteIP, RemotePort, RemoteUrl, SHA1, TimeGenerated

Stage 6: extend

extend HostName
HostName =
ifDeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))
elseDeviceName

Stage 7: extend

extend DnsDomain
DnsDomain =
ifDeviceName has "."substring(DeviceName, (indexof(DeviceName, '.') + 1))
else""

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
InitiatingProcessFileNamematch
  • regsvr32.exe transforms: term corpus 2 (kusto 2)
  • regsvr32.exe transforms: term
  • rundll32.exe transforms: term corpus 2 (kusto 2)
  • rundll32.exe transforms: term
field:"process_basename" kind:match
RemoteIPTypeeq
  • Public
field:"RemoteIPType" kind:eq value:"Public"

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
FileNameends_with.dllexcludes:FileName field:"FileName" value:".dll"

Output Fields

These fields are emitted when the rule matches.

FieldSource
DeviceNameproject
FileNameproject
FolderPathproject
InitiatingProcessCommandLineproject
InitiatingProcessFileNameproject
InitiatingProcessParentFileNameproject
LocalIPproject
LocalPortproject
RemoteIPproject
RemotePortproject
RemoteUrlproject
SHA1project
TimeGeneratedproject
HostNameextend
DnsDomainextend

Service Principal Name (SPN) Assigned to User Account

#
Severity
medium
Time window
1h
Group by
AttributeValue, Computer, ObjectDN, SubjectAccount, SubjectDomainName, SubjectUserName, SubjectUserSid
Author
Vasileios Paschalidis
Source
github.com/Azure/Azure-Sentinel

This query identifies whether an Active Directory user object was assigned a service principal name which could indicate that an adversary is preparing for performing Kerberoasting. This query checks for event id 5136, that the Object Class field is "user" and the LDAP Display Name is "servicePrincipalName". Ref: https://thevivi.net/assets/docs/2019/theVIVI-AD-Security-Workshop_AfricaHackon2019.pdf

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation

Telemetry Coverage

Rule Body

id: 875d0eb1-883a-4191-bd0e-dbfdeb95a464
name: Service Principal Name (SPN) Assigned to User Account
description: |
  'This query identifies whether an Active Directory user object was assigned a service principal name which could indicate that an adversary is preparing for performing Kerberoasting. 
  This query checks for event id 5136, that the Object Class field is "user" and the LDAP Display Name is "servicePrincipalName".
  Ref: https://thevivi.net/assets/docs/2019/theVIVI-AD-Security-Workshop_AfricaHackon2019.pdf'
severity: Medium
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1134
query: |
    SecurityEvent
    | where EventID == 5136 
    | parse EventData with * 'AttributeLDAPDisplayName">' AttributeLDAPDisplayName "<" *
    | parse EventData with * 'ObjectClass">' ObjectClass "<" *
    | where AttributeLDAPDisplayName == "servicePrincipalName" and  ObjectClass == "user"
    | parse EventData with * 'ObjectDN">' ObjectDN "<" *
    | parse EventData with * 'AttributeValue">' AttributeValue "<" *
    | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by Computer, SubjectAccount, ObjectDN, AttributeValue, SubjectUserName, SubjectDomainName, 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: 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: NTDomain
        columnName: HostNameDomain
version: 1.0.4
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Vasileios Paschalidis
    support:
        tier: Community
    categories:
        domains: [ "Security - Others", "Identity" ]

Stages and Predicates

Stage 1: source

SecurityEvent

Stage 2: where

| where EventID == 5136

Stage 3: parse

| parse EventData with * 'AttributeLDAPDisplayName">' AttributeLDAPDisplayName "<" *

Stage 4: parse

| parse EventData with * 'ObjectClass">' ObjectClass "<" *

Stage 5: where

| where AttributeLDAPDisplayName == "servicePrincipalName" and  ObjectClass == "user"

Stage 6: parse

| parse EventData with * 'ObjectDN">' ObjectDN "<" *

Stage 7: parse

| parse EventData with * 'AttributeValue">' AttributeValue "<" *

Stage 8: summarize

| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by Computer, SubjectAccount, ObjectDN, AttributeValue, SubjectUserName, SubjectDomainName, SubjectUserSid

Stage 9: extend

| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))

Stage 10: extend

| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
HostNameDomain =
ifDomainIndex != -1substring(Computer, (DomainIndex + 1))
elseComputer

Stage 11: project-away

| project-away DomainIndex

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AttributeValuesummarize
Computersummarize
EndTimesummarize
ObjectDNsummarize
StartTimesummarize
SubjectAccountsummarize
SubjectDomainNamesummarize
SubjectUserNamesummarize
SubjectUserSidsummarize
HostNameextend
HostNameDomainextend

TacitRed - Repeat Compromise Detection

#

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.

Severity
high
Time window
7d
Group by
domain_s, email_s
Source
github.com/Azure/Azure-Sentinel

Detects users who have been compromised multiple times within a 7-day window. This may indicate a persistent threat or inadequate remediation. Ref: https://data443.com/tacitred-attack-surface-intelligence/

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Rule Body

id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
name: TacitRed - Repeat Compromise Detection
version: 1.0.1
kind: Scheduled
description: |-
  Detects users who have been compromised multiple times within a 7-day window.
  This may indicate a persistent threat or inadequate remediation.
  
  Ref: https://data443.com/tacitred-attack-surface-intelligence/
severity: High
requiredDataConnectors:
  - connectorId: TacitRedThreatIntel
    dataTypes:
      - TacitRed_Findings_CL
queryFrequency: 1h
queryPeriod: 7d
triggerOperator: GreaterThan
triggerThreshold: 0
tactics:
  - CredentialAccess
  - Persistence
relevantTechniques:
  - T1078
query: |-
  let lookback = 7d;
  TacitRed_Findings_CL
  | where TimeGenerated >= ago(lookback)
  | summarize
      CompromiseCount = count(),
      FirstSeen = min(TimeGenerated),
      LastSeen = max(TimeGenerated),
      Sources = make_set(source_s),
      AvgConfidence = avg(confidence_d)
    by email_s, domain_s
  | where CompromiseCount > 1
  | extend
      Email = tostring(email_s),
      Domain = tostring(domain_s)
  | project
      Email,
      Domain,
      CompromiseCount,
      FirstSeen,
      LastSeen,
      Sources,
      AvgConfidence
suppressionEnabled: false
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: Selected
    groupByEntities:
      - Account
suppressionDuration: 5h
eventGroupingSettings:
  aggregationKind: AlertPerResult
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Email
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: Domain

Stages and Predicates

Parameters

let lookback = 7d;

Stage 1: source

TacitRed_Findings_CL

Stage 2: where

| where TimeGenerated >= ago(lookback)

Stage 3: summarize

| summarize
    CompromiseCount = count(),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated),
    Sources = make_set(source_s),
    AvgConfidence = avg(confidence_d)
  by email_s, domain_s

Stage 4: where

| where CompromiseCount > 1

Stage 5: extend

| extend
    Email = tostring(email_s),
    Domain = tostring(domain_s)

Stage 6: project

| project
    Email,
    Domain,
    CompromiseCount,
    FirstSeen,
    LastSeen,
    Sources,
    AvgConfidence

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
CompromiseCountgt
  • 1 transforms: number
field:"CompromiseCount" kind:gt value:"1"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AvgConfidenceproject
CompromiseCountproject
Domainproject
Emailproject
FirstSeenproject
LastSeenproject
Sourcesproject

Theom - Dev secrets unencrypted

#

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.

Status
available
Severity
high
Time window
5m
Source
github.com/Azure/Azure-Sentinel

"Creates Sentinel incidents for critical/high Theom risks, associated with ruleId TRIS0001 (Developer secrets have been observed in unencrypted data stores. Encrypt data at rest to comply with this CIS requirement)"

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Rule Body

id: f2490f5b-269c-471d-9ff4-475f62ea498e
name: Theom - Dev secrets unencrypted
description: |
   "Creates Sentinel incidents for critical/high Theom risks, associated with ruleId TRIS0001 (Developer secrets have been observed in unencrypted data stores. Encrypt data at rest to comply with this CIS requirement)"
severity: High
status : Available
requiredDataConnectors:
  - connectorId: Theom
    dataTypes:
      - TheomAlerts_CL
queryPeriod: 5m
queryFrequency: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1552
query: |
   TheomAlerts_CL
     | where customProps_RuleId_s == "TRIS0001" and (priority_s == "P1" or priority_s == "P2")
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: "Theom Alert ID: {{id_s}} "
  alertDescriptionFormat: "\nSummary: {{summary_s}}  \nAdditional info: {{details_s}}\nPlease investigate further on Theom UI at {{deepLink_s}}\n"
entityMappings: 
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: customProps_AssetName_s
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: deepLink_s
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

TheomAlerts_CL

Stage 2: where

| where customProps_RuleId_s == "TRIS0001" and (priority_s == "P1" or priority_s == "P2")

Indicators

These rows show field, operator, and value matches.

TI map IP entity to AppServiceHTTPLogs

#
Severity
medium
Time window
14d
Group by
$left.TI_ipEntity, $right.CIp, CIp, IndicatorId
Source
github.com/Azure/Azure-Sentinel

Identifies a match in AppServiceHTTPLogs from any IP IOC from TI

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: f9949656-473f-4503-bf43-a9d9890f7d08
name: TI map IP entity to AppServiceHTTPLogs
description: |
  Identifies a match in AppServiceHTTPLogs from any IP IOC from TI
severity: Medium
requiredDataConnectors:
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1h; // Look back 1 hour for AppServiceHTTPLogs
  let ioc_lookBack = 14d; // Look back 14 days for threat intelligence indicators
  // Fetch threat intelligence indicators related to IP addresses
  let IP_Indicators = ThreatIntelligenceIndicator
    // Filter out indicators without relevant IP address fields
    | where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)
    | where TimeGenerated >= ago(ioc_lookBack)
    // Filtering out rows where the Confidence Score is less than 50 as they would not have an Alert Priority label. 
    | where ConfidenceScore > 50
    // Select the IP entity based on availability of different IP fields
    | extend TI_ipEntity = iff(isnotempty(NetworkIP), NetworkIP, NetworkDestinationIP)
    | extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
    | extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(EmailSourceIpAddress), EmailSourceIpAddress, TI_ipEntity)
    // Determine AlertPriority based on ConfidenceScore
    | extend AlertPriority = case(ConfidenceScore > 82, "High",
                                  ConfidenceScore > 74, "Medium",
                                  "Low")
    // Exclude local addresses using the ipv4_is_private operator and filtering out specific address prefixes
    | 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 IndicatorId
    | where Active == true and ExpirationDateTime > now();
  // Perform a join between IP indicators and AppServiceHTTPLogs to identify potential malicious activity
  IP_Indicators
    // 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 (
      AppServiceHTTPLogs | where TimeGenerated >= ago(dt_lookBack)
      | where isnotempty(CIp)
      | extend WebApp = split(_ResourceId, '/')[8]
      | extend AppService_TimeGenerated = TimeGenerated // Rename time column for clarity
    )
    on $left.TI_ipEntity == $right.CIp
    // Filter out logs that occurred after the expiration of the corresponding indicator
    | where AppService_TimeGenerated < ExpirationDateTime
    // Group the results by IndicatorId and CIp, and keep the log entry with the latest timestamp
    | summarize AppService_TimeGenerated = arg_max(AppService_TimeGenerated, *) by IndicatorId, CIp
    // Select the desired output fields
    | project AppService_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore, TI_ipEntity, CsUsername, WebApp = split(_ResourceId, '/')[8], CIp, CsHost, NetworkIP, NetworkDestinationIP, NetworkSourceIP, EmailSourceIpAddress, _ResourceId, Type, AlertPriority
    // Extract hostname and DNS domain from the CsHost field
    | extend HostName = tostring(split(CsHost, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(CsHost, '.'), 1, -1), '.'))
    // Rename the timestamp field
    | extend timestamp = AppService_TimeGenerated
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain   
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: CsUsername
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: CIp
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
  - entityType: AzureResource
    fieldMappings:
      - identifier: ResourceId
        columnName: _ResourceId
alertDetailsOverride:
  alertSeverityColumnName: AlertPriority
version: 1.5.3
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 8 define let IP_Indicators as the rule's main pipeline source. Stages 9 to 14 use it.

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)

Stage 3: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 4: where

| where ConfidenceScore > 50

Stage 5: extend (4 consecutive steps)

| extend TI_ipEntity = iff(isnotempty(NetworkIP), NetworkIP, NetworkDestinationIP)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(EmailSourceIpAddress), EmailSourceIpAddress, TI_ipEntity)
| extend AlertPriority = case(ConfidenceScore > 82, "High",
                                ConfidenceScore > 74, "Medium",
                                "Low")
TI_ipEntity =
ifisnotempty(NetworkIP)NetworkIP
elseNetworkDestinationIP

Stage 6: where

| where ipv4_is_private(TI_ipEntity) == false and  TI_ipEntity !startswith "fe80" and TI_ipEntity !startswith "::" and TI_ipEntity !startswith "127."

Stage 7: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId

Stage 8: where

| where Active == true and ExpirationDateTime > now()

Stage 9: join

IP_Indicators
| join kind=innerunique (
    AppServiceHTTPLogs | where TimeGenerated >= ago(dt_lookBack)
    | where isnotempty(CIp)
    | extend WebApp = split(_ResourceId, '/')[8]
    | extend AppService_TimeGenerated = TimeGenerated
  )
  on $left.TI_ipEntity == $right.CIp

Stage 10: where

where AppService_TimeGenerated < ExpirationDateTime

Stage 11: summarize

summarize AppService_TimeGenerated by IndicatorId, CIp

Stage 12: project

project ActivityGroupNames, AlertPriority, AppService_TimeGenerated, CIp, ConfidenceScore, CsHost, CsUsername, Description, EmailSourceIpAddress, ExpirationDateTime, IndicatorId, NetworkDestinationIP, NetworkIP, NetworkSourceIP, TI_ipEntity, ThreatType, Type, Url, WebApp, _ResourceId

Stage 13: extend

extend DnsDomain, HostName

Stage 14: extend

extend timestamp

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
TI_ipEntitycidr_match10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8excludes:TI_ipEntity
TI_ipEntitystarts_with127.excludes:TI_ipEntity field:"TI_ipEntity" value:"127."
TI_ipEntitystarts_with::excludes:TI_ipEntity field:"TI_ipEntity" value:"::"
TI_ipEntitystarts_withfe80excludes:TI_ipEntity field:"TI_ipEntity" value:"fe80"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
AlertPriorityproject
AppService_TimeGeneratedproject
CIpproject
ConfidenceScoreproject
CsHostproject
CsUsernameproject
Descriptionproject
EmailSourceIpAddressproject
ExpirationDateTimeproject
IndicatorIdproject
NetworkDestinationIPproject
NetworkIPproject
NetworkSourceIPproject
TI_ipEntityproject
ThreatTypeproject
Typeproject
Urlproject
WebAppproject
_ResourceIdproject
DnsDomainextend
HostNameextend
timestampextend

Valimail Enforce - Unusual Rate of Configuration Changes or User Additions

#
Status
available
Severity
medium
Time window
1h
Group by
User
Source
github.com/Azure/Azure-Sentinel

This query searches for a single user performing more than 3 configuration changes or user additions within a 1-hour window on any domain. An unusual burst of changes may indicate a compromised admin account, unauthorized automation, or insider threat.

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation
Stealth
Defense Impairment
Impact

Rule Body

id: 3cbb78d9-81ac-42c9-b3cd-7e6baea7d9ff
name: Valimail Enforce - Unusual Rate of Configuration Changes or User Additions
description: |
  This query searches for a single user performing more than 3 configuration changes or user
  additions within a 1-hour window on any domain. An unusual burst of changes may
  indicate a compromised admin account, unauthorized automation, or insider threat.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: ValimailEnforce
    dataTypes:
      - ValimailEnforceEvents_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
  - DefenseEvasion
  - PrivilegeEscalation
relevantTechniques:
  - T1562
  - T1531
  - T1078
query: |
  let threshold = 3;
  let timeWindow = 1h;
  ValimailEnforceEvents_CL
  | where EventCategory in (
      "DMARCPolicy",
      "SPFConfiguration",
      "DKIMConfiguration",
      "DomainManagement",
      "UserManagement"
    )
  | summarize
      ChangeCount       = count(),
      HighValueCount    = countif(IsHighValueEvent == true),
      Domains           = make_set(Subject),
      DomainCount       = dcount(Subject),
      Actions           = make_set(EventType),
      Categories        = make_set(EventCategory),
      FirstSeen         = min(PerformedAt),
      LastSeen          = max(PerformedAt)
    by User, bin(PerformedAt, timeWindow)
  | where ChangeCount > threshold
  | extend
      AccountName   = tostring(split(User, "@")[0]),
      AccountDomain = tostring(split(User, "@")[1]),
      DomainName    = tostring(Domains[0]),
      ChangesPerMin = round(todouble(ChangeCount) / 60.0, 2)
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountDomain
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: DomainName
alertDetailsOverride:
  alertDisplayNameFormat: "Unusual change rate by {{User}}, {{ChangeCount}} changes in 1h across {{DomainCount}} domain(s)"
  alertDescriptionFormat: |
    User '{{User}}' made {{ChangeCount}} configuration changes across {{DomainCount}} domain(s) within one hour.
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 1d
    matchingMethod: Selected
    groupByEntities:
      - Account
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 3;
let timeWindow = 1h;

Stage 1: source

ValimailEnforceEvents_CL

Stage 2: where

| where EventCategory in (
    "DMARCPolicy",
    "SPFConfiguration",
    "DKIMConfiguration",
    "DomainManagement",
    "UserManagement"
  )

Stage 3: summarize

| summarize
    ChangeCount       = count(),
    HighValueCount    = countif(IsHighValueEvent == true),
    Domains           = make_set(Subject),
    DomainCount       = dcount(Subject),
    Actions           = make_set(EventType),
    Categories        = make_set(EventCategory),
    FirstSeen         = min(PerformedAt),
    LastSeen          = max(PerformedAt)
  by User, bin(PerformedAt, timeWindow)

Stage 4: where

| where ChangeCount > threshold

Stage 5: extend

| extend
    AccountName   = tostring(split(User, "@")[0]),
    AccountDomain = tostring(split(User, "@")[1]),
    DomainName    = tostring(Domains[0]),
    ChangesPerMin = round(todouble(ChangeCount) / 60.0, 2)

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ChangeCountgt
  • 3 transforms: number
field:"ChangeCount" kind:gt value:"3"
EventCategoryin
  • DKIMConfiguration
  • DMARCPolicy
  • DomainManagement
  • SPFConfiguration
  • UserManagement
field:"EventCategory" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
Actionssummarize
Categoriessummarize
ChangeCountsummarize
DomainCountsummarize
Domainssummarize
FirstSeensummarize
HighValueCountsummarize
LastSeensummarize
Usersummarize
AccountDomainextend
AccountNameextend
ChangesPerMinextend
DomainNameextend

Versasec CMS - Multiple Failed Login Attempts

#
Status
available
Severity
high
Time window
1h
Source
github.com/Azure/Azure-Sentinel

Detects when Operator login failed to often.

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Rule Body

id: B1DB8B7E-9D74-48C3-9683-74483CBEFF4E
name: Versasec CMS - Multiple Failed Login Attempts
description: |
  Detects when Operator login failed to often.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: VersasecCms
    dataTypes:
      - VersasecCmsSysLogs
queryFrequency: 5m
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 5
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110 # Brute Force
query: |
  let threshold = 5;
  VersasecCmsSysLogs 
  | where EventId == 2 
  | sort by ComputerName asc, TimeGenerated asc
  | extend TimeDiff = datetime_diff('minute', TimeGenerated, prev(TimeGenerated))
  | where TimeDiff <= threshold and ComputerName == prev(ComputerName)
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: ComputerName
eventGroupingSettings:
  aggregationKind: SingleAlert
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: 5m
    matchingMethod: AllEntities
version: 1.0.1
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 5;

Stage 1: source

VersasecCmsSysLogs

Stage 2: where

| where EventId == 2

Stage 3: sort

| sort by ComputerName asc, TimeGenerated asc

Stage 4: extend

| extend TimeDiff = datetime_diff('minute', TimeGenerated, prev(TimeGenerated))

Stage 5: where

| where TimeDiff <= threshold and ComputerName == prev(ComputerName)

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
TimeDiffextend

VMware ESXi - VM stopped

#
Status
available
Severity
medium
Time window
1h
Source
github.com/Azure/Azure-Sentinel

'Detects when VM was stopped.'

MITRE ATT&CK Coverage

TacticTechniques
Impact

Rule Body

id: 43889f30-7bce-4d8a-93bb-29c9615ca8dd
name: VMware ESXi - VM stopped
description: |
  'Detects when VM was stopped.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1529
query: |
  VMwareESXi
  | where SyslogMessage has ('VmPoweredOffEvent')
  | extend SrcUsername = extract(@'\[info\]\s+\[(.*?)\]', 1, SyslogMessage)
  | extend DstHostname = extract(@'\[\d+\]\s+\[(.*?)\s+on', 1, SyslogMessage)
  | extend HostCustomEntity = DstHostname, AccountCustomEntity = SrcUsername
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: HostCustomEntity
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

VMwareESXi

Stage 2: where

| where SyslogMessage has ('VmPoweredOffEvent')

Stage 3: extend (3 consecutive steps)

| extend SrcUsername = extract(@'\[info\]\s+\[(.*?)\]', 1, SyslogMessage)
| extend DstHostname = extract(@'\[\d+\]\s+\[(.*?)\s+on', 1, SyslogMessage)
| extend HostCustomEntity = DstHostname, AccountCustomEntity = SrcUsername

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
SyslogMessagematch
  • VmPoweredOffEvent transforms: term
field:"SyslogMessage" kind:match value:"VmPoweredOffEvent"

Output Fields

These fields are emitted when the rule matches.

FieldSource
SrcUsernameextend
DstHostnameextend
AccountCustomEntityextend
HostCustomEntityextend

Back to all Kusto rules