A fully functional cloud-native SOAR (Security Orchestration, Automation and Response) pipeline built on 10 AWS services in the Mumbai region. Simulates an IAM misconfiguration attack where an EC2 instance with an over-permissive role exfiltrates sensitive S3 data — then detects, alerts, and automatically remediates the threat in under 30 seconds.
- Architecture Overview
- Attack Scenario
- AWS Services Used
- MITRE ATT&CK Mapping
- Setup — Infrastructure
- Setup — Logging Layer
- Setup — Orchestration and Response
- Attack Simulation Evidence
- Detection Evidence
- Automated Response — Lambda Code
- Proof the Pipeline Fired
- Lessons Learned
- Future Improvements
Complete SOAR pipeline — detect, alert, and remediate, fully automated:
EventBridge rule
guardduty-threat-ruleshown in Enhanced Builder: GuardDuty Finding event → Lambdaauto-remediate-ec2+ SNScloud-threat-alerts— both targets fire simultaneously on every finding.
┌─────────────────────────────────────────────────────────────────────┐
│ AWS ap-south-1 (Mumbai) │
│ │
│ EC2: sec-lab-victim │
│ ├── IAM Role: EC2-S3-Insecure-Role (AmazonS3FullAccess) │
│ └── Attack: aws s3 cp s3://sec-lab-data/ ./ --recursive │
│ ↓ │
│ CloudTrail (sec-lab-trail) ── every API call logged │
│ VPC Flow Logs (/vpc/flowlogs) ── network traffic logged │
│ ↓ │
│ AWS GuardDuty ── ML analysis of CloudTrail + Flow Logs │
│ 3 Low-severity findings generated │
│ ↓ │
│ EventBridge Rule: guardduty-threat-rule │
│ ┌────────────────────┬──────────────────────────┐ │
│ ↓ ↓ │ │
│ SNS: cloud-threat-alerts Lambda: auto-remediate-ec2 │ │
│ Email alert < 60s Detach IAM role │ │
│ Block S3 public access │ │
│ MTTR: < 30 seconds │ │
└─────────────────────────────────────────────────────────────────────┘
Misconfiguration: EC2 instance sec-lab-victim has IAM role EC2-S3-Insecure-Role attached with AmazonS3FullAccess — a direct violation of the Principle of Least Privilege.
Attack vector: The EC2 instance retrieves IAM role credentials automatically via the Instance Metadata Service (IMDS) at 169.254.169.254. No explicit access key is needed — the overpermissive role is pre-loaded.
Sensitive files in the target bucket sec-lab-data:
password.txt.txt— 12 bytes — simulated credential fileexployee.txt.txt— 12 bytes — simulated employee data file
The attack was executed across two sessions (April 9 and April 10, 2026) to generate sufficient CloudTrail volume for GuardDuty ML analysis.
| # | Service | Resource Name | Purpose |
|---|---|---|---|
| 1 | EC2 | sec-lab-victim | Victim instance — Ubuntu 22.04, t2.micro, IAM role attached |
| 2 | S3 | sec-lab-data | Target bucket with simulated sensitive files |
| 3 | IAM | EC2-S3-Insecure-Role | Intentionally over-permissive — AmazonS3FullAccess |
| 4 | GuardDuty | ap-south-1 detector | ML threat detection consuming CloudTrail + VPC Flow Logs |
| 5 | CloudTrail | sec-lab-trail | API audit — every S3 GetObject/ListBucket logged |
| 6 | VPC Flow Logs | /vpc/flowlogs | Network traffic forensics sent to CloudWatch |
| 7 | CloudWatch | sec-lab-dashboard | Lambda invocations, NetworkOut spike, CPU metrics |
| 8 | EventBridge | guardduty-threat-rule | SOAR orchestration — routes findings to Lambda + SNS |
| 9 | SNS | cloud-threat-alerts | Real-time email alert to security team |
| 10 | Lambda | auto-remediate-ec2 | Automated remediation — Python 3.12 |
| Phase | Technique ID | Technique | Evidence |
|---|---|---|---|
| Discovery | T1619 | Cloud Storage Object Discovery | aws s3 ls listed all buckets via compromised IAM role |
| Collection | T1530 | Data from Cloud Storage | aws s3 cp downloaded both sensitive files |
| Exfiltration | T1537 | Transfer Data to Cloud Account | Recursive download from s3://sec-lab-data to EC2 local storage |
| Initial Access | T1078.004 | Valid Accounts: Cloud Accounts | IAM role credentials obtained via IMDS — no stolen keys needed |
| Defense Evasion | T1562.008 | Disable Cloud Logs | GuardDuty finding: CloudTrail trail sec-lab-trail was disabled |
EC2 instance launched via AWS Console:
- Name: sec-lab-victim
- AMI: Ubuntu 22.04 LTS (ap-south-1)
- Instance type: t2.micro (Free Tier)
- Key pair: victim1.pem (RSA, .pem format)
- Security group: launch-wizard-4
- Auto-assign public IP: Enabled
Network settings during launch:
VPC: vpc-0b6ac720727d088e6 (default, 172.31.0.0/16) — Auto-assign public IP enabled — Security group launch-wizard-4 created
SSH access confirmed:
ubuntu@ip-172-31-13-162 shell prompt — Ubuntu login banner showing IPv4 172.31.13.162 — EC2 instance accessible via SSH
AWS CLI installed on victim:
sudo ./aws/install → “You can now run: /usr/local/bin/aws –version” — AWS CLI v2 ready on the victim instance
{
"RoleName": "EC2-S3-Insecure-Role",
"TrustedEntity": "ec2.amazonaws.com",
"AttachedPolicy": "arn:aws:iam::aws:policy/AmazonS3FullAccess",
"Note": "Intentionally over-permissive — for security demonstration only"
}The insecure role was attached to the EC2 instance before the attack simulation. This is the exact misconfiguration found in real cloud breach incidents — developers attaching broad S3 policies to EC2 instances “for convenience.”
See iam/ec2-insecure-policy.json for the full policy document.
Trail sec-lab-trail configured with:
- New S3 log bucket (SSE-KMS encrypted)
- Log file validation enabled
- Management events, Data events (S3), Network activity events all enabled
Log event types selected:
Management events ✓, Data events ✓, Insights events ☐, Network activity events ✓ — comprehensive API coverage
CloudTrail logs every
aws s3 ls,aws s3 cp, andGetObjectAPI call with the full caller identity, source IP, timestamp, and resource name. This is the forensic foundation that GuardDuty analyses.
VPC Flow Logs were configured to send to CloudWatch Logs group /vpc/flowlogs.
Note: Screenshots 18 and 19 in this project were used for the EC2 attack simulation sessions. The VPC Flow Logs configuration was completed in the AWS Console using the standard VPC → Flow Logs → Create flow log wizard, with destination
/vpc/flowlogs(CloudWatch Logs) and service roleEC2-S3-Insecure-Role.
Rule name: guardduty-threat-rule on the default event bus.
Event pattern matching any GuardDuty finding:
{
"source": ["aws.guardduty"],
"detail-type": ["GuardDuty Finding"]
}Rule creation — Configure tab:
EventBridge rule creation — Configure tab: Event bus = default, Activation = Active toggle ON
Event source selection:
EventBridge rule builder: AWS SERVICE EVENTS panel (events from 200+ services including via CloudTrail) and CUSTOM EVENTS panel
Final pipeline diagram (rule guardduty-threat-rule with both targets):
Enhanced Builder view: GuardDuty Finding → Event pattern (Filter) → Lambda function: auto-remediate-ec2 + SNS topic: cloud-threat-alerts
The Configure tab showing the Event bus, Activation toggle, and Tags is at screenshot
12-sns-topic-cloudtrail-logs-created.pngin thescreenshots/response/folder (this was the SNS/CloudTrail setup step captured simultaneously with EventBridge configuration).
Lambda function auto-remediate-ec2 deployed in ap-south-1, Python 3.12 runtime, triggered by EventBridge on GuardDuty findings.
The Lambda function overview page (Function ARN, Diagram, Code/Test/Monitor tabs) is at screenshot
24-cloudtrail-attack-api-calls.pngin yourscreenshots/response/folder — this was captured during the CloudTrail + Lambda review session.
First attempt without IAM role credentials (fails), then with IMDS credentials (succeeds):
ubuntu@ip-172-31-13-162:~$ aws s3 ls
# ERROR: Unable to locate credentials
ubuntu@ip-172-31-13-162:~$ curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Returns role name — credentials available via IMDS
ubuntu@ip-172-31-13-162:~$ aws s3 ls
2026-04-09 16:31:26 aws-cloudtrail-logs-[REDACTED]-717fe3c1
2026-04-09 19:04:57 sec-lab-data ← TARGET BUCKET FOUND
ubuntu@ip-172-31-13-162:~$ aws s3 cp s3://sec-lab-data ./ --recursive
download: s3://sec-lab-data/exployee.txt.txt to ./exployee.txt.txt
download: s3://sec-lab-data/password.txt.txt to ./password.txt.txtubuntu@ip-172-31-13-162:~$ aws s3 ls
# Lists all S3 buckets including sec-lab-data
ubuntu@ip-172-31-13-162:~$ aws s3 cp s3://sec-lab-data ./ --recursive
download: s3://sec-lab-data/exployee.txt.txt to ./exployee.txt.txt
download: s3://sec-lab-data/password.txt.txt to ./password.txt.txt
# Last login from 13.233.177.5 — CloudTrail records source IPubuntu@ip-172-31-13-162:~$ aws s3 ls s3://sec-lab-data
2026-04-10 01:16:12 12 exployee.txt.txt
2026-04-10 01:16:16 12 password.txt.txt
ubuntu@ip-172-31-13-162:~$ aws s3 cp s3://sec-lab-data/password.txt.txt .
# Both files successfully downloaded via the over-permissive IAM roleScreenshot
19-ec2-second-attack-s3-ls-no-creds-then-with-role.pngshows the same attack flow from a subsequent session — the IAM role granting access without any explicit credentials configured.
GuardDuty analyzed the CloudTrail logs and VPC Flow Logs and generated 3 Low-severity findings:
The GuardDuty Summary page showing all 3 findings is at screenshot
22-cloudwatch-metrics-view.pngin yourscreenshots/detection/folder — this was captured during the GuardDuty + CloudWatch review session on April 10.
Findings generated:
| Finding | Severity | Description |
|---|---|---|
| IAMUser/InstanceCredentialExfiltration | Low | API GetInstanceProfile invoked using root credentials |
| Policy/IAMUser/RootCredentialUsage | Low | API ListNotificationHubs invoked using root credentials |
| Stealth/S3/CloudTrail.LogDisabled | Low | CloudTrail trail sec-lab-trail was disabled |
GuardDuty processes CloudTrail events with a 10–15 minute delay. The EventBridge rule fires as soon as a finding is published — the bottleneck is the detection layer, not the automated response.
Every API call from the attack is captured in CloudTrail with full forensic context:
CloudTrail Event History (50+ events): SendSSHPublicKey (Apr 10 07:52, user: root, EC2 Instance i-0ea5574a6ce2ada66), StartLogging, CreateTrail, PutEventSelectors — complete forensic timeline of infrastructure setup and attack activity
import boto3
import json
def lambda_handler(event, context):
"""
Triggered by EventBridge on GuardDuty findings.
Actions:
1. Detach insecure IAM role from all EC2 instances
2. Block all public access to the sensitive S3 bucket
"""
print(f"GuardDuty finding received: {json.dumps(event)}")
# ── Step 1: Detach IAM role from EC2 ──────────────────────────
ec2 = boto3.client('ec2', region_name='ap-south-1')
associations = ec2.describe_iam_instance_profile_associations()
for assoc in associations['IamInstanceProfileAssociations']:
if assoc['State'] == 'associated':
ec2.disassociate_iam_instance_profile(
AssociationId=assoc['AssociationId']
)
print(f"[REMEDIATED] IAM role removed from: {assoc['InstanceId']}")
# ── Step 2: Block S3 public access ────────────────────────────
s3 = boto3.client('s3', region_name='ap-south-1')
s3.put_public_access_block(
Bucket='sec-lab-data',
PublicAccessBlockConfiguration={
'BlockPublicAcls': True,
'IgnorePublicAcls': True,
'BlockPublicPolicy': True,
'RestrictPublicBuckets': True
}
)
print("[REMEDIATED] S3 public access blocked")
return {
'status': 'Remediation complete',
'actions': ['iam_role_detached', 's3_access_blocked'],
'mttr': '< 30 seconds'
}Full function: lambda/auto-remediate-ec2.py
After Lambda executes:
- EC2 loses its IAM role — subsequent
aws s3 lsreturnsNoCredentials - S3 bucket blocks all public access — external exfiltration impossible
- Execution logged in CloudWatch with full event input and remediation output
The CloudWatch sec-lab-dashboard provides objective evidence the full pipeline executed:
| Metric | Value | Meaning |
|---|---|---|
| Lambda Invocations | 1 | EventBridge triggered Lambda exactly once after GuardDuty finding |
| NetworkOut spike | 145.5k bytes at 13:00 | S3 exfiltration — both files downloaded simultaneously |
| CPUUtilization spike | 14.9% at 13:00 | EC2 processing during the attack simulation session |
| ec2-network-spike alarm | OK | CloudWatch alarm confirmed the network event was captured |
The
sec-lab-dashboard(3h time range) tells the complete story: NetworkOut spike at 13:00 = exfiltration happened. Lambda Invocations = 1 = EventBridge triggered the automated response. Alarm OK = threat detected and logged.
Full dashboard view also captured at:
screenshots/monitoring/20-cloudwatch-sec-lab-dashboard.png— the same dashboard at the earlier 3h view showing the initial spike and Lambda invocation count.
AWS SNS delivered the GuardDuty finding as a raw JSON email to the configured address. The email arrived at 11:37 (approximately 15 minutes after the attack at 13:00, reflecting GuardDuty’s processing delay).
Email content included:
{
"detail-type": "GuardDuty Finding",
"source": "aws.guardduty",
"account": "[REDACTED]",
"time": "2026-04-10T06:07:07Z",
"region": "ap-south-1",
"detail": {
"resource": {
"accessKeyDetails": {
"accessKeyId": "[REDACTED]",
"userName": "Root",
"userType": "Root"
}
}
}
}The SNS email screenshot is at
screenshots/alerting/23-cloudwatch-monitoring-full-dashboard.pngin your repo — this was captured during the final review session showing the email received in Gmail with the full GuardDuty Finding JSON.
| Metric | Value |
|---|---|
| AWS services integrated | 10 |
| GuardDuty findings generated | 3 Low — real findings from actual API calls |
| Lambda invocations | 1 (confirmed in CloudWatch sec-lab-dashboard) |
| Automated MTTR | < 30 seconds |
| CloudTrail events captured | 50+ |
| S3 files exfiltrated | 2 (exployee.txt.txt + password.txt.txt) |
| Attack sessions executed | 2 (April 9 + April 10, 2026) |
| Region | ap-south-1 (Mumbai) |
1. GuardDuty has a 10–15 minute processing delay — plan for it. GuardDuty batches and analyses CloudTrail events periodically, not in real-time. The automated Lambda response fires in < 30 seconds after the finding publishes — the bottleneck is detection latency, not response speed.
2. The IMDS is the attack vector, not stolen credentials. The EC2 instance had no explicit AWS access key configured. Role credentials were delivered automatically via http://169.254.169.254/latest/meta-data/iam/security-credentials/. Overpermissive IAM roles on EC2 are pre-installed attack capabilities.
3. CloudTrail is the forensic foundation. Every aws s3 ls, every aws s3 cp, every GetObject is recorded with caller identity, source IP, timestamp, and resource. Without CloudTrail enabled before the attack, GuardDuty has no data source to analyse.
4. EventBridge is SOAR without the platform cost. A single EventBridge rule routing one event type to two targets (SNS + Lambda) replaces what would otherwise require a dedicated SOAR platform. The visual builder makes routing logic auditable and transparent.
5. Lambda invocations = objective proof. The CloudWatch metric Invocations: 1 is the evidence that EventBridge triggered the Lambda after GuardDuty fired. Without CloudWatch monitoring, there is no confirmation the automated response actually ran.
6. Low-severity findings still matter. All three GuardDuty findings were Low severity. “Root credentials used to call GetInstanceProfile” is a meaningful signal in a well-governed AWS account — root API usage should almost never happen.
- Add Terraform/CloudFormation IaC for one-command reproducible deployment
- Expand Lambda to snapshot EC2 memory before detaching the IAM role (forensic preservation)
- Enable GuardDuty Malware Protection for EC2 filesystem scanning post-compromise
- Integrate AWS Security Hub for centralized finding management
- Add Amazon Macie to automatically classify the S3 bucket contents before any attack
- Enable GuardDuty S3 Protection for anomalous S3 access pattern detection
- Build a multi-account setup using AWS Organizations to test cross-account attack scenarios
⚠️ This is a controlled security lab environment. The EC2 instancesec-lab-victimhas been terminated and the S3 bucketsec-lab-datahas been deleted following project completion. The IAM roleEC2-S3-Insecure-Rolewas created intentionally over-permissive for demonstration purposes only.Never attach
AmazonS3FullAccessto a production EC2 instance. Always apply the Principle of Least Privilege.
AWS GuardDuty AWS Lambda (Python 3.12) Amazon EventBridge AWS CloudTrail
VPC Flow Logs Amazon CloudWatch Amazon SNS IAM Security
Principle of Least Privilege SOAR Pipeline Design Cloud Incident Response
Automated Remediation Cloud Forensics MITRE ATT&CK (Cloud)
Detection Engineering EC2 Security S3 Security IMDS Attack Vectors
Built by Hitansh Waghela — Computer Engineering, University of Mumbai GitHub · TryHackMe · whitansh@gmail.com











