Detection rules › Kusto
GCP Audit Logs - DNSSEC Disabled on Managed DNS Zone
Detects when DNSSEC (DNS Security Extensions) is disabled on a Google Cloud DNS managed zone. DNSSEC provides cryptographic authentication of DNS data, preventing DNS spoofing and cache poisoning attacks. Adversaries may disable DNSSEC to enable DNS-based command and control, phishing campaigns, or to redirect traffic to malicious infrastructure without cryptographic validation. This rule monitors DNS zone patch operations where DNSSEC state changes from ON to OFF.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Resource Development | |
| Defense Impairment | |
| Command & Control |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| GCP | dns.managedZones.patch: Patch managed zone |
| GCP | dns.managedZones.update: Update managed zone |
Rule body
id: 9129a43e-e204-4a9a-969e-d8861ce3437c
name: GCP Audit Logs - DNSSEC Disabled on Managed DNS Zone
description: |
'Detects when DNSSEC (DNS Security Extensions) is disabled on a Google Cloud DNS managed zone.
DNSSEC provides cryptographic authentication of DNS data, preventing DNS spoofing and cache poisoning attacks.
Adversaries may disable DNSSEC to enable DNS-based command and control, phishing campaigns, or
to redirect traffic to malicious infrastructure without cryptographic validation.
This rule monitors DNS zone patch operations where DNSSEC state changes from ON to OFF.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: GCPAuditLogsDefinition
dataTypes:
- GCPAuditLogs
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- CommandAndControl
- ResourceDevelopment
relevantTechniques:
- T1562.001
- T1071.004
- T1584.002
tags:
- GCP
- DNS
- DNSSEC
- Cloud Security
query: |
GCPAuditLogs
| where ServiceName == "dns.googleapis.com"
| where MethodName in ("dns.managedZones.update", "dns.managedZones.patch")
| where GCPResourceType == "dns_managed_zone" and Severity == "NOTICE"
| extend
ResponseJson = parse_json(Response),
RequestMetadataJson = parse_json(RequestMetadata),
AuthInfoJson = parse_json(AuthenticationInfo)
| extend ZoneContext = ResponseJson.operation.zoneContext
| where isnotempty(ZoneContext)
| extend
OldDnsSecState = tostring(ZoneContext.oldValue.dnssecConfig.state),
NewDnsSecState = tostring(ZoneContext.newValue.dnssecConfig.state)
| where OldDnsSecState == "ON" and NewDnsSecState == "OFF"
| extend
ManagedZoneName = extract(@"managedZones/([^/]+)", 1, GCPResourceName),
DnsName = tostring(ResponseJson.managedZone.dnsName),
ZoneId = tostring(ResponseJson.managedZone.id),
ZoneDescription = tostring(ResponseJson.managedZone.description),
Visibility = tostring(ResponseJson.managedZone.visibility),
OperationId = tostring(ResponseJson.operation.id),
CallerIpAddress = tostring(RequestMetadataJson.callerIp),
AuthEmail = tostring(AuthInfoJson.principalEmail)
| extend
AccountName = tostring(split(PrincipalEmail, "@")[0]),
AccountUPNSuffix = tostring(split(PrincipalEmail, "@")[1])
| project TimeGenerated,
PrincipalEmail,
AuthEmail,
ProjectId,
ManagedZoneName,
DnsName,
ResourceName = GCPResourceName,
Visibility,
ZoneId,
ZoneDescription,
OperationId,
CallerIpAddress,
MethodName,
ServiceName,
Severity,
LogName,
InsertId,
AccountName,
AccountUPNSuffix
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: PrincipalEmail
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: CallerIpAddress
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: ProjectId
- identifier: InstanceName
columnName: ResourceName
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: DnsName
customDetails:
ProjectId: ProjectId
ManagedZoneName: ManagedZoneName
DnsName: DnsName
ResourceName: ResourceName
Visibility: Visibility
ZoneId: ZoneId
alertDetailsOverride:
alertDisplayNameFormat: "DNSSEC Disabled on DNS Zone {{ManagedZoneName}} ({{DnsName}}) by {{PrincipalEmail}}"
alertDescriptionFormat: |-
User {{PrincipalEmail}} disabled DNSSEC on DNS managed zone {{ManagedZoneName}} ({{DnsName}}).
This action removes cryptographic validation of DNS responses and may indicate an attempt to facilitate DNS-based attacks.
Investigate immediately to determine if this change was authorized and assess potential security impact.
Review DNS query logs for suspicious activity and consider re-enabling DNSSEC if unauthorized.
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
GCPAuditLogs
Stage 2: where
| where ServiceName == "dns.googleapis.com"
Stage 3: where
| where MethodName in ("dns.managedZones.update", "dns.managedZones.patch")
Stage 4: where
| where GCPResourceType == "dns_managed_zone" and Severity == "NOTICE"
Stage 5: extend
| extend
ResponseJson = parse_json(Response),
RequestMetadataJson = parse_json(RequestMetadata),
AuthInfoJson = parse_json(AuthenticationInfo)
Stage 6: extend
| extend ZoneContext = ResponseJson.operation.zoneContext
Stage 7: where
| where isnotempty(ZoneContext)
Stage 8: extend
| extend
OldDnsSecState = tostring(ZoneContext.oldValue.dnssecConfig.state),
NewDnsSecState = tostring(ZoneContext.newValue.dnssecConfig.state)
Stage 9: where
| where OldDnsSecState == "ON" and NewDnsSecState == "OFF"
Stage 10: extend
| extend
ManagedZoneName = extract(@"managedZones/([^/]+)", 1, GCPResourceName),
DnsName = tostring(ResponseJson.managedZone.dnsName),
ZoneId = tostring(ResponseJson.managedZone.id),
ZoneDescription = tostring(ResponseJson.managedZone.description),
Visibility = tostring(ResponseJson.managedZone.visibility),
OperationId = tostring(ResponseJson.operation.id),
CallerIpAddress = tostring(RequestMetadataJson.callerIp),
AuthEmail = tostring(AuthInfoJson.principalEmail)
Stage 11: extend
| extend
AccountName = tostring(split(PrincipalEmail, "@")[0]),
AccountUPNSuffix = tostring(split(PrincipalEmail, "@")[1])
Stage 12: project
| project TimeGenerated,
PrincipalEmail,
AuthEmail,
ProjectId,
ManagedZoneName,
DnsName,
ResourceName = GCPResourceName,
Visibility,
ZoneId,
ZoneDescription,
OperationId,
CallerIpAddress,
MethodName,
ServiceName,
Severity,
LogName,
InsertId,
AccountName,
AccountUPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
GCPResourceType | eq |
| field:"gcp::resource_type" kind:eq value:"dns_managed_zone" |
MethodName | in |
| field:"gcp::method_name" kind:in |
NewDnsSecState | eq |
| field:"NewDnsSecState" kind:eq value:"OFF" |
OldDnsSecState | eq |
| field:"OldDnsSecState" kind:eq value:"ON" |
ServiceName | eq |
| field:"ServiceName" kind:eq value:"dns.googleapis.com" |
Severity | eq |
| field:"Severity" kind:eq value:"NOTICE" |
ZoneContext | is_not_null | field:"ZoneContext" kind:is_not_null |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
AccountUPNSuffix | project |
AuthEmail | project |
CallerIpAddress | project |
DnsName | project |
InsertId | project |
LogName | project |
ManagedZoneName | project |
MethodName | project |
OperationId | project |
PrincipalEmail | project |
ProjectId | project |
ResourceName | project |
ServiceName | project |
Severity | project |
TimeGenerated | project |
Visibility | project |
ZoneDescription | project |
ZoneId | project |