Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-SkillsFramework mappings
MITRE ATT&CK
When to Use
- When investigating suspected AWS account compromise
- After detecting unauthorized API calls or credential exposure
- During incident response involving cloud infrastructure
- When analyzing S3 data exfiltration or IAM privilege escalation
- For post-incident forensic timeline reconstruction
Prerequisites
- AWS account with CloudTrail enabled (management and data events)
- IAM permissions for cloudtrail:LookupEvents, s3:GetObject, athena:StartQueryExecution
- boto3 Python SDK installed
- CloudTrail logs delivered to S3 with optional Athena table configured
- AWS CLI configured with appropriate credentials
Workflow
- Scope Investigation: Identify timeframe, affected accounts, and compromised credentials.
- Query CloudTrail: Use boto3 lookup_events or Athena to retrieve relevant API events.
- Filter by Indicators: Search for suspicious user agents, source IPs, and event names.
- Reconstruct Timeline: Build chronological sequence of attacker actions from API calls.
- Analyze Access Patterns: Identify data access, IAM changes, and resource modifications.
- Identify Persistence: Check for new IAM users, access keys, roles, or Lambda functions.
- Generate Report: Produce forensic timeline with findings and remediation steps.
Key Concepts
| Concept | Description |
|---|---|
| LookupEvents | CloudTrail API to query management events (last 90 days) |
| Athena Queries | SQL queries against CloudTrail logs in S3 for historical analysis |
| User Agent Analysis | Identify tool signatures (AWS CLI, SDK, console, custom) |
| AccessKeyId | Track activity by specific IAM access key |
| EventName | AWS API action name (e.g., GetObject, CreateUser, AssumeRole) |
| sourceIPAddress | Origin IP of API call for geolocation analysis |
Tools & Systems
| Tool | Purpose |
|---|---|
| boto3 CloudTrail client | Programmatic CloudTrail event lookup |
| AWS Athena | SQL-based analysis of CloudTrail S3 logs |
| AWS CLI | Command-line CloudTrail queries |
| jq | JSON processing for CloudTrail event parsing |
| CloudTrail Lake | Advanced event data store with SQL query support |
Output Format
Forensic Report: AWS-IR-[DATE]-[SEQ]
Account: [AWS Account ID]
Timeframe: [Start] to [End]
Compromised Credentials: [Access Key IDs]
Suspicious Events: [Count]
Source IPs: [List of attacker IPs]
Actions Taken: [API calls by attacker]
Data Accessed: [S3 objects, secrets, etc.]
Persistence Mechanisms: [New users, keys, roles]Source materials
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 1
api-reference.md3.2 KB
AWS CloudTrail Forensics API Reference
boto3 CloudTrail Client
import boto3
client = boto3.client("cloudtrail", region_name="us-east-1")lookup_events
response = client.lookup_events(
LookupAttributes=[
{"AttributeKey": "Username", "AttributeValue": "compromised-user"},
],
StartTime=datetime(2025, 1, 1),
EndTime=datetime(2025, 1, 2),
MaxResults=50,
)LookupAttributes Keys
| AttributeKey | Description |
|---|---|
| EventId | Unique event identifier |
| EventName | AWS API action (e.g., CreateUser, GetObject) |
| ReadOnly | true/false for read-only API calls |
| Username | IAM user or role session name |
| ResourceType | AWS resource type (e.g., AWS::S3::Object) |
| ResourceName | Name or ARN of the resource accessed |
| EventSource | AWS service (e.g., iam.amazonaws.com) |
| AccessKeyId | IAM access key used for the API call |
Response Structure
{
"Events": [
{
"EventId": "abc123",
"EventName": "CreateUser",
"EventTime": "2025-01-01T12:00:00Z",
"Username": "attacker",
"CloudTrailEvent": "{\"sourceIPAddress\":\"1.2.3.4\",\"userAgent\":\"aws-cli/2.0\",...}"
}
],
"NextToken": "..."
}Paginator Usage
paginator = client.get_paginator("lookup_events")
for page in paginator.paginate(
LookupAttributes=[{"AttributeKey": "AccessKeyId", "AttributeValue": "AKIA..."}],
StartTime=start, EndTime=end
):
for event in page["Events"]:
ct = json.loads(event["CloudTrailEvent"])
print(ct["sourceIPAddress"], ct["eventName"])AWS CLI Equivalents
# Lookup events by username
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=Username,AttributeValue=compromised-user \
--start-time 2025-01-01T00:00:00Z \
--output json
# Search by access key
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAEXAMPLE \
--max-results 50Athena Query for S3 CloudTrail Logs
SELECT eventtime, eventname, useridentity.arn, sourceipaddress, useragent,
requestparameters, responseelements, errorcode
FROM cloudtrail_logs
WHERE eventtime BETWEEN '2025-01-01' AND '2025-01-02'
AND useridentity.accesskeyid = 'AKIAEXAMPLE'
ORDER BY eventtime;Key Forensic Event Names
| Event Name | Service | Forensic Significance |
|---|---|---|
| CreateUser | IAM | Persistence - new user account |
| CreateAccessKey | IAM | Persistence - new credential |
| AssumeRole | STS | Lateral movement / privilege escalation |
| GetObject | S3 | Data exfiltration |
| StopLogging | CloudTrail | Anti-forensics |
| PutBucketPolicy | S3 | Permission modification |
| RunInstances | EC2 | Cryptomining / C2 infrastructure |
| GetSecretValue | SecretsManager | Credential theft |
Suspicious User Agents
| User Agent Pattern | Tool |
|---|---|
Pacu/... |
AWS exploitation framework |
python-requests |
Custom Python scripts |
aws-cli/2.x from unusual IP |
CLI from attacker machine |
Scout Suite |
Cloud security assessment |
Prowler |
AWS security scanner |
Scripts 1
agent.py8.6 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""AWS CloudTrail Forensics Agent - investigates API activity for incident response using boto3."""
import json
import argparse
import logging
from collections import defaultdict
from datetime import datetime, timedelta
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
try:
import boto3
HAS_BOTO3 = True
except ImportError:
HAS_BOTO3 = False
SUSPICIOUS_EVENTS = [
"CreateUser", "CreateAccessKey", "AttachUserPolicy", "AttachRolePolicy",
"PutUserPolicy", "CreateRole", "AssumeRole", "CreateLoginProfile",
"UpdateLoginProfile", "CreateFunction20150331", "UpdateFunctionCode20150331v2",
"AuthorizeSecurityGroupIngress", "RunInstances", "StopLogging", "DeleteTrail",
"PutBucketPolicy", "PutBucketAcl", "GetSecretValue", "GetParametersByPath",
]
PERSISTENCE_EVENTS = [
"CreateUser", "CreateAccessKey", "CreateRole", "CreateLoginProfile",
"CreateFunction20150331", "CreateEventSourceMapping20150331",
]
def lookup_events(client, start_time, end_time, username=None, access_key_id=None, event_name=None):
"""Query CloudTrail using lookup_events with pagination."""
kwargs = {
"StartTime": start_time,
"EndTime": end_time,
"MaxResults": 50,
}
lookup_attrs = []
if username:
lookup_attrs.append({"AttributeKey": "Username", "AttributeValue": username})
if access_key_id:
lookup_attrs.append({"AttributeKey": "AccessKeyId", "AttributeValue": access_key_id})
if event_name:
lookup_attrs.append({"AttributeKey": "EventName", "AttributeValue": event_name})
if lookup_attrs:
kwargs["LookupAttributes"] = lookup_attrs
events = []
paginator = client.get_paginator("lookup_events")
for page in paginator.paginate(**kwargs):
for event in page.get("Events", []):
ct_event = json.loads(event.get("CloudTrailEvent", "{}"))
events.append({
"event_time": str(event.get("EventTime", "")),
"event_name": event.get("EventName", ""),
"event_source": ct_event.get("eventSource", ""),
"username": event.get("Username", ""),
"source_ip": ct_event.get("sourceIPAddress", ""),
"user_agent": ct_event.get("userAgent", ""),
"access_key_id": ct_event.get("userIdentity", {}).get("accessKeyId", ""),
"arn": ct_event.get("userIdentity", {}).get("arn", ""),
"error_code": ct_event.get("errorCode", ""),
"error_message": ct_event.get("errorMessage", ""),
"request_params": ct_event.get("requestParameters", {}),
"response_elements": ct_event.get("responseElements", {}),
"aws_region": ct_event.get("awsRegion", ""),
})
logger.info("Retrieved %d CloudTrail events", len(events))
return events
def detect_suspicious_activity(events):
"""Flag events matching suspicious API calls."""
suspicious = []
for event in events:
if event["event_name"] in SUSPICIOUS_EVENTS:
event["indicator"] = "suspicious_api_call"
event["severity"] = "high" if event["event_name"] in PERSISTENCE_EVENTS else "medium"
suspicious.append(event)
if event["error_code"] == "AccessDenied":
event["indicator"] = "access_denied_enumeration"
event["severity"] = "medium"
suspicious.append(event)
return suspicious
def detect_persistence(events):
"""Identify persistence mechanisms created by attacker."""
persistence = []
for event in events:
if event["event_name"] in PERSISTENCE_EVENTS and not event["error_code"]:
details = {}
resp = event.get("response_elements", {})
if event["event_name"] == "CreateUser":
details["created_user"] = resp.get("user", {}).get("userName", "")
elif event["event_name"] == "CreateAccessKey":
details["access_key_id"] = resp.get("accessKey", {}).get("accessKeyId", "")
details["for_user"] = resp.get("accessKey", {}).get("userName", "")
elif event["event_name"] == "CreateRole":
details["role_name"] = resp.get("role", {}).get("roleName", "")
persistence.append({**event, "persistence_details": details})
return persistence
def analyze_source_ips(events):
"""Analyze source IP distribution for anomalies."""
ip_activity = defaultdict(lambda: {"count": 0, "events": set(), "users": set()})
for event in events:
ip = event["source_ip"]
if ip:
ip_activity[ip]["count"] += 1
ip_activity[ip]["events"].add(event["event_name"])
ip_activity[ip]["users"].add(event["username"])
result = {}
for ip, data in ip_activity.items():
result[ip] = {
"request_count": data["count"],
"unique_events": len(data["events"]),
"unique_users": len(data["users"]),
"event_types": list(data["events"])[:10],
}
return dict(sorted(result.items(), key=lambda x: x[1]["request_count"], reverse=True))
def analyze_user_agents(events):
"""Analyze user agents for tool identification."""
ua_counts = defaultdict(int)
for event in events:
ua = event.get("user_agent", "unknown")
ua_counts[ua] += 1
suspicious_uas = {}
for ua, count in ua_counts.items():
if any(tool in ua.lower() for tool in ["pacu", "prowler", "scoutsuite", "boto", "python", "curl", "custom"]):
suspicious_uas[ua] = count
return {
"all_user_agents": dict(sorted(ua_counts.items(), key=lambda x: x[1], reverse=True)[:15]),
"suspicious_user_agents": suspicious_uas,
}
def build_timeline(events):
"""Build chronological attack timeline."""
return sorted(
[{"time": e["event_time"], "event": e["event_name"], "user": e["username"],
"source_ip": e["source_ip"], "error": e.get("error_code", "")}
for e in events],
key=lambda x: x["time"]
)
def generate_report(events, suspicious, persistence, ip_analysis, ua_analysis):
"""Generate forensic investigation report."""
report = {
"timestamp": datetime.utcnow().isoformat(),
"investigation_type": "AWS CloudTrail Forensic Analysis",
"total_events_analyzed": len(events),
"suspicious_events": len(suspicious),
"persistence_mechanisms_found": len(persistence),
"unique_source_ips": len(ip_analysis),
"source_ip_analysis": dict(list(ip_analysis.items())[:10]),
"user_agent_analysis": ua_analysis,
"persistence_details": persistence[:10],
"top_suspicious_events": suspicious[:20],
"timeline": build_timeline(events)[:50],
}
return report
def main():
parser = argparse.ArgumentParser(description="AWS CloudTrail Forensics Agent")
parser.add_argument("--hours-back", type=int, default=24, help="Hours to look back (default: 24)")
parser.add_argument("--username", help="Filter by IAM username")
parser.add_argument("--access-key-id", help="Filter by access key ID")
parser.add_argument("--event-name", help="Filter by specific event name")
parser.add_argument("--region", default="us-east-1", help="AWS region (default: us-east-1)")
parser.add_argument("--profile", help="AWS CLI profile name")
parser.add_argument("--output", default="cloudtrail_forensics_report.json")
args = parser.parse_args()
if not HAS_BOTO3:
logger.error("boto3 is required: pip install boto3")
return
session_kwargs = {}
if args.profile:
session_kwargs["profile_name"] = args.profile
session = boto3.Session(**session_kwargs)
client = session.client("cloudtrail", region_name=args.region)
end_time = datetime.utcnow()
start_time = end_time - timedelta(hours=args.hours_back)
logger.info("Querying CloudTrail: %s to %s", start_time.isoformat(), end_time.isoformat())
events = lookup_events(client, start_time, end_time, args.username, args.access_key_id, args.event_name)
suspicious = detect_suspicious_activity(events)
persistence = detect_persistence(events)
ip_analysis = analyze_source_ips(events)
ua_analysis = analyze_user_agents(events)
report = generate_report(events, suspicious, persistence, ip_analysis, ua_analysis)
with open(args.output, "w") as f:
json.dump(report, f, indent=2, default=str)
logger.info("Forensics: %d events, %d suspicious, %d persistence mechanisms",
len(events), len(suspicious), len(persistence))
print(json.dumps(report, indent=2, default=str))
if __name__ == "__main__":
main()
Keep exploring