Skip to content

security: block SSRF in webhook subscriptions#2053

Open
createkr wants to merge 1 commit intoScottcjn:mainfrom
createkr:sec/issue2052-webhook-ssrf-hardening
Open

security: block SSRF in webhook subscriptions#2053
createkr wants to merge 1 commit intoScottcjn:mainfrom
createkr:sec/issue2052-webhook-ssrf-hardening

Conversation

@createkr
Copy link
Copy Markdown
Contributor

@createkr createkr commented Apr 4, 2026

Summary

This change hardens webhook subscriptions against server-side request forgery by validating subscriber destinations before they are stored and by disabling redirect following during delivery.

What changed

  • Added webhook URL validation for resolved destination addresses
  • Blocked loopback, RFC1918, link-local, CGNAT, and IPv6 internal ranges
  • Rejected unresolved or malformed destinations
  • Disabled redirect following in outbound webhook delivery requests

Why

The previous implementation accepted attacker-controlled http:// and https:// subscriber URLs and later issued server-side POST requests to them without checking whether they resolved to internal or reserved addresses. This allowed SSRF into protected network locations.

Scope

  • tools/webhooks/webhook_server.py only

Closes #2052

Payout Wallet

RTC1d48d848a5aa5ecf2c5f01aa5fb64837daaf2f35

@github-actions github-actions bot added BCOS-L1 Beacon Certified Open Source tier BCOS-L1 (required for non-doc PRs) size/M PR: 51-200 lines labels Apr 4, 2026
Copy link
Copy Markdown

@ukgorclawbot-stack ukgorclawbot-stack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good first step, but it still leaves a classic DNS-rebinding hole open.

Blocking issue: validate_webhook_url() resolves the hostname once at subscription time and rejects private/reserved IPs at that moment, but deliver_webhook() later sends requests.post(sub.url, ...) using the original hostname again. Because the resolved IP is not pinned and the destination is not revalidated at delivery time, an attacker can register a hostname that initially resolves to a public IP, pass validation, and then rebind DNS to an internal address before the server sends the webhook.

So the current flow is still bypassable:

  1. subscribe with https://attacker.example/webhook while it resolves publicly
  2. validation stores the raw URL
  3. attacker flips DNS to 127.0.0.1, 169.254.169.254, RFC1918, etc.
  4. deliver_webhook() resolves again implicitly inside requests.post() and the server-side POST goes to the new internal target

allow_redirects=False helps with redirect-based SSRF, but it doesn't address post-subscription DNS rebinding. To really close the SSRF bug, you need to either re-resolve and re-check on every delivery or pin the validated address and connect to that pinned IP instead of the mutable hostname.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

BCOS-L1 Beacon Certified Open Source tier BCOS-L1 (required for non-doc PRs) size/M PR: 51-200 lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security: webhook subscription endpoint allows SSRF to internal services

2 participants