Meziantou.Framework.Http.ServerSideRequestForgery
2.0.5
Prefix Reserved
dotnet add package Meziantou.Framework.Http.ServerSideRequestForgery --version 2.0.5
NuGet\Install-Package Meziantou.Framework.Http.ServerSideRequestForgery -Version 2.0.5
<PackageReference Include="Meziantou.Framework.Http.ServerSideRequestForgery" Version="2.0.5" />
<PackageVersion Include="Meziantou.Framework.Http.ServerSideRequestForgery" Version="2.0.5" />
<PackageReference Include="Meziantou.Framework.Http.ServerSideRequestForgery" />
paket add Meziantou.Framework.Http.ServerSideRequestForgery --version 2.0.5
#r "nuget: Meziantou.Framework.Http.ServerSideRequestForgery, 2.0.5"
#:package Meziantou.Framework.Http.ServerSideRequestForgery@2.0.5
#addin nuget:?package=Meziantou.Framework.Http.ServerSideRequestForgery&version=2.0.5
#tool nuget:?package=Meziantou.Framework.Http.ServerSideRequestForgery&version=2.0.5
Meziantou.Framework.Http.ServerSideRequestForgery
SSRF protection for SocketsHttpHandler using scheme allow-listing and runtime IP validation.
Usage
using Meziantou.Framework.Http.ServerSideRequestForgery;
var options = new ServerSideRequestForgeryOptions
{
ResolutionStrategy = IpAddressResolutionStrategy.PreferIpv4,
DisallowMixedSafeAndUnsafeIpAddresses = true,
};
options.SafeSchemes.Add("https");
options.SafeSchemes.Add("wss");
options.UnsafeIpNetworks.Add(IPNetwork.Parse("203.0.113.0/24"));
options.SafeIpNetworks.Add(IPNetwork.Parse("198.51.100.10/32"));
var handler = new ServerSideRequestForgeryClientHandler(new SocketsHttpHandler { UseProxy = false }, options);
using var httpClient = new HttpClient(handler, disposeHandler: true);
ServerSideRequestForgeryClientHandler configures the inner SocketsHttpHandler and rejects the requests that
would bypass it over HTTP/3. SocketsHttpHandler.ConfigureSsrf(options) applies the connection validation on its
own, but leaves the HTTP/3 gap below open, so prefer the handler.
Behavior
- Validates request scheme against
SafeSchemes. - Resolves DNS on every connection attempt to avoid TOCTOU vulnerabilities.
- Validates each resolved address against
UnsafeIpNetworks, unlessSafeIpNetworksalready matches it. - Optionally rejects mixed safe/unsafe DNS responses.
- Uses
IpAddressResolutionStrategyto select the final address (Ipv4Only,Ipv6Only,PreferIpv4,Random,RoundRobin). - Rejects connections that target an HTTP proxy.
- Rejects requests that would be sent over HTTP/3, whose QUIC connection cannot be validated.
Configuration
The collections start populated, and the example above adds to those defaults rather than replacing them:
SafeSchemesalready containshttpsandwss.UnsafeIpNetworksalready contains the loopback, private, link-local, carrier-grade NAT, multicast and reserved ranges, plus the IPv6 transition ranges that embed an IPv4 address.SafeIpNetworksstarts empty.
Call Clear() first to define a set from scratch:
options.SafeSchemes.Clear();
options.SafeSchemes.Add("https");
SafeIpNetworks takes precedence: an address it matches is allowed even when UnsafeIpNetworks also matches it.
That is how you reach a specific internal host without dropping the range it sits in — and why a broad entry there
silently re-opens whatever part of the deny list it covers.
Errors
A rejected request surfaces as an HttpRequestException with a ServerSideRequestForgeryException as its
InnerException, because the runtime wraps anything thrown from ConnectCallback. Catch it accordingly:
catch (HttpRequestException ex) when (ex.InnerException is ServerSideRequestForgeryException ssrf)
The HTTP/3 rejection is thrown before the request reaches the inner handler, so that one arrives as a
ServerSideRequestForgeryException directly.
Proxies
Validation happens when the connection is opened, which for a proxied request is the connection to the proxy, not to the target. The proxy then reaches the real target itself, over a tunnel this library cannot inspect, so a proxied request cannot be validated at all.
Rather than appear to protect such a request, the handler rejects it with a
ServerSideRequestForgeryException. Note that SocketsHttpHandler.UseProxy defaults to true and
HttpClient.DefaultProxy reads HTTP_PROXY, HTTPS_PROXY and ALL_PROXY, so a proxy can be in effect
without the application configuring one. Send requests that need SSRF protection through a handler with
UseProxy = false:
var handler = new SocketsHttpHandler { UseProxy = false };
handler.ConfigureSsrf(options);
Requests the proxy is configured to bypass are connected to directly and are validated normally.
The rejection does not depend on what the proxy reports for the destinations it can be asked about here. An
https destination is reached over a CONNECT tunnel the connection pool opens with a request of its own, which
targets the proxy and carries the real destination only in a Host header. A proxy that applies to some
destinations only - a PAC script, or a custom IWebProxy - answers DIRECT for every destination reachable from
the connection callback, and would otherwise hide the destination it does proxy behind its own address. The tunnel
request is therefore treated as a proxy connection on its own.
HTTP/3
Validation runs from SocketsHttpHandler.ConnectCallback, which the runtime uses only for TCP connections. An
HTTP/3 connection is established over QUIC by ConnectHelper.ConnectQuicAsync, which resolves the endpoint itself
and never calls the callback, so an HTTP/3 request is not validated at all. Setting a ConnectCallback does not
disable HTTP/3: HttpConnectionPool clears it for plaintext HTTP and for every proxy kind, but not for a direct
HTTPS connection, and HTTP/3 is enabled by default on Windows, Linux and macOS.
Two requests reach QUIC:
- one that asks for it —
Version3.0 with a policy other thanRequestVersionOrLower; - one that merely allows an upgrade —
VersionPolicy = RequestVersionOrHigherover TLS. The server then only has to answer with anAlt-Svc: h3="..."header, which may name any host and port, and the next request to that authority goes to it over QUIC. The first, validated connection buys nothing.
ServerSideRequestForgeryClientHandler rejects both with a ServerSideRequestForgeryException, the same way a
proxied request is rejected, rather than appear to protect a request it cannot see. A request is accepted when its
Version is below 3.0 and its VersionPolicy is not RequestVersionOrHigher; HTTP/2 is still negotiated over
TLS under those settings.
The check is on the version alone and not on the scheme, because SocketsHttpHandler follows redirects below this
handler: a plaintext request that redirects to HTTPS would otherwise slip through.
If you call ConfigureSsrf directly instead of using the handler, HTTP/3 must be ruled out some other way — the
DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP3SUPPORT=0 environment variable, the matching AppContext switch,
or HttpClient.DefaultRequestVersion and HttpClient.DefaultVersionPolicy on every client.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. net11.0 is compatible. |
-
net10.0
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.12)
-
net11.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.