Detection rules › Panther
AWS S3 Access IP Allowlist
Checks that the remote IP accessing the S3 bucket is in the IP allowlist.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Collection | T1530 Data from Cloud Storage |
Rule body yaml
AnalysisType: rule
Filename: aws_s3_access_ip_allowlist.py
RuleID: "AWS.S3.ServerAccess.IPWhitelist"
DisplayName: "AWS S3 Access IP Allowlist"
DedupPeriodMinutes: 60 # 1 hour
Enabled: false
LogTypes:
- AWS.S3ServerAccess
Tags:
- AWS
- Configuration Required
- Identity & Access Management
- Collection:Data From Cloud Storage Object
Reports:
MITRE ATT&CK:
- TA0009:T1530
Severity: Medium
Description: >
Checks that the remote IP accessing the S3 bucket is in the IP allowlist.
Runbook: >
Verify whether unapproved access of S3 objects occurred, and take appropriate steps to remediate damage (for example, informing related parties of unapproved access and potentially invalidating data that was accessed). Consider updating the access policies of the S3 bucket to prevent future unapproved access.
Reference: https://aws.amazon.com/premiumsupport/knowledge-center/block-s3-traffic-vpc-ip/
SummaryAttributes:
- bucket
- key
- remoteip
Tests:
- Name: Access From Approved IP
ExpectedResult: false
Log: { "remoteip": "10.0.0.1", "bucket": "my-test-bucket" }
- Name: Access From Unapproved IP
ExpectedResult: true
Log: { "remoteip": "11.0.0.1", "bucket": "my-test-bucket" }
- Name: Access From IPv6
ExpectedResult: true
Log: { "remoteip": "2600:1ffe:8140::a47:a85a", "bucket": "my-test-bucket" }
Detection logic
Rule logic imperative Python
from ipaddress import IPv4Network, IPv6Network, ip_network
from panther_aws_helpers import aws_rule_context
BUCKETS_TO_MONITOR = {
}
ALLOWLIST_NETWORKS = {
ip_network("10.0.0.0/8"),
}
def rule(event):
if BUCKETS_TO_MONITOR:
if event.get("bucket") not in BUCKETS_TO_MONITOR:
return False
if "remoteip" not in event:
return False
cidr_ip = ip_network(event.get("remoteip"))
return not any(
is_subnet(approved_ip_range, cidr_ip) for approved_ip_range in ALLOWLIST_NETWORKS
)
def title(event):
return f"Non-Approved IP access to S3 Bucket [{event.get('bucket', '<UNKNOWN_BUCKET>')}]"
def alert_context(event):
return aws_rule_context(event)
def is_subnet(supernet: IPv4Network | IPv6Network, subnet: IPv4Network | IPv6Network) -> bool:
"""Return true if 'subnet' is a subnet of 'supernet'"""
if supernet.network_address.version != subnet.network_address.version:
return False
return subnet.subnet_of(supernet)
The parser cannot express this rule's logic as a field filter; the imperative Python above is the detection.
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 |
|---|
eventName |
eventSource |
awsRegion |
recipientAccountId |
sourceIPAddress |
userAgent |
userIdentity |
bucket |