AWS Security Group Detection
This example shows how to build a detection for overly permissive AWS EC2 security group modifications. Security groups act as virtual firewalls for EC2 instances. Misconfigurations that allow unrestricted internet access create attack vectors that lead to breaches. You can identify dangerous modifications by analyzing CloudTrail events for security group changes that expose sensitive ports to the internet.
Attack overview
Security group misconfigurations lead to cloud breaches through this pattern:
- Initial misconfiguration - Administrators accidentally allow unrestricted access
- Deliberate modification - Attackers modify security groups after compromise
- Service exposure - Sensitive services become accessible from the internet
- Lateral movement - Exposed services provide entry points for further attacks
You can detect these misconfigurations by monitoring CloudTrail events for security group modifications that create overly permissive rules.
Detection strategy
The detection focuses on security group modifications that allow traffic from
any source (0.0.0.0/0 or ::/0) to sensitive ports. You write queries that
parse CloudTrail JSON events to extract security group changes, identify
permissive rules, and calculate risk scores based on the ports and protocols
exposed.
Hamelin targets CloudTrail events like AuthorizeSecurityGroupIngress and
ModifySecurityGroupRules that indicate firewall rule changes, then filters
for modifications that create internet-accessible endpoints.
Complete detection query
This detection query parses CloudTrail JSON events to identify security group modifications that expose services to unrestricted internet access. Hamelin extracts relevant fields from nested JSON, identifies sensitive ports, calculates risk scores based on exposure level, and creates structured output for security teams:
// Name: AWS EC2 Security Group Permissive Changes
// Author: Detection Engineer
//-
//- Description: This detection identifies potentially dangerous modifications
//- to EC2 security groups that could expose resources to unauthorized access.
//- It specifically looks for rules that allow traffic from any source
//- (0.0.0.0/0 or ::/0) on sensitive ports. Security groups act as virtual
//- firewalls, and overly permissive rules are a common security misconfiguration
//- that can lead to breaches.
//-
//- Tags: aws, ec2, security-group, network-security, misconfiguration
//- Mitre-Tactic: TA0005 (Defense Evasion), TA0001 (Initial Access)
//- Mitre-Technique: T1562.007 (Impair Defenses: Disable or Modify Cloud Firewall)
//-
FROM simba.cloudtrail_events
// Parse the JSON and extract fields
| SET evt = parse_json(event.original) AS variant
| SET eventName = evt.eventName AS string
| SET source_ip = evt.sourceIPAddress AS string
| SET user_identity = evt.userIdentity.arn AS string
| SET account_id = evt.userIdentity.accountId AS string
| SET aws_region = evt.awsRegion AS string
| SET error_code = evt.errorCode AS string
| SET request_params = evt.requestParameters AS variant
| SET group_id = evt.requestParameters.groupId AS string
| SET group_name = evt.requestParameters.groupName AS string
// Filter for security group modification events
| WHERE coalesce(eventName, '') IN [
'AuthorizeSecurityGroupIngress',
'AuthorizeSecurityGroupEgress',
'RevokeSecurityGroupIngress',
'RevokeSecurityGroupEgress',
'CreateSecurityGroup',
'ModifySecurityGroupRules'
]
// Only process successful events
| WHERE coalesce(error_code, '') == ''
// Check for overly permissive rules in the original event
| SET has_any_source = contains(event.original, '0.0.0.0/0') OR contains(event.original, '::/0')
// Extract port information (simplified approach)
| SET from_port = evt.requestParameters.ipPermissions[0].fromPort AS int
| SET to_port = evt.requestParameters.ipPermissions[0].toPort AS int
| SET ip_protocol = evt.requestParameters.ipPermissions[0].ipProtocol AS string
// Define sensitive ports
| SET sensitive_ports = [22, 23, 3389, 1433, 3306, 5432, 5984, 6379, 7000, 7001, 8020, 8086, 8888, 9042, 9160, 9200, 9300, 11211, 27017, 27018, 27019, 50070]
| SET is_sensitive_port = coalesce(from_port, 0) IN sensitive_ports OR
coalesce(to_port, 0) IN sensitive_ports OR
(coalesce(from_port, 0) <= 22 AND coalesce(to_port, 0) >= 22) OR
(coalesce(from_port, 0) <= 3389 AND coalesce(to_port, 0) >= 3389)
// Check for allow all protocols
| SET is_all_traffic = coalesce(ip_protocol, '') == '-1' OR
(coalesce(from_port, 0) == 0 AND coalesce(to_port, 0) == 65535)
// Only flag events with permissive rules
| WHERE has_any_source
// Calculate risk score
| SET risk_score = if(
contains(coalesce(eventName, ''), 'Authorize') AND is_all_traffic,
100, // Critical - allowing all traffic from anywhere
if(
contains(coalesce(eventName, ''), 'Authorize') AND is_sensitive_port,
90, // Very high - sensitive ports exposed
if(
contains(coalesce(eventName, ''), 'Authorize'),
75, // High - any port exposed to internet
40 // Low - revoking permissive rules (good action)
)
)
)
// Create human-readable message
| SET action_type = if(contains(coalesce(eventName, ''), 'Authorize'), 'opened', 'closed')
| SET from_port_str = coalesce(from_port, 0) AS string
| SET to_port_str = coalesce(to_port, 0) AS string
| SET port_info = if(
is_all_traffic,
'all ports',
'port(s) ' + from_port_str + '-' + to_port_str
)
| SET message = 'Security group ' + action_type + ' to internet: ' +
port_info + ' on group ' + coalesce(group_id, coalesce(group_name, 'unknown'))
// Create labels map for context
| SET labels = map(
'Severity': if(risk_score >= 90, 'CRITICAL', if(risk_score >= 75, 'HIGH', 'LOW')) AS string,
'Account': coalesce(account_id, '') AS string,
'Security Group': coalesce(group_id, coalesce(group_name, '')) AS string,
'User': coalesce(user_identity, '') AS string,
'Source IP': coalesce(source_ip, '') AS string,
'Event': coalesce(eventName, '') AS string,
'Region': coalesce(aws_region, '') AS string,
'Protocol': coalesce(ip_protocol, 'unknown') AS string,
'Port Range': port_info AS string,
'Action': action_type AS string,
'Tactic': 'Defense Evasion' AS string,
'Technique': 'T1562.007' AS string
)
// Format the final output
| SELECT
timestamp,
event.start = timestamp,
event.end = timestamp,
message,
labels,
host.name = coalesce(account_id, ''),
user.name = coalesce(user_identity, ''),
source.ip = coalesce(source_ip, ''),
cloud.region = coalesce(aws_region, ''),
cloud.service.name = 'ec2',
network.protocol = coalesce(ip_protocol, ''),
rule = {
name: 'AWS EC2 Security Group Permissive Changes' AS string,
description: 'Detects modifications to EC2 security groups that allow ' +
'unrestricted access from the internet (0.0.0.0/0), especially ' +
'on sensitive ports commonly targeted by attackers.' AS string,
mitre_tactics: ['Defense Evasion', 'Initial Access'] AS array(string),
mitre_techniques: ['T1562.007'] AS array(string),
severity: if(risk_score >= 90, 'critical', if(risk_score >= 75, 'high', 'low')) AS string,
references: [
'https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-rules.html',
'https://docs.aws.amazon.com/vpc/latest/userguide/VPC_SecurityGroups.html'
] AS array(string)
}
Query breakdown
This detection shows several techniques that make Hamelin effective for cloud security monitoring. Let's examine each component:
JSON parsing and field extraction
The detection starts by parsing CloudTrail JSON events and extracting relevant fields. CloudTrail events contain nested JSON structures, so you need to extract specific fields systematically:
| SET evt = parse_json(event.original) AS variant
| SET eventName = evt.eventName AS string
| SET source_ip = evt.sourceIPAddress AS string
| SET user_identity = evt.userIdentity.arn AS string
| SET group_id = evt.requestParameters.groupId AS string
The parse_json() function converts the JSON string into a variant type.
You can then extract specific fields using dot notation. The coalesce()
function handles missing fields gracefully.
Event filtering and validation
The detection filters for specific CloudTrail events that indicate security group modifications. You process only successful events to avoid false positives from failed API calls:
| WHERE coalesce(eventName, '') IN [
'AuthorizeSecurityGroupIngress',
'AuthorizeSecurityGroupEgress',
'RevokeSecurityGroupIngress',
'RevokeSecurityGroupEgress',
'CreateSecurityGroup',
'ModifySecurityGroupRules'
]
| WHERE coalesce(error_code, '') == ''
This filtering ensures you analyze actual security group changes, not failed API calls or unrelated CloudTrail events.
Permissive rule detection
The detection identifies overly permissive rules by searching for CIDR blocks that allow traffic from anywhere on the internet:
| SET has_any_source = contains(event.original, '0.0.0.0/0') OR contains(event.original, '::/0')
| WHERE has_any_source
The contains() function searches the raw JSON for these permissive CIDR
blocks. This catches rules that expose services to the entire internet.
Sensitive port identification
The detection defines a list of commonly targeted ports and checks whether the security group modification affects these sensitive services:
| SET sensitive_ports = [22, 23, 3389, 1433, 3306, 5432, ...]
| SET is_sensitive_port = coalesce(from_port, 0) IN sensitive_ports OR
coalesce(to_port, 0) IN sensitive_ports
This includes SSH (22), RDP (3389), database ports (1433, 3306, 5432), and other services commonly targeted by attackers.
Risk scoring logic
The detection calculates risk scores based on the type of modification and the sensitivity of exposed ports:
| SET risk_score = if(
contains(coalesce(eventName, ''), 'Authorize') AND is_all_traffic,
100, // Critical - allowing all traffic from anywhere
if(
contains(coalesce(eventName, ''), 'Authorize') AND is_sensitive_port,
90, // Very high - sensitive ports exposed
if(
contains(coalesce(eventName, ''), 'Authorize'),
75, // High - any port exposed to internet
40 // Low - revoking permissive rules (good action)
)
)
)
This nested if() structure prioritizes the most dangerous configurations.
It also recognizes when administrators remove permissive rules.
Structured output generation
The detection creates both human-readable messages and structured labels for different consumers:
| SET message = 'Security group ' + action_type + ' to internet: ' +
port_info + ' on group ' + coalesce(group_id, group_name)
| SET labels = map(
'Severity': if(risk_score >= 90, 'CRITICAL', 'HIGH'),
'Account': account_id,
'Security Group': group_id,
'User': user_identity
)
This provides context for analysts and maintains machine-readable structure for SIEM integration.
Advanced techniques demonstrated
This detection uses several techniques that make Hamelin effective for cloud security monitoring:
JSON parsing and navigation handles complex nested CloudTrail event
structures using parse_json() and dot notation for field extraction.
Flexible field handling uses coalesce() to handle missing or null fields
in CloudTrail events gracefully.
String pattern matching uses contains() for efficient searching of
CIDR blocks within raw JSON content.
Conditional risk scoring uses nested if() statements to create
nuanced risk assessments based on multiple factors.
Cloud-native field mapping creates ECS-compliant output with cloud-specific
fields like cloud.region and cloud.service.name.
Reference documentation includes links to official AWS documentation for analyst context and verification.
Detection tuning
You can adapt this detection for different cloud environments:
Expand sensitive ports by adding environment-specific services to the sensitive ports list based on applications in use.
Adjust risk scores by modifying the scoring logic based on risk tolerance and compliance requirements.
Add region filtering by including or excluding specific AWS regions based on where resources should legitimately exist.
Customize user filtering by adding exclusions for automated tools or service accounts that legitimately modify security groups.
The modular structure with clear variable definitions makes it easy to modify individual components without affecting the overall detection logic.