Skip to main content

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:

  1. Initial misconfiguration - Administrators accidentally allow unrestricted access
  2. Deliberate modification - Attackers modify security groups after compromise
  3. Service exposure - Sensitive services become accessible from the internet
  4. 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.