
📌 Exposure Overview
This post covers a widely overlooked information leakage behavior across cloud storage providers — particularly AWS S3 and GCP Cloud Storage — where “Access Denied” error messages still return traceable metadata. These leaks are not CVEs, but they should be considered recon-surface failures by any defender worth their salt.
When probing private or misconfigured buckets, the following data is frequently returned in the HTTP response or XML error body, even when access is correctly denied:
AccountId– the unique cloud account that owns the bucketRequestId– an operation-level UUID, often logged internallyHostId– internal routing metadata, which can reveal infrastructure regions or nodes
Attack Vector and Scope
- Vector: Remote unauthenticated access via HTTP GET or HEAD
- Scope: Any public-facing or guessable cloud bucket endpoint
- Privileges Required: None
- User Interaction: None
🔬 Exploitation Detail
Here’s how it plays out:
- An attacker sends an HTTP GET or HEAD request to a guessed or discovered bucket URL
- The cloud service responds with a 403 Access Denied error
- Despite the denial, metadata is embedded in the response body or headers
Sample Response
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<AccountId>REDACTED-ACCOUNT-ID</AccountId>
<RequestId>REDACTED-REQUEST-ID</RequestId>
<HostId>REDACTED-HOST-ID</HostId>
</Error>
This data may seem harmless at first glance, but:
- It allows enumeration of valid bucket names and resource presence
- It leaks account or infrastructure identifiers
- It gives threat actors a way to trace actions across cloud services, logs, and misconfigured systems
📎 Attacker Behavior Snapshot
- What the attacker sends: GET or HEAD to
https://bucket-name.s3.amazonaws.com/ - What the system does: Returns 403 + XML body with metadata
- What comes back that shouldn’t:
AccountId,HostId,RequestId
🧪 YARA Rule
rule Exposed_CloudBucket_Metadata
{
strings:
$access_denied = "AccessDenied"
$request_id = /<RequestId>[A-Za-z0-9+\/=]+<\/RequestId>/
$host_id = /<HostId>[A-Za-z0-9+\/=]+<\/HostId>/
$account_id = /<AccountId>[A-Za-z0-9\-]+<\/AccountId>/
condition:
all of them
}
🌐 Suricata Rule
alert http any any -> any any (
msg:"Potential Cloud Bucket Metadata Leak";
content:"AccessDenied";
http_client_body;
pcre:"/<RequestId>[A-Za-z0-9+\/=]+<\/RequestId>/";
classtype:attempted-recon;
sid:2025091201;
rev:1;
)
⚡ Sigma Rule
title: Cloud Storage Bucket Metadata Leak
logsource:
category: webserver
detection:
selection:
EventID: 403
Message|contains:
- "Access Denied"
- "RequestId"
- "HostId"
- "AccountId"
condition: selection
level: medium
📊 Splunk Query
index=web OR index=proxy
status=403
("Access Denied" AND ("RequestId" OR "HostId" OR "AccountId"))
| stats count by uri_path, src_ip, user_agent
🛠️ SOC Detection Strategy
Tier 1–3 Triage Recommendations
- Tier 1: Monitor external traffic hitting known cloud bucket URLs with 403s
- Tier 2: Correlate source IPs and user agents for mass enumeration or scanning
- Tier 3: Tie
HostIdto backend infrastructure or leaked internal topology
Log Sources
- AWS CloudTrail or GCP Audit Logs
- Web server logs (Apache, Nginx, CloudFront, etc.)
- Firewall and proxy logs
🔐 Hardening & Mitigation
- Enforce “Block Public Access” or equivalent on all cloud storage buckets
- Use explicit deny policies on IAM roles and bucket permissions
- Mask or suppress metadata in error responses using API Gateway or HTTP header manipulation
- Enable audit logging to catch repeated probes on denied endpoints
📋 Incident Response Snippets
- IR Hunt Query:
index=web "Access Denied" "RequestId" "HostId" "AccountId" src_ip!=internal - IR Questions: Was the bucket ever public? Is the
AccountIdreused across other buckets or services? - Indicators to Hunt: Leaked
HostIdvalues, repeated enumeration attempts, unusual User-Agent strings - Cleanup: Rotate bucket names, review IAM policy scope, inspect logs for related enum attempts
📚 Suggested Reading & External References
🧾 Final Thoughts
This isn’t a zero-day — it’s a zero-effort leak that most orgs never see coming. “Access Denied” isn’t the end of the conversation. It’s often just the start of the metadata breadcrumb trail. HostId values can fingerprint regions. AccountId values can correlate services. RequestId values can be replayed or abused in other AWS services.
If your cloud bucket returns anything other than a dead silent 403 — you’ve got a recon hole. Seal it. Log it. Monitor it.
Published: September 12, 2025
Leave a comment