Add src/csp-perms-audit.sh

This commit is contained in:
colin 2024-11-01 16:55:00 -04:00
parent da76544520
commit bbb24b77a6
1 changed files with 105 additions and 0 deletions

105
src/csp-perms-audit.sh Normal file
View File

@ -0,0 +1,105 @@
# Function to audit CSP, Permissions Policy, and secure headers for a given URL.
csp_perms_audit() {
URL=$1
if [ -z "$URL" ]; then
echo "Usage: csp_perms_audit <URL>"
return 1
fi
# Generate a nonce for CSP policies
NONCE=$(openssl rand -base64 12)
# Instructions and example Traefik labels for setting CSP, Permissions Policy, and secure headers.
echo "=== Instructions ==="
echo "Use the following analysis to create a Traefik label-based Content-Security-Policy (CSP), Permissions Policy, and a set of secure headers:"
echo "- This CSP includes a dynamically generated nonce ($NONCE). Apply the same nonce to each inline script to allow it to execute."
echo "- 'unsafe-inline' is added to support older browsers that do not recognize nonces or hashes. Modern browsers will ignore it if a nonce or hash is present."
echo "- If you prefer hashes instead of nonces, precompute SHA-256 hashes for each inline script's content."
echo "- Remember: Traefik labels do not support multiline YAML syntax, so ensure the CSP, Permissions Policy, and secure headers are formatted as single-line strings."
echo "\nImportant: Each service should have a unique `secure-headers` middleware to avoid conflicts. Use a specific name for each service's `secure-headers` middleware, like `serviceA-secure-headers`, to ensure settings are applied uniquely to that service."
echo "\nExample Traefik labels for your Docker Compose or Traefik configuration:\n"
echo "# Content-Security-Policy (CSP) Label"
echo "traefik.http.middlewares.csp-headers.headers.customResponseHeaders.Content-Security-Policy: \"default-src 'self'; script-src 'self' 'strict-dynamic' 'nonce-$NONCE' 'unsafe-inline'; style-src 'self' https://fonts.googleapis.com 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; font-src 'self' https://use.typekit.net; frame-src 'none'; base-uri 'none'; object-src 'none'; require-trusted-types-for 'script';\""
echo "\n# Permissions-Policy Label"
echo "traefik.http.middlewares.permissions-policy.headers.customResponseHeaders.Permissions-Policy: \"geolocation=(), microphone=(), camera=(), payment=(), usb=()\""
echo "\n# Additional Secure Headers (ensure middleware is unique for each service)"
echo "traefik.http.middlewares.unique-secure-headers.headers.customResponseHeaders.X-Frame-Options: \"DENY\""
echo "traefik.http.middlewares.unique-secure-headers.headers.customResponseHeaders.X-Content-Type-Options: \"nosniff\""
echo "traefik.http.middlewares.unique-secure-headers.headers.customResponseHeaders.X-XSS-Protection: \"1; mode=block\""
echo "traefik.http.middlewares.unique-secure-headers.headers.customResponseHeaders.Referrer-Policy: \"no-referrer\""
echo "traefik.http.middlewares.unique-secure-headers.headers.customResponseHeaders.Strict-Transport-Security: \"max-age=63072000; includeSubDomains; preload\""
echo "\n=== Checking Headers for Permissions and Relevant Policies on $URL ==="
# Fetch headers and parse for permissions policies
curl -s -D - "$URL" -o /dev/null | grep -i "feature-policy\|permissions-policy" || echo "No relevant headers found."
# Extract embedded resources (scripts, stylesheets, images, frames)
echo "\n=== Embedded Resources (Scripts, Stylesheets, Images, Frames) ==="
embedded_resources=$(curl -s "$URL" | grep -Eo 'src="([^"]+)"|href="([^"]+)|url\(([^)]+)\)' | sed -E 's/src=|href=|url\(|\)|"//g')
for resource in $embedded_resources; do
echo "$resource"
done
# Checking accessibility of external resources
echo "\n=== Checking External Resources Accessibility ==="
check_resource_accessibility() {
resource=$1
# Perform a HEAD request and check for 200, 301, 302, 307, 308 status codes
if curl -s -o /dev/null -w "%{http_code}" --head "$resource" | grep -E "^(200|301|302|307|308)$" > /dev/null; then
echo "✔️ Accessible: $resource"
else
echo "❌ Inaccessible: $resource (This may break functionality)"
fi
}
# Recursive extraction for linked CSS files to capture additional resources
for css_url in $(echo "$embedded_resources" | grep -E "\.css$"); do
css_resources=$(curl -s "$css_url" | grep -Eo 'url\(([^)]+)\)' | sed -E 's/url\(|\)|"//g')
for css_resource in $css_resources; do
echo "$css_resource (from $css_url)"
external_resources="$external_resources $css_resource"
done
done
external_resources=$(echo "$embedded_resources" | grep -E "^https?://")
for resource in $external_resources; do
check_resource_accessibility "$resource"
done
# Check for inline scripts
echo "\n=== Checking for Inline Scripts ==="
inline_scripts=$(curl -s "$URL" | grep -i "<script>" | wc -l)
if [ "$inline_scripts" -gt 0 ]; then
echo "⚠️ Inline scripts detected. Consider securing them with nonces or hashes."
else
echo "✔️ No inline scripts detected (or already secured with nonces)."
fi
# Check for embedded iFrames
echo "\n=== Checking for Embedded iFrames ==="
iframes=$(curl -s "$URL" | grep -i "<iframe" | wc -l)
if [ "$iframes" -gt 0 ]; then
echo "⚠️ iFrames detected. Ensure 'X-Frame-Options: DENY' or 'frame-src' CSP is properly configured."
else
echo "✔️ No iFrames detected (or no conflict with 'X-Frame-Options: DENY')."
fi
# Check for mixed content
echo "\n=== Checking for Mixed Content ==="
mixed_content=$(curl -s "$URL" | grep -E "http://" | wc -l)
if [ "$mixed_content" -gt 0 ]; then
echo "⚠️ Mixed content detected. Ensure all resources are loaded over HTTPS."
else
echo "✔️ No mixed content detected."
fi
# Suggested default CSP to allow 'self' hosted assets
echo "\n=== Suggested CSP for 'self'-hosted Assets ==="
echo "default-src 'self'; script-src 'self'; style-src 'self' https://fonts.googleapis.com; img-src 'self' data:; connect-src 'self'; font-src 'self' https://use.typekit.net; frame-src 'none'; base-uri 'none'; object-src 'none'; require-trusted-types-for 'script';"
}