Detection rules › Kusto

GCP Security Command Center - Detect Firewall rules allowing unrestricted high-risk ports

Status
available
Severity
high
Time window
1h
Group by
Description, ExternalUri, FindingName, FirewallName, ProjectName, ResourceName, Severity
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
Initial Access
Discovery
Lateral Movement

Rule body

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 =
ifMinPort =~ MaxPorttostring(MinPort)
elsestrcat(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

These rows show field, operator, and value matches.

FieldKindValuesSearch
ExternalSourceRangescontains
  • 0.0.0.0/0 transforms: tostring
field:"ExternalSourceRanges" kind:contains value:"0.0.0.0/0"
FindingCategoryeq
  • OPEN_FIREWALL
field:"FindingCategory" kind:eq value:"OPEN_FIREWALL"
MaxPortin
  • 110
  • 135
  • 143
  • 1433
  • 1434
  • 20
  • 21
  • 22
  • 23
  • 25
  • 3000
  • 3306
  • 3389
  • 4333
  • 445
  • 5000
  • 5432
  • 5500
  • 5601
  • 8080
  • 8088
  • 8888
  • 9200
  • 9300
field:"MaxPort" kind:in
MinPortin
  • 110
  • 135
  • 143
  • 1433
  • 1434
  • 20
  • 21
  • 22
  • 23
  • 25
  • 3000
  • 3306
  • 3389
  • 4333
  • 445
  • 5000
  • 5432
  • 5500
  • 5601
  • 8080
  • 8088
  • 8888
  • 9200
  • 9300
field:"MinPort" kind:in
stateeq
  • ACTIVE transforms: tostring
field:"state" kind:eq value:"ACTIVE"

Output fields

These fields are emitted when the rule matches.

FieldSource
AttackExposureScoresummarize
Descriptionsummarize
ExternalUrisummarize
FindingNamesummarize
FirewallNamesummarize
OpenHighRiskPortsextend
ProjectNamesummarize
Protocolsextend
ResourceNamesummarize
Severitysummarize
TimeGeneratedsummarize