Skip to content

[PM-30751] - add secure SSRF protection for internal IPs#7256

Open
jaasen-livefront wants to merge 5 commits intomainfrom
PM-30751
Open

[PM-30751] - add secure SSRF protection for internal IPs#7256
jaasen-livefront wants to merge 5 commits intomainfrom
PM-30751

Conversation

@jaasen-livefront
Copy link
Collaborator

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-30751

📔 Objective

Adds centralized SSRF (Server-Side Request Forgery) protection to prevent server-initiated HTTP requests from reaching internal/private/reserved IP ranges when the destination URL is derived from external input.

Problem

SSRF protection logic existed only in the Icons project (IPAddressExtension), and did not cover CGNAT ranges (RFC 6598). Multiple other HTTP clients accepting user-supplied or admin-configured URLs had no IP validation at all.

Additionally, because SsrfProtectionHandler is a DelegatingHandler, it only validates the initial request URI. When AllowAutoRedirect = true (the default on HttpClientHandler/SocketsHttpHandler), redirected requests are followed internally by the primary handler and never pass back through the SSRF handler. This means an attacker-controlled domain can redirect to internal addresses (e.g., http://169.254.169.254/latest/meta-data/) and bypass SSRF protection entirely.

Solution

Shared IP validation — Moved IsInternal() logic from Icons into Core/Utilities/IPAddressExtensions.cs so all projects can use it. Added CGNAT blocking (100.64.0.0/10).

SsrfProtectionHandler — A DelegatingHandler that:

  • Resolves DNS before connecting (closes the gap where redirects could bypass IP checks)
  • Validates all resolved addresses against the internal IP blocklist
  • Blocks with SsrfProtectionException if any resolved IP is internal
  • Rewrites the request URI to the validated IP while preserving the Host header for TLS SNI
  • Handles HTTP redirects manually (301, 302, 303, 307, 308), validating each redirect hop through the full SSRF check pipeline before following it
  • Limits redirect chains to 10 hops
  • Blocks redirects to non-HTTP/HTTPS schemes (e.g., ftp://)
  • Respects RFC 7231 method semantics: 301/302/303 change POST→GET; 307/308 preserve the original method

One-line opt-inIHttpClientBuilder.AddSsrfProtection() extension method to wire the handler into any named HttpClient. This also disables AllowAutoRedirect on the primary handler (both HttpClientHandler and SocketsHttpHandler) so that redirects always pass back through SsrfProtectionHandler for validation. This provides defense-in-depth: the handler validates each hop, and the primary handler is prevented from silently following redirects on its own.

Protected clients

Client Risk Reason
ChangePasswordUriService Highest User-supplied domain
WebhookIntegrationHandler High Admin-configured URL
DatadogIntegrationHandler High Admin-configured URL
SlackService Medium Integration URLs
TeamsService Medium Integration URLs
Icons (fetch + redirect) Existing Now uses shared code + CGNAT fix

Note: The Icons client already set AllowAutoRedirect = false and handled redirects manually via IconHttpRequest.FollowRedirectsAsync(), so it was not vulnerable to the redirect bypass. All other clients listed above were vulnerable.

Out-of-scope clients (server-configured URLs not from external input): BaseIdentityClientService, SSOController, NotificationHubPushRegistrationService, AliveJob, HomeController.

Changes

  • New: src/Core/Utilities/IPAddressExtensions.cs — shared IP blocklist with CGNAT
  • New: src/Core/Utilities/SsrfProtectionHandler.cs — DNS-resolving delegating handler with redirect-following and per-hop SSRF validation
  • New: src/Core/Utilities/HttpClientBuilderSsrfExtensions.cs.AddSsrfProtection() extension (registers handler + disables AllowAutoRedirect)
  • Modified: src/Icons/Util/IPAddressExtension.cs — delegates to shared Core implementation
  • Modified: src/Icons/Util/ServiceCollectionExtension.cs — adds SSRF protection to Icons/ChangePasswordUri clients
  • Modified: src/Core/Dirt/EventIntegrations/EventIntegrationsServiceCollectionExtensions.cs — adds SSRF protection to Webhook, Datadog, Slack, Teams clients
  • New: test/Core.Test/Utilities/IPAddressExtensionsTests.cs — 30 test cases covering all IP ranges + CGNAT boundaries
  • New: test/Core.Test/Utilities/SsrfProtectionHandlerTests.cs — 27 test cases for handler behavior including redirect validation

Redirect protection test coverage

  • Redirect to internal IP (AWS metadata endpoint) → blocked
  • Redirect to localhost → blocked
  • Multi-hop redirect chain eventually hitting internal IP → blocked
  • Redirect to public IP → followed successfully
  • Multi-hop redirects through public IPs → followed successfully
  • Relative redirects → resolved against original URI and validated
  • Non-HTTP scheme redirects (e.g., ftp://) → stopped, returns redirect response as-is
  • 301/302/303 POST→GET method change
  • 307 method preservation

Future usage

Any new HTTP client that accepts external input can opt in with:

services.AddHttpClient("MyClient").AddSsrfProtection();

📸 Screenshots

@jaasen-livefront jaasen-livefront requested a review from a team as a code owner March 19, 2026 17:11
@github-actions
Copy link
Contributor

Logo
Checkmarx One – Scan Summary & Details6cff663d-45fe-4f86-be54-d019f3c11045


Fixed Issues (2) Great job! The following issues were fixed in this Pull Request
Severity Issue Source File / Package
MEDIUM CSRF /src/Api/KeyManagement/Controllers/AccountsKeyManagementController.cs: 193
MEDIUM Use_Of_Hardcoded_Password /src/Core/Constants.cs: 211

@sonarqubecloud
Copy link

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.

2 participants