Detection rules › Kusto
AWS Security Hub - Detect EC2 Security groups allowing unrestricted high-risk ports
This query detects EC2 Security Groups that allow unrestricted (0.0.0.0/0 or ::/0) ingress to high-risk ports using AWS Security Hub control EC2.19 findings. Publicly exposed management, database, and service ports (e.g., RDP 3389, SSH 22, SQL 1433/3306) significantly increase the risk of brute-force attacks, exploitation, and lateral movement.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access | T1133 External Remote Services |
| Discovery | T1046 Network Service Discovery |
| Lateral Movement | T1021 Remote Services |
Rule body kusto
id: d2b6fa0f-6a4c-4c48-8c64-5e2e1ac4e7b9
name: AWS Security Hub - Detect EC2 Security groups allowing unrestricted high-risk ports
description: |
This query detects EC2 Security Groups that allow unrestricted (0.0.0.0/0 or ::/0) ingress to high-risk ports using AWS Security Hub control EC2.19 findings.
Publicly exposed management, database, and service ports (e.g., RDP 3389, SSH 22, SQL 1433/3306) significantly increase the risk of brute-force attacks, exploitation, and lateral movement.
severity: High
status: Available
requiredDataConnectors:
- connectorId: AWSSecurityHub
dataTypes:
- AWSSecurityHubFindings
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- LateralMovement
- Discovery
relevantTechniques:
- T1133
- T1021
- T1046
tags:
- AWS Foundational Security Best Practices v1.0.0
- NIST 800-53 r5
query: |
let HighRiskPorts = dynamic([3389,20,23,110,143,3306,8080,1433,9200,9300,25,445,135,21,1434,4333,5432,5500,5601,22,3000,5000,8088,8888]);
AWSSecurityHubFindings
| where RecordState == "ACTIVE" and ComplianceStatus == "FAILED"
| where tostring(AwsSecurityFindingGeneratorId) == "security-control/EC2.19"
or tostring(ComplianceSecurityControlId) == "EC2.19"
| mv-expand Resource = Resources
| where tostring(Resource.Type) == "AwsEc2SecurityGroup"
| extend SGDetails = Resource.Details.AwsEc2SecurityGroup
| extend IpPermissions = SGDetails.IpPermissions
| mv-expand Perm = IpPermissions
| where toint(Perm.FromPort) in (HighRiskPorts)
| mv-expand Range = Perm.IpRanges
| where tostring(Range.CidrIp) in ("0.0.0.0/0", "::/0")
| summarize TimeGenerated = max(TimeGenerated), OpenHighRiskPorts = make_set(tostring(Perm.FromPort))
by AwsAccountId, AwsRegion, AwsSecurityFindingTitle, AwsSecurityFindingDescription,
AwsSecurityFindingId, ComplianceSecurityControlId, SecurityGroupId = tostring(SGDetails.GroupId), SecurityGroupARN = tostring(Resource.Id)
| extend OpenHighRiskPorts = strcat_array(OpenHighRiskPorts, ", ")
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AwsAccountId
- identifier: CloudAppAccountId
columnName: AwsAccountId
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: SecurityGroupARN
customDetails:
ComplianceControlId: ComplianceSecurityControlId
Region: AwsRegion
FindingId: AwsSecurityFindingId
SecurityGroupId: SecurityGroupId
OpenHighRiskPorts: OpenHighRiskPorts
alertDetailsOverride:
alertDisplayNameFormat: "EC2 Security group {{SecurityGroupId}} allows unrestricted high-risk ports"
alertDescriptionFormat: |-
EC2 Security group {{SecurityGroupId}} allows unrestricted (0.0.0.0/0 or ::/0) ingress to high-risk ports: {{OpenHighRiskPorts}}. Restrict or remove the offending rules.
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let HighRiskPorts = dynamic([3389,20,23,110,143,3306,8080,1433,9200,9300,25,445,135,21,1434,4333,5432,5500,5601,22,3000,5000,8088,8888]);
Stage 1: source
AWSSecurityHubFindings
Stage 2: where
| where RecordState == "ACTIVE" and ComplianceStatus == "FAILED"
Stage 3: where
| where tostring(AwsSecurityFindingGeneratorId) == "security-control/EC2.19"
or tostring(ComplianceSecurityControlId) == "EC2.19"
Stage 4: mv-expand
| mv-expand Resource = Resources
Stage 5: where
| where tostring(Resource.Type) == "AwsEc2SecurityGroup"
Stage 6: extend
| extend SGDetails = Resource.Details.AwsEc2SecurityGroup
Stage 7: extend
| extend IpPermissions = SGDetails.IpPermissions
Stage 8: mv-expand
| mv-expand Perm = IpPermissions
Stage 9: where
| where toint(Perm.FromPort) in (HighRiskPorts)
Stage 10: mv-expand
| mv-expand Range = Perm.IpRanges
Stage 11: where
| where tostring(Range.CidrIp) in ("0.0.0.0/0", "::/0")
Stage 12: summarize
| summarize TimeGenerated = max(TimeGenerated), OpenHighRiskPorts = make_set(tostring(Perm.FromPort))
by AwsAccountId, AwsRegion, AwsSecurityFindingTitle, AwsSecurityFindingDescription,
AwsSecurityFindingId, ComplianceSecurityControlId, SecurityGroupId = tostring(SGDetails.GroupId), SecurityGroupARN = tostring(Resource.Id)
Stage 13: extend
| extend OpenHighRiskPorts = strcat_array(OpenHighRiskPorts, ", ")
Indicators
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 |
|---|---|---|
AwsSecurityFindingGeneratorId | eq |
|
CidrIp | in |
|
ComplianceSecurityControlId | eq |
|
ComplianceStatus | eq |
|
FromPort | in |
|
RecordState | eq |
|
Type | 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 |
|---|---|
AwsAccountId | summarize |
AwsRegion | summarize |
AwsSecurityFindingDescription | summarize |
AwsSecurityFindingId | summarize |
AwsSecurityFindingTitle | summarize |
ComplianceSecurityControlId | summarize |
OpenHighRiskPorts | extend |
SecurityGroupARN | summarize |
SecurityGroupId | summarize |
TimeGenerated | summarize |