npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
When to Use
- Cloud security posture management (CSPM) alerts on unauthorized resource changes
- CloudTrail, Azure Activity Logs, or GCP Audit Logs show suspicious API calls
- Cloud access keys or service principal credentials are suspected compromised
- Unauthorized compute instances, storage buckets, or IAM changes are detected
- A cloud-hosted application is breached and attacker activity spans cloud services
Do not use for on-premises-only incidents with no cloud component; use standard enterprise IR procedures.
Prerequisites
- Cloud-native logging enabled and centralized: AWS CloudTrail (all regions), Azure Activity/Sign-in Logs, GCP Cloud Audit Logs
- IR-specific cloud IAM roles pre-provisioned with read-only forensic access
- Isolated forensic account/subscription/project for evidence preservation
- Cloud incident response runbooks specific to each cloud provider
- Cloud-native security tools: AWS GuardDuty, Azure Defender for Cloud, GCP Security Command Center
- Network traffic logging: VPC Flow Logs (AWS/GCP), NSG Flow Logs (Azure)
Workflow
Step 1: Detect and Confirm the Cloud Incident
Identify the scope and nature of the compromise:
AWS Indicators:
CloudTrail suspicious events to investigate:
- ConsoleLogin from unexpected geolocation or IP
- CreateAccessKey for existing IAM user (persistence)
- RunInstances for crypto-mining (large instance types)
- PutBucketPolicy making S3 bucket public
- AssumeRole to cross-account roles
- DeleteTrail or StopLogging (defense evasion)
- CreateUser or AttachUserPolicy (privilege escalation)Azure Indicators:
Azure Activity Log events to investigate:
- Sign-in from anonymous IP or TOR exit node
- Service principal credential added
- Role assignment changes (Owner, Contributor added)
- VM created in unusual region
- Storage account access key regenerated
- Conditional Access policy modified or deleted
- MFA disabled for user accountGCP Indicators:
GCP Audit Log events to investigate:
- SetIamPolicy changes granting broad access
- CreateServiceAccountKey for existing SA
- InsertInstance in unexpected zone
- SetBucketIamPolicy with allUsers
- DeleteLog or UpdateSink (log tampering)Step 2: Contain Cloud Identity Compromise
Cloud containment is primarily an identity operation:
AWS Containment:
# Disable compromised IAM access keys
aws iam update-access-key --user-name compromised-user \
--access-key-id AKIA... --status Inactive
# Attach deny-all policy to compromised user
aws iam attach-user-policy --user-name compromised-user \
--policy-arn arn:aws:iam::aws:policy/AWSDenyAll
# Revoke all active sessions for compromised IAM role
aws iam put-role-policy --role-name compromised-role \
--policy-name RevokeOlderSessions --policy-document '{
"Version":"2012-10-17",
"Statement":[{
"Effect":"Deny",
"Action":"*",
"Resource":"*",
"Condition":{"DateLessThan":
{"aws:TokenIssueTime":"2025-11-15T15:00:00Z"}}
}]
}'
# Isolate compromised EC2 instance
aws ec2 modify-instance-attribute --instance-id i-0abc123 \
--groups sg-isolate-forensicAzure Containment:
# Disable compromised user
Set-AzureADUser -ObjectId "user@tenant.onmicrosoft.com" -AccountEnabled $false
# Revoke all sessions
Revoke-AzureADUserAllRefreshToken -ObjectId "user-object-id"
# Remove role assignments
Remove-AzRoleAssignment -ObjectId "sp-object-id" -RoleDefinitionName "Contributor"
# Isolate VM with NSG deny-all rule
$nsg = New-AzNetworkSecurityGroup -Name "isolate-nsg" -ResourceGroupName "rg" -Location "eastus"
$nsg | Add-AzNetworkSecurityRuleConfig -Name "DenyAll" -Priority 100 -Direction Inbound `
-Access Deny -Protocol * -SourceAddressPrefix * -SourcePortRange * `
-DestinationAddressPrefix * -DestinationPortRange *Step 3: Preserve Cloud Evidence
Collect evidence before ephemeral resources are terminated or logs rotate:
AWS Evidence Collection:
- Export CloudTrail events to S3 in the forensic account
- Snapshot EBS volumes of compromised EC2 instances
- Copy S3 access logs and object versions
- Export VPC Flow Logs for the affected VPC
- Capture IAM credential reports and access advisor data
Azure Evidence Collection:
- Export Azure Activity Logs and Sign-in Logs (90-day retention by default)
- Snapshot managed disks of compromised VMs
- Export Azure AD audit logs
- Capture NSG flow logs
- Export Conditional Access sign-in details
GCP Evidence Collection:
- Export Cloud Audit Logs to a forensic storage bucket
- Snapshot persistent disks of compromised VMs
- Export VPC Flow Logs
- Capture IAM policy snapshots
Step 4: Investigate Cloud-Specific Attack Patterns
Analyze logs for common cloud attack techniques:
Common Cloud Attack Patterns:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. Credential Compromise → IAM Privilege Escalation → Resource Abuse
2. Public S3/Blob → Data Exfiltration
3. SSRF from Web App → IMDS Token Theft → Lateral Movement
4. Compromised CI/CD Pipeline → Malicious Deployment
5. Cross-Account Role Abuse → Multi-Account Pivot
6. Lambda/Function Abuse → Crypto-mining or Data ProcessingIMDS Token Theft Investigation (AWS):
# Search CloudTrail for API calls using instance role credentials from external IP
aws cloudtrail lookup-events --lookup-attributes \
AttributeKey=EventSource,AttributeValue=ec2.amazonaws.com \
--start-time 2025-11-14 --end-time 2025-11-16 \
| jq '.Events[] | select(.CloudTrailEvent | fromjson | .sourceIPAddress != "internal")'Step 5: Eradicate and Recover
Remove adversary access and restore secure state:
- Rotate all compromised credentials (access keys, passwords, service principal secrets)
- Remove unauthorized IAM users, roles, policies, and access keys created by the attacker
- Terminate unauthorized compute instances (crypto-miners, C2 servers)
- Restore modified S3 bucket policies and storage access policies to pre-incident state
- Re-enable security controls that were disabled (CloudTrail, GuardDuty, Defender for Cloud)
- Review and restore Conditional Access policies and MFA configurations
Step 6: Post-Incident Cloud Hardening
Implement controls to prevent recurrence:
- Enable MFA for all IAM users and require MFA for sensitive API calls
- Implement SCPs (AWS) or Azure Policy to prevent logging disablement
- Enable GuardDuty / Defender for Cloud / Security Command Center with auto-remediation
- Implement least-privilege IAM policies using access analyzer data
- Enable IMDS v2 (token-required) on all EC2 instances to prevent SSRF-based token theft
- Configure budget alerts to detect crypto-mining cost spikes
Key Concepts
| Term | Definition |
|---|---|
| IMDS (Instance Metadata Service) | Cloud service providing instance credentials accessible from within a VM; SSRF attacks target IMDS to steal tokens |
| CloudTrail | AWS service logging all API calls across the AWS account; primary evidence source for AWS incident response |
| Service Principal | Non-human identity in Azure AD used by applications and services; compromise enables persistent API access |
| SCP (Service Control Policy) | AWS Organizations policy that limits the maximum permissions available to accounts; useful for guardrails |
| Ephemeral Infrastructure | Cloud resources (containers, functions, auto-scaled instances) that may be terminated before evidence can be collected |
| Cross-Account Role Assumption | AWS mechanism allowing one account to temporarily access resources in another; attackers pivot through assumed roles |
Tools & Systems
- AWS CloudTrail / Azure Activity Logs / GCP Audit Logs: Cloud-native API logging services providing the primary audit trail
- Cado Response: Cloud-native forensics platform for automated evidence capture from AWS, Azure, and GCP
- Prowler (AWS) / ScoutSuite (multi-cloud): Open-source cloud security assessment tools for post-incident posture review
- Steampipe: Open-source SQL-based tool for querying cloud APIs to investigate IAM configurations and resource states
- Cartography (Lyft): Open-source tool for mapping cloud infrastructure relationships and identifying attack paths
Common Scenarios
Scenario: AWS Access Key Compromised via Public GitHub Repository
Context: AWS GuardDuty alerts on API calls from an unexpected IP address using an IAM user's access key. The key was accidentally committed to a public GitHub repository 4 hours ago.
Approach:
- Immediately disable the compromised access key via AWS IAM
- Attach AWSDenyAll policy to the affected IAM user
- Query CloudTrail for all API calls made with the compromised key since exposure
- Identify resources created or modified by the attacker (EC2 instances for crypto-mining, new IAM users for persistence)
- Terminate unauthorized resources and remove backdoor IAM entities
- Rotate all credentials the compromised user had access to
- Enable GitHub secret scanning to prevent future credential leaks
Pitfalls:
- Only disabling the access key without checking for new access keys or IAM users created as persistence
- Not checking all AWS regions for attacker-created resources (crypto-miners deployed in every region)
- Forgetting to revoke temporary credentials from assumed roles (STS tokens remain valid until expiry)
- Not calculating the financial impact of unauthorized resource usage for insurance claims
Output Format
CLOUD INCIDENT RESPONSE REPORT
================================
Incident: INC-2025-1705
Cloud Provider: AWS (Account: 123456789012)
Date Detected: 2025-11-15T14:00:00Z
Detection Source: GuardDuty - UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration
COMPROMISE SUMMARY
Initial Access: IAM access key exposed in public GitHub repo
Affected Identity: iam-user: deploy-bot (AKIA...)
Attacker IP: 203.0.113.42 (VPN exit node, Netherlands)
Duration: 4 hours (10:00 UTC - 14:00 UTC)
ATTACKER ACTIVITY (from CloudTrail)
10:15 UTC - DescribeInstances (reconnaissance)
10:18 UTC - RunInstances x 12 (c5.4xlarge, all regions - crypto-mining)
10:22 UTC - CreateUser "backup-admin" (persistence)
10:23 UTC - CreateAccessKey for "backup-admin"
10:25 UTC - AttachUserPolicy - AdministratorAccess to "backup-admin"
10:30 UTC - PutBucketPolicy - s3://data-bucket made public (exfiltration)
CONTAINMENT ACTIONS
[x] Original access key disabled
[x] User policy set to AWSDenyAll
[x] Backdoor IAM user "backup-admin" deleted
[x] 12 crypto-mining instances terminated (all regions)
[x] S3 bucket policy restored to private
FINANCIAL IMPACT
Unauthorized EC2: $2,847 (4 hours x 12 x c5.4xlarge)
Data Transfer: $127 (S3 public access data egress)
Total: $2,974
POST-INCIDENT HARDENING
1. GitHub secret scanning enabled
2. Access key rotation policy implemented
3. SCP preventing CloudTrail disablement deployed
4. GuardDuty auto-remediation Lambda configuredReferences and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 1
api-reference.md2.4 KB
API Reference: Cloud Incident Response Agent
Overview
Automates AWS cloud incident response: disables compromised access keys, attaches deny-all policies, isolates EC2 instances, captures EBS snapshots for forensics, and queries CloudTrail for attacker activity timeline.
Dependencies
| Package | Version | Purpose |
|---|---|---|
| requests | >=2.28 | HTTP API calls |
| AWS CLI | >=2.0 | AWS service interaction (subprocess) |
CLI Usage
python agent.py --incident-id INC-2025-001 --username compromised-user \
--access-key-id AKIA... --instance-id i-0abc123 --output report.jsonArguments
| Argument | Required | Description |
|---|---|---|
--incident-id |
Yes | Incident ticket identifier |
--username |
Yes | Compromised IAM username |
--access-key-id |
No | Access key ID to disable |
--instance-id |
No | EC2 instance ID to isolate |
--forensic-sg |
No | Forensic isolation security group ID |
--output |
No | Output report file path |
Key Functions
aws_disable_access_key(username, access_key_id)
Disables a compromised IAM access key by setting status to Inactive via aws iam update-access-key.
aws_attach_deny_all(username)
Attaches the AWSDenyAll managed policy to block all API calls from the compromised user.
aws_isolate_ec2(instance_id, forensic_sg)
Changes an EC2 instance's security groups to a forensic isolation group that denies all traffic.
aws_snapshot_ebs(instance_id)
Creates forensic snapshots of all EBS volumes attached to the compromised instance.
aws_query_cloudtrail(username, hours_back)
Queries CloudTrail for all API events made by the compromised identity, parsing source IP, user agent, and resources.
aws_list_attacker_resources(username, events)
Filters CloudTrail events for resource creation actions (Create*, Run*, Put*, Attach*).
aws_check_all_regions_instances()
Scans all AWS regions for running EC2 instances to detect crypto-mining deployments.
AWS CLI Commands Used
| Command | Purpose |
|---|---|
aws iam update-access-key |
Disable access key |
aws iam attach-user-policy |
Attach deny-all policy |
aws ec2 modify-instance-attribute |
Change security groups |
aws ec2 create-snapshot |
Capture EBS volume snapshot |
aws cloudtrail lookup-events |
Query API audit trail |
aws ec2 describe-regions |
List all regions |
Scripts 1
agent.py7.4 KB
#!/usr/bin/env python3
"""Cloud Incident Response Agent - Automates AWS/Azure cloud IR containment and evidence collection."""
import json
import logging
import argparse
import subprocess
from datetime import datetime, timedelta
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
def aws_disable_access_key(username, access_key_id):
"""Disable a compromised IAM access key via AWS CLI."""
cmd = [
"aws", "iam", "update-access-key",
"--user-name", username,
"--access-key-id", access_key_id,
"--status", "Inactive",
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
if result.returncode == 0:
logger.info("Disabled access key %s for user %s", access_key_id, username)
else:
logger.error("Failed to disable key: %s", result.stderr)
return result.returncode == 0
def aws_attach_deny_all(username):
"""Attach AWSDenyAll policy to a compromised IAM user."""
cmd = [
"aws", "iam", "attach-user-policy",
"--user-name", username,
"--policy-arn", "arn:aws:iam::aws:policy/AWSDenyAll",
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
if result.returncode == 0:
logger.info("Attached AWSDenyAll to user %s", username)
return result.returncode == 0
def aws_isolate_ec2(instance_id, forensic_sg):
"""Isolate an EC2 instance by changing its security group to forensic isolation SG."""
cmd = [
"aws", "ec2", "modify-instance-attribute",
"--instance-id", instance_id,
"--groups", forensic_sg,
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
if result.returncode == 0:
logger.info("Isolated EC2 %s with security group %s", instance_id, forensic_sg)
return result.returncode == 0
def aws_snapshot_ebs(instance_id):
"""Create EBS snapshots of all volumes attached to an EC2 instance."""
cmd = [
"aws", "ec2", "describe-volumes",
"--filters", f"Name=attachment.instance-id,Values={instance_id}",
"--query", "Volumes[*].VolumeId",
"--output", "text",
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
volume_ids = result.stdout.strip().split()
snapshots = []
for vol_id in volume_ids:
snap_cmd = [
"aws", "ec2", "create-snapshot",
"--volume-id", vol_id,
"--description", f"IR evidence - {instance_id} - {datetime.utcnow().isoformat()}",
]
snap_result = subprocess.run(snap_cmd, capture_output=True, text=True, timeout=120)
if snap_result.returncode == 0:
snap_data = json.loads(snap_result.stdout)
snapshots.append(snap_data.get("SnapshotId"))
logger.info("Created snapshot %s for volume %s", snap_data.get("SnapshotId"), vol_id)
return snapshots
def aws_query_cloudtrail(username, hours_back=24):
"""Query CloudTrail for API calls made by a specific IAM user."""
start_time = (datetime.utcnow() - timedelta(hours=hours_back)).strftime("%Y-%m-%dT%H:%M:%SZ")
cmd = [
"aws", "cloudtrail", "lookup-events",
"--lookup-attributes", f"AttributeKey=Username,AttributeValue={username}",
"--start-time", start_time,
"--output", "json",
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
if result.returncode == 0:
events = json.loads(result.stdout).get("Events", [])
logger.info("CloudTrail: %d events for user %s in last %d hours", len(events), username, hours_back)
parsed = []
for event in events:
ct_event = json.loads(event.get("CloudTrailEvent", "{}"))
parsed.append({
"time": event.get("EventTime", ""),
"event_name": event.get("EventName", ""),
"source_ip": ct_event.get("sourceIPAddress", ""),
"user_agent": ct_event.get("userAgent", ""),
"resources": event.get("Resources", []),
})
return parsed
return []
def aws_list_attacker_resources(username, events):
"""Identify resources created by the attacker from CloudTrail events."""
create_events = [
e for e in events
if e["event_name"].startswith(("Create", "Run", "Put", "Attach"))
]
logger.info("Identified %d resource creation events", len(create_events))
return create_events
def aws_check_all_regions_instances():
"""Check all AWS regions for unauthorized EC2 instances."""
cmd = ["aws", "ec2", "describe-regions", "--query", "Regions[*].RegionName", "--output", "text"]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
regions = result.stdout.strip().split()
all_instances = {}
for region in regions:
cmd = [
"aws", "ec2", "describe-instances",
"--region", region,
"--query", "Reservations[*].Instances[*].[InstanceId,InstanceType,State.Name]",
"--output", "json",
]
r = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
if r.returncode == 0:
instances = json.loads(r.stdout)
running = [i for reservation in instances for i in reservation if i[2] == "running"]
if running:
all_instances[region] = running
logger.info("Found instances in %d regions", len(all_instances))
return all_instances
def generate_ir_report(incident_id, username, events, snapshots, containment_actions):
"""Generate a cloud incident response report."""
report = {
"incident_id": incident_id,
"timestamp": datetime.utcnow().isoformat(),
"compromised_identity": username,
"cloudtrail_events": len(events),
"evidence_snapshots": snapshots,
"containment_actions": containment_actions,
"attacker_activity": events[:20],
}
print(json.dumps(report, indent=2))
return report
def main():
parser = argparse.ArgumentParser(description="Cloud Incident Response Agent")
parser.add_argument("--incident-id", required=True, help="Incident ID")
parser.add_argument("--username", required=True, help="Compromised IAM username")
parser.add_argument("--access-key-id", help="Compromised access key to disable")
parser.add_argument("--instance-id", help="EC2 instance to isolate")
parser.add_argument("--forensic-sg", default="sg-forensic-isolate", help="Forensic SG ID")
parser.add_argument("--output", default="cloud_ir_report.json")
args = parser.parse_args()
containment = []
if args.access_key_id:
aws_disable_access_key(args.username, args.access_key_id)
containment.append(f"Disabled access key {args.access_key_id}")
aws_attach_deny_all(args.username)
containment.append(f"Attached AWSDenyAll to {args.username}")
snapshots = []
if args.instance_id:
aws_isolate_ec2(args.instance_id, args.forensic_sg)
containment.append(f"Isolated EC2 {args.instance_id}")
snapshots = aws_snapshot_ebs(args.instance_id)
events = aws_query_cloudtrail(args.username, hours_back=72)
attacker_actions = aws_list_attacker_resources(args.username, events)
report = generate_ir_report(args.incident_id, args.username, events, snapshots, containment)
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
logger.info("IR report saved to %s", args.output)
if __name__ == "__main__":
main()