Installs into .claude/skills of the current project.
Are you the author of Red Cloud Storage Exposure Testing?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/bitbytelabio-red-cloud-storage-exposure-testing)
---
name: red-cloud-storage-exposure-testing
description: Offensive storage exposure testing across AWS S3, Azure Blob/ADLS, and GCP Cloud Storage - resolves s3 public access state (Block Public Access, ACLs, signed URLs) before declaring exposure - covers — existence, listing, read, write, overwrite, delete, ACL/policy modification, and signed-URL…
domain: red
category: cloud-security
hide: true
license: Apache-2.0
metadata:
manifest_entry: src/corpus/red-cloud-storage-exposure-testing/SKILL.md
---
# Cloud Storage Exposure Testing (S3 / Azure Blob / GCS)
## Purpose
Test cloud storage for unauthorized access — externally (black-box) or with credentials — proving the exact permission level obtained: existence, listing, read, write, overwrite, delete, or policy modification. Never collapse these into a single "public bucket" status.
## Entry Conditions
- Candidate bucket/container names (from `cloud-attack-surface-discovery`, recon, or credentialed enumeration)
- Anonymous context always; authenticated-low-priv and discovered-credential contexts where available
- Credentialed mode: `s3:GetBucketPolicy`-class read access to evaluate effective configuration
## Discovery Signals
- Bucket hostnames in JS/CNAME/error messages
- Company/domain/project naming seeds (`<company>-backups`, `<domain>-assets`, `<project>-staging`)
- Presigned URLs (scope, expiry, and signature reveal bucket + often account ID)
- Credentialed: `s3api list-buckets`, storage account keys, `gsutil ls`
## Attack Model
Separate the permission levels and prove each independently:
```text
existence — does the resource exist at all?
listing — can the caller enumerate object keys?
read — can the caller retrieve object contents?
write — can the caller create objects?
overwrite — can the caller replace existing objects?
delete — can the caller remove objects?
ACL/policy mod — can the caller rewrite the access policy itself?
signed URLs — can the caller mint/extend/abuse presigned access?
cross-account — can another tenant/account principal reach it?
```
## Methodology
### Step 1: Existence and Anonymous Baseline (AWS)
```bash
# Existence: 404 vs AccessDenied (a 403 on anonymous GET means the bucket EXISTS)
curl -sk -o /dev/null -w "%{http_code}\n" https://s3.amazonaws.com/<bucket> # 404=absent, 403=exists-protected
curl -sk "https://s3.amazonaws.com/<bucket>/" | head -c 200 # 200 = anonymous LISTING
curl -sk "https://s3.amazonaws.com/<bucket>/known-object-key" # object-level read
aws s3 ls s3://<bucket> --no-sign-request # anonymous listing via CLI
```
### Step 2: Effective Access (not policy text)
Before declaring exposure, resolve the actual s3 public access state (Block Public Access, access points, object ACLs) end to end. A policy containing `"Principal": "*"` is only a CANDIDATE. Effective access can still be denied by:
- Block Public Access (account and bucket level)
- policy conditions (`aws:SourceIp`, `s3:authType-*`)
- access point policies
- object-level ACLs
- VPC endpoint policies
- organizational SCPs
Verify the account/bucket configuration directly when credentialed:
```bash
aws s3api get-public-access-block --bucket <bucket> # and account level
aws s3api get-bucket-policy --bucket <bucket>
```
Prove it: actually list and read. Conversely, `allAuthenticatedUsers` (GCS `allUsers`/`allAuthenticatedUsers`, Azure public container/SAS) is a candidate until a real object is retrieved with the anonymous/authenticated-low context.
### Step 3: Write/Delete Probes (where scope permits)
```bash
# Create a uniquely-named test object — never touch existing data
echo "arsenal-probe-$(date +%s)" > /tmp/probe.txt
aws s3 cp /tmp/probe.txt s3://<bucket>/arsenal-probe-test.txt --no-sign-request
aws s3api put-object --bucket <bucket> --key arsenal-probe-test.txt --body /tmp/probe.txt --no-sign-request
# Overwrite/delete probes only with explicit runtime authorization for state changes
```
### Step 4: Azure Blob
```bash
# Public access: container-level "blob"/"container" publicAccess
curl -sk -o /dev/null -w "%{http_code}\n" "https://<account>.blob.core.windows.net/<container>?restype=container&comp=list"
# SAS tokens: check expiry, permissions (sp=rwdl...), IP scope; try extension/abuse
# Credentialed: az storage container list --account-name <account>
```
### Step 5: GCP Cloud Storage
```bash
curl -sk "https://storage.googleapis.com/<bucket>" | head -c 200 # XML API anonymous listing
gsutil ls -L gs://<bucket> # if authenticated
# TestIamPermissions is a claim, not proof — follow with actual ls/cp
# Run enumeration BOTH anonymous AND with any Google account (allAuthenticatedUsers grants)
```
### Step 6: Credentialed Configuration Review
In credentialed mode, configuration evidence can itself be a finding when the assessment asks for misconfigurations: public policies without BPA, wildcard principals, over-broad SAS, disabled soft-delete on sensitive data. Distinguish these as configuration findings, not exploit-proven findings.
## Evidence Contract
```text
candidate bucket
→ anonymous request
→ actual private object returned / written / deleted (concrete object name + content class)
→ sensitivity/impact assessment (what data, how sensitive, how much)
→ report at the proven permission level
```
A bucket existing is not a vulnerability. A wildcard policy is not proof. Read/list/write must each be demonstrated. A write is only a finding with the probe object name recorded.
**NOT evidence**: 403 on anonymous GET (existence only), policy text with `Principal:"*"` (candidate), `allAuthenticatedUsers` mention (candidate), tool output (CloudStorage scan) without retrieval
## Common Misses
- Object-level ACLs granting read on a public-in-policy bucket (or vice versa)
- Access points (S3) with policies differing from the bucket policy
- Presigned URL leakage in JS/referers — often grants write or higher
- Azure SAS tokens with excessive permissions/long expiry
- GCS buckets granting `allAuthenticatedUsers` — missed when only anonymous tests ran
- Snapshot/AMI sharing (adjacent storage surfaces): `restorable-by-user-ids all` in credentialed mode
- Region-specific endpoints (s3.eu-west-1.amazonaws.com) when the global endpoint 404s
## False Positives / Non-Findings
- Public-by-design assets (marketing images, public releases) with no sensitive objects
- Existence-only signals (403s)
- Expired presigned URLs
- Buckets belonging to third parties that merely share a naming seed
## Arsenal Tool Strategy
- `terminal_execute` with aws CLI/az/gsutil/curl for direct evidence
- Python for seed-based candidate generation (bounded wordlists)
- GCPBucketBrute optional for anonymous+authenticated enumeration with custom keywords
- Every confirmed level → ledger with object name + sensitivity
## Specialist Handoffs
- Credentials found in bucket contents → `cloud-credential-abuse`
- Bucket serving a web app / origin for CDN → `cloud-attack-surface-discovery` attack model entry
- Credentialed configuration-only findings → configuration assessment mode section of the provider skill
## Stopping Rule
For each candidate: prove existence, then anonymous listing/read, then (where authorized) write. Stop candidate generation once target-derived seeds are exhausted and no new high-confidence names emerge. Do not enumerate unlimited global namespaces.