fix(webhook): block SSRF via IP normalization bypasses - #5786
Open
pacocartones wants to merge 1 commit into
Open
fix(webhook): block SSRF via IP normalization bypasses#5786pacocartones wants to merge 1 commit into
pacocartones wants to merge 1 commit into
Conversation
The webhook destination allowlist only recognized dotted-decimal IPv4 literals, so private/reserved targets could be reached through alternate encodings that new URL() and the underlying network stack still resolve: - decimal (https://2130706433/ -> 127.0.0.1) - hex (https://0x7f000001/) and octal (https://0177.0.0.1/) - dotless/short forms (https://127.1/) - IPv6 loopback/unspecified ([::1], [::]) - IPv6 link-local (fe80::/10) and ULA (fc00::/7) - IPv4-mapped IPv6 ([::ffff:127.0.0.1], [::ffff:169.254.169.254]) - carrier-grade NAT range (100.64.0.0/10) isPrivateOrReservedHostname now canonicalizes IPv4 (any inet_aton-style encoding) and fully parses IPv6 literals, checking the embedded IPv4 of mapped/compatible addresses. Adds a discriminant unit test covering the bypass vectors and public-address negatives. isPrivateOrReservedHostname and validateWebhookDestination are exported for unit testing.
|
The latest updates on your projects. Learn more about Vercel for GitHub. 3 Skipped Deployments
|
Contributor
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Harden webhook destination validation against SSRF via IP address normalization bypasses.
isPrivateOrReservedHostnameinwebhookSender.tspreviously only recognized dotted-decimal IPv4 literals (parts.length === 4). Any private/reserved target reachable through a non-dotted-decimal encoding, or via IPv6, slipped past the allowlist and the webhookfetchwould still connect.Root cause (why the naive check was incomplete)
Two distinct gaps, and it's worth separating them because they explain the size of the change:
IPv4 alternate encodings (
http://2130706433/,0x7f000001,0177.0.0.1,127.1).These are, in practice, already neutralized at the call site:
new URL(destination).hostnamecanonicalizes them to127.0.0.1before the check runs. So they were covered by accident. The fix now handles them explicitly and defensively so the function is correct independent of who calls it.The real, live bypass was IPv6 + CGNAT.
new URL()does not flatten these to something the old check caught:[::1],[::]fe80::/10fc00::/7[::ffff:127.0.0.1](URL yields[::ffff:7f00:1]),[::ffff:169.254.169.254](cloud metadata!)100.64.0.0/10A
https://[::ffff:169.254.169.254]/latest/meta-data/destination would previously pass validation and let a webhook hit the cloud metadata endpoint.The fix
isPrivateOrReservedHostnamenow:inet_aton-style encoding (decimal, hex, octal, dotless/short) into canonical octets, then checks it against the private/reserved ranges (0/8,10/8,127/8,169.254/16,172.16/12,192.168/16, and100.64/10CGNAT).::compression, zone IDs, and embedded IPv4 tails) and blocks loopback, unspecified, link-local, ULA, and IPv4-mapped/compatible addresses (inspecting the embedded IPv4 against the same range logic).Both
isPrivateOrReservedHostnameandvalidateWebhookDestinationare exported so the behavior can be unit-tested directly.Tests
Added
valhalla/jawn/src/lib/clients/__tests__/webhookSender.ssrf.test.ts, a discriminant suite covering:example.com,8.8.8.8,1.1.1.1, public IPv62606:4700:4700::1111) and range boundaries chosen to catch off-by-one errors (172.15.0.1,172.32.0.1,192.169.0.1,11.0.0.1,100.63.0.1,100.128.0.1).Verified against the pre-fix logic the suite fails exactly the 11 bypass vectors (IPv6 + CGNAT + IPv4-mapped); with the fix the full suite passes 51/51.
tsc --noEmitonjawnreports no new errors from these files.Scope
valhalla/jawn/src/lib/clients/webhookSender.ts(validation logic only; thesendToWebhook/sendTestWebhookflow and payload handling are unchanged).valhalla/jawn/src/lib/clients/__tests__/webhookSender.ssrf.test.ts(new).