Skip to main content
emnode / learn
Compliance High severity

AWS Security Hub · CloudFront

CloudFront.12: A distribution points at a non-existent S3 origin (takeover risk)

Written and reviewed by Emnode · Last reviewed

What does AWS Security Hub CloudFront.12 check?

CloudFront.12 checks whether a distribution's S3 origin points to a bucket that still exists. It reports FAILED when the referenced bucket is gone — a periodic check that catches origins left dangling long after the distribution last changed. It scopes to plain S3 REST-endpoint origins, not buckets configured for static website hosting.

Why does CloudFront.12 matter?

S3 bucket names are globally unique and released back to the pool the moment a bucket is deleted. An attacker who re-creates the orphaned name in their own account now controls what your distribution serves on your trusted domain — your URL, your TLS certificate, your brand — hosting a phishing page, defacement, or malware. The takeover is silent and trivially automated, which is why it's rated High.

How do I fix CloudFront.12?

  1. Confirm the origin is genuinely dangling: extract the bucket name and run `aws s3api head-bucket --bucket <name>` — a 404 is the failing condition, a 403 means it exists but you may not own it.
  2. Close the exposure immediately — if the distribution must stay, re-create the bucket under your own account to reclaim the name; otherwise remove the dangling origin or disable and delete the distribution.
  3. If repointing, update the origin `DomainName` to the correct bucket via the ETag-guarded `update-distribution` flow and re-attach origin access control.
  4. Keep the `cloudfront-s3-origin-non-existent-bucket` Config rule enabled and order teardown so deleting a bucket can't leave a live origin pointing at nothing.

Remediation script · bash

# 1. Lock an S3 origin: only THIS distribution may read the bucket.
cat > bucket-policy.json <<'JSON'
{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "AllowCloudFrontServicePrincipalReadOnly",
    "Effect": "Allow",
    "Principal": { "Service": "cloudfront.amazonaws.com" },
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::my-downloads-bucket/*",
    "Condition": { "StringEquals": {
      "AWS:SourceArn": "arn:aws:cloudfront::111122223333:distribution/E2QWRUHAPOMQZL"
    } }
  }]
}
JSON
aws s3api put-bucket-policy --bucket my-downloads-bucket --policy file://bucket-policy.json

# 2. Raise the minimum TLS version (the field is nested in ViewerCertificate, so send the whole config back).
aws cloudfront get-distribution-config --id E2QWRUHAPOMQZL > dist.json
ETAG=$(python3 -c "import json;print(json.load(open('dist.json'))['ETag'])")
# ... edit dist.json: ViewerCertificate.MinimumProtocolVersion = TLSv1.2_2021, ViewerProtocolPolicy = redirect-to-https ...
aws cloudfront update-distribution --id E2QWRUHAPOMQZL \
  --distribution-config file://distribution-config.json --if-match "$ETAG"

# 3. Confirm the second door is shut: a raw S3 GET should now return 403.
curl -s -o /dev/null -w '%{http_code}\n' https://my-downloads-bucket.s3.amazonaws.com/file.pdf

Full walkthrough (console steps, edge cases and verification) in the lesson Protect CloudFront distributions and origins.

Part of the learning path Lock down access