Skip to content

fix(providers): block IPv4-embedding IPv6 forms + restrict ports in SSRF defense#173

Open
ruhex wants to merge 2 commits into
mainfrom
fix/158-ssrf-ipv6-bypass
Open

fix(providers): block IPv4-embedding IPv6 forms + restrict ports in SSRF defense#173
ruhex wants to merge 2 commits into
mainfrom
fix/158-ssrf-ipv6-bypass

Conversation

@ruhex

@ruhex ruhex commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Closes #158

Problem

Two gaps in PrefixSourceUrlValidator (the #144 SSRF defense):

1. IPv6 normalization was incompleteIsBlockedAddress mapped IPv4-mapped-to-IPv6 but did not cover IPv6 forms that embed a non-public IPv4:

  • 2002::/16 (6to4) — 2002:ac10:0001:: encodes 172.16.0.1 (RFC 1918 private).
  • 2001::/32 (Teredo) — can embed private IPv4 in the last 32 bits.
  • IPv4-compatible IPv6 (::a.b.c.d, deprecated but parseable).

An attacker controlling a DNS record can return such a 6to4 address for a peer-supplied URL and reach the connect path to an internal host.

2. No port restrictionValidateUrlAsync checked scheme is http/https but did not restrict the port. A peer can supply http://internal-host:9000/...; the ConnectCallback validates the IP, but the port is attacker-controlled — internal services on non-standard ports remain reachable.

Fix

BGPLite.Providers/PrefixSourceUrlValidator.cs:

  • Added 2002::/16 (6to4), 2001::/32 (Teredo), ::ffff:0:0/96 (IPv4-mapped — also caught by IsIPv4MappedToIPv6, defense in depth), ::/96 (IPv4-compatible) to BlockedRanges.
  • ValidateUrlAsync now restricts the port to 80/443 (scheme defaults or their explicit :80/:443 forms).

Verification

  • dotnet build BGPLite.sln
  • dotnet test BGPLite.sln ✅ 440 passed (+10 new in PrefixSourceUrlValidatorTests)
  • New tests: IsBlockedAddress_Rejects_IPv4EmbeddingForms (Theory: 6to4 encoding private/loopback/metadata, Teredo-style embedding, IPv4-compatible, IPv4-mapped private), ValidateUrlAsync_Rejects_NonStandardPort (:9000 → rejected), ValidateUrlAsync_Accepts_StandardPorts (Theory: implicit/explicit 80 and 443).

Risk / behavior change

  • Intentional behavior change: peer-supplied URLs on non-80/443 ports are now rejected at ValidateUrlAsync. This is acceptable — CIDR-list endpoints serve on standard ports; an operator with a legitimate non-standard port can extend the allowlist later. The ConnectCallback (live defense) is unaffected — it still validates the IP for whatever port the named client was configured with.
  • The ValidateUrlAsync is still not wired into the live fetch path (it's the API-submission-time validator) — wiring it is a separate concern tracked in the issue. This PR closes the validation-logic gaps; the live defense (ConnectCallback) already covers the IP, and now also covers the new IPv6 ranges via IsBlockedAddress.

Summary by CodeRabbit

  • Bug Fixes
    • Strengthened URL validation to block additional IPv6 representations that can embed private IPv4 ranges.
    • Added stricter port enforcement so HTTP/HTTPS targets are allowed only on standard ports (80 for HTTP, 443 for HTTPS), rejecting non-standard ports before any network connection is attempted.
  • Tests
    • Added coverage to verify IPv4-embedding IPv6 forms are rejected and that only standard ports are accepted.

…SRF defense (#158)

Two gaps in PrefixSourceUrlValidator:

1. IsBlockedAddress normalized IPv4-mapped IPv6 but did not cover IPv6
   forms that EMBED a non-public IPv4 — 6to4 (2002::/16, where the last
   32 bits encode an IPv4 e.g. 2002:ac10:1:: → 172.16.0.1), Teredo
   (2001::/32), and the deprecated IPv4-compatible form (::a.b.c.d).
   An attacker controlling DNS could return such an address for a peer-
   supplied URL and reach an internal IPv4 host via the embedding.

2. ValidateUrlAsync checked scheme but not port — a peer could supply
   http://internal-host:9000/... and the ConnectCallback validates only
   the IP, leaving the port attacker-controlled.

Added 2002::/16 (6to4), 2001::/32 (Teredo), ::ffff:0:0/96 (IPv4-mapped,
defense in depth), and ::/96 (IPv4-compatible) to BlockedRanges.
ValidateUrlAsync now restricts the port to 80/443 (scheme defaults or
their explicit forms).
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1c37be2d-9e42-45bd-9355-b20eeb291280

📥 Commits

Reviewing files that changed from the base of the PR and between cc14341 and 554873e.

📒 Files selected for processing (2)
  • BGPLite.Providers/PrefixSourceUrlValidator.cs
  • BGPLite.Tests/PrefixSourceUrlValidatorTests.cs

📝 Walkthrough

Walkthrough

PrefixSourceUrlValidator now blocks additional IPv6 forms that can embed private IPv4 addresses and rejects non-standard ports in both URL validation and live connection paths. Tests cover the new address forms and port allowlist behavior.

Changes

SSRF Validator Hardening

Layer / File(s) Summary
Blocked IPv6 range extensions
BGPLite.Providers/PrefixSourceUrlValidator.cs, BGPLite.Tests/PrefixSourceUrlValidatorTests.cs
BlockedRanges gains IPv6 entries for 6to4, Teredo, IPv4-mapped, and IPv4-compatible forms, and tests assert these embedded-private-address forms are rejected.
Port allowlist enforcement
BGPLite.Providers/PrefixSourceUrlValidator.cs, BGPLite.Tests/PrefixSourceUrlValidatorTests.cs
IsAllowedPort is shared by ValidateUrlAsync and CreateValidatedConnectionAsync, which now reject ports outside 80 and 443; tests cover rejected, accepted, and allowlist-consistency cases.

Estimated code review effort: 2 (Simple) | ~12 minutes

Possibly related PRs

  • ruhex/BGPLite#145: Both PRs modify PrefixSourceUrlValidator's SSRF defenses, with this PR extending the blocked-IP checks and ValidateUrlAsync logic introduced in the related PR.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR covers IPv6-embedding blocking and port allowlisting, but it does not show the requested ValidateUrlAsync wiring into the live fetch path. Wire ValidateUrlAsync into the live fetch path (for example, HttpPrefixProvider.LoadAsync) so scheme and port checks run before fetching.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is specific and matches the main SSRF hardening changes to IPv6 embedding forms and port restrictions.
Out of Scope Changes check ✅ Passed No clear out-of-scope changes are evident; the edits stay within SSRF validation and related tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/158-ssrf-ipv6-bypass

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@BGPLite.Providers/PrefixSourceUrlValidator.cs`:
- Around line 148-156: The port allowlist in PrefixSourceUrlValidator only
covers ValidateUrlAsync, but the live fetch path still uses
CreateValidatedConnectionAsync as the SocketsHttpHandler.ConnectCallback and
passes context.DnsEndPoint.Port directly to socket.ConnectAsync. Update
CreateValidatedConnectionAsync (and any shared helper it uses) to enforce the
same 80/443 allowlist before connecting, so non-standard ports are rejected at
fetch time too.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e49d31a7-2c3a-4752-9398-1850551f0bf7

📥 Commits

Reviewing files that changed from the base of the PR and between a46c07f and cc14341.

📒 Files selected for processing (2)
  • BGPLite.Providers/PrefixSourceUrlValidator.cs
  • BGPLite.Tests/PrefixSourceUrlValidatorTests.cs

Comment thread BGPLite.Providers/PrefixSourceUrlValidator.cs
…CodeRabbit #173)

ValidateUrlAsync checked the port, but the live fetch path
(CreateValidatedConnectionAsync, the SocketsHttpHandler.ConnectCallback)
passed context.DnsEndPoint.Port straight to socket.ConnectAsync — so a
peer URL on a non-standard port still reached internal services at fetch
time. Extracted IsAllowedPort (80/443 only) and call it from both the
API-submission validator and the live connect callback so the two layers
cannot drift.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[security/providers] SSRF bypass via 6to4/teredo IPv6 + no port restriction

1 participant