Detection rules › Kusto
GCP Security Command Center - Detect Firewall rules allowing unrestricted high-risk ports
This query detects GCP Firewall rules that allow unrestricted (0.0.0.0/0) ingress to high-risk ports using Google Cloud Security Command Center OPEN_FIREWALL 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 | T1133 External Remote Services |
| Discovery | T1046 Network Service Discovery |
| Lateral Movement | T1021 Remote Services |
Rule body kusto
id: f4f92ca4-6ebe-4f2a-90e5-b0d04b709651
name: GCP Security Command Center - Detect Firewall rules allowing unrestricted high-risk ports
description: |
This query detects GCP Firewall rules that allow unrestricted (0.0.0.0/0) ingress to high-risk ports using Google Cloud Security Command Center OPEN_FIREWALL 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: GoogleSCCDefinition
dataTypes:
- GoogleCloudSCC
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- LateralMovement
- Discovery
relevantTechniques:
- T1133
- T1021
- T1046
tags:
- PCI-DSS v3.2.1 1.2.1
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]);
GoogleCloudSCC
| where tostring(Findings.state) == "ACTIVE"
| extend FindingCategory = tostring(Findings.category)
| where FindingCategory == "OPEN_FIREWALL"
| extend FindingsJson = parse_json(Findings)
| extend SourcePropertiesJson = parse_json(tostring(FindingsJson.sourceProperties))
| extend IpRulesJson = parse_json(tostring(FindingsJson.ipRules))
| where tostring(FindingsJson.state) == "ACTIVE"
| where tostring(SourcePropertiesJson.ExternalSourceRanges) contains "0.0.0.0/0"
| extend AllowedIpRules = parse_json(tostring(IpRulesJson.allowed.ipRules))
| mv-expand IpRule = AllowedIpRules
| extend PortRanges = parse_json(tostring(IpRule.portRanges))
| mv-expand PortRange = PortRanges
| extend MinPort = toint(PortRange.min), MaxPort = toint(PortRange.max)
| where MinPort in (HighRiskPorts) or MaxPort in (HighRiskPorts)
| extend
ResourceName = tostring(FindingsJson.resourceName),
FindingName = tostring(FindingsJson.name),
Protocol = tostring(IpRule.protocol),
Severity = tostring(FindingsJson.severity),
Description = tostring(FindingsJson.description),
ExternalUri = tostring(FindingsJson.externalUri),
AttackExposureScore = todouble(FindingsJson.attackExposure.score),
ProjectName = extract(@"projects/([^/]+)", 1, tostring(FindingsJson.resourceName)),
FirewallName = extract(@"firewalls/([^/]+)", 1, tostring(FindingsJson.resourceName))
| extend PortInfo = case(
MinPort == MaxPort, tostring(MinPort),
strcat(tostring(MinPort), "-", tostring(MaxPort))
)
| summarize
TimeGenerated = max(TimeGenerated),
OpenHighRiskPorts = make_set(PortInfo),
Protocols = make_set(Protocol),
AttackExposureScore = max(AttackExposureScore)
by ProjectName, FirewallName, ResourceName, FindingName, Severity, Description, ExternalUri
| extend OpenHighRiskPorts = strcat_array(OpenHighRiskPorts, ", ")
| extend Protocols = strcat_array(Protocols, ", ")
entityMappings:
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: ResourceName
customDetails:
ProjectName: ProjectName
FirewallName: FirewallName
FindingId: FindingName
OpenHighRiskPorts: OpenHighRiskPorts
Protocols: Protocols
AttackExposureScore: AttackExposureScore
ExternalUri: ExternalUri
alertDetailsOverride:
alertDisplayNameFormat: "GCP Firewall {{FirewallName}} allows unrestricted high-risk ports"
alertDescriptionFormat: |-
GCP Firewall {{FirewallName}} in project {{ProjectName}} allows unrestricted (0.0.0.0/0) ingress to high-risk ports: {{OpenHighRiskPorts}}.
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
GoogleCloudSCC
Stage 2: where
| where tostring(Findings.state) == "ACTIVE"
Stage 3: extend
| extend FindingCategory = tostring(Findings.category)
Stage 4: where
| where FindingCategory == "OPEN_FIREWALL"
Stage 5: extend (3 consecutive steps)
| extend FindingsJson = parse_json(Findings)
| extend SourcePropertiesJson = parse_json(tostring(FindingsJson.sourceProperties))
| extend IpRulesJson = parse_json(tostring(FindingsJson.ipRules))
Stage 6: where
| where tostring(FindingsJson.state) == "ACTIVE"
Stage 7: where
| where tostring(SourcePropertiesJson.ExternalSourceRanges) contains "0.0.0.0/0"
Stage 8: extend
| extend AllowedIpRules = parse_json(tostring(IpRulesJson.allowed.ipRules))
Stage 9: mv-expand
| mv-expand IpRule = AllowedIpRules
Stage 10: extend
| extend PortRanges = parse_json(tostring(IpRule.portRanges))
Stage 11: mv-expand
| mv-expand PortRange = PortRanges
Stage 12: extend
| extend MinPort = toint(PortRange.min), MaxPort = toint(PortRange.max)
Stage 13: where
| where MinPort in (HighRiskPorts) or MaxPort in (HighRiskPorts)
Stage 14: extend
| extend
ResourceName = tostring(FindingsJson.resourceName),
FindingName = tostring(FindingsJson.name),
Protocol = tostring(IpRule.protocol),
Severity = tostring(FindingsJson.severity),
Description = tostring(FindingsJson.description),
ExternalUri = tostring(FindingsJson.externalUri),
AttackExposureScore = todouble(FindingsJson.attackExposure.score),
ProjectName = extract(@"projects/([^/]+)", 1, tostring(FindingsJson.resourceName)),
FirewallName = extract(@"firewalls/([^/]+)", 1, tostring(FindingsJson.resourceName))
Stage 15: extend
| extend PortInfo = case(
MinPort == MaxPort, tostring(MinPort),
strcat(tostring(MinPort), "-", tostring(MaxPort))
)
PortInfo =MinPort == "MaxPort"tostring(MinPort)strcat(tostring(MinPort), "-", tostring(MaxPort))Stage 16: summarize
| summarize
TimeGenerated = max(TimeGenerated),
OpenHighRiskPorts = make_set(PortInfo),
Protocols = make_set(Protocol),
AttackExposureScore = max(AttackExposureScore)
by ProjectName, FirewallName, ResourceName, FindingName, Severity, Description, ExternalUri
Stage 17: extend
| extend OpenHighRiskPorts = strcat_array(OpenHighRiskPorts, ", ")
Stage 18: extend
| extend Protocols = strcat_array(Protocols, ", ")
Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
| Field | Kind | Values |
|---|---|---|
ExternalSourceRanges | contains |
|
FindingCategory | eq |
|
MaxPort | in |
|
MinPort | in |
|
state | eq |
|
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
AttackExposureScore | summarize |
Description | summarize |
ExternalUri | summarize |
FindingName | summarize |
FirewallName | summarize |
OpenHighRiskPorts | extend |
ProjectName | summarize |
Protocols | extend |
ResourceName | summarize |
Severity | summarize |
TimeGenerated | summarize |