Getting started

edit

Install the package

dotnet package add Meziantou.Framework.Http.ServerSideRequestForgery

The examples omit the using directives: they need using Meziantou.Framework.Http.ServerSideRequestForgery;.

Protect an HttpClient

Wrap a SocketsHttpHandler in a ServerSideRequestForgeryClientHandler, and send the requests that target user-supplied URLs through it:

var options = new ServerSideRequestForgeryOptions();
var handler = new ServerSideRequestForgeryClientHandler(new SocketsHttpHandler { UseProxy = false }, options);
using var client = new HttpClient(handler, disposeHandler: true);

// Requests to a loopback or private address are rejected before connecting
var exception = await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(new Uri("https://127.0.0.1/")));
Assert.IsType<ServerSideRequestForgeryException>(exception.InnerException);
  • The default options block the loopback, private, link-local and reserved networks, and allow the https and wss schemes only. See Configuration.
  • UseProxy = false matters: a request sent through a proxy cannot be validated, so it is rejected. SocketsHttpHandler uses the proxy of the HTTP_PROXY, HTTPS_PROXY and ALL_PROXY environment variables by default. See Proxies and HTTP/3.
  • The constructor configures the SocketsHttpHandler you pass: create one for this purpose, and do not share it with a client that must reach internal hosts.

Catch a rejected request

A rejected connection surfaces as an HttpRequestException whose InnerException is a ServerSideRequestForgeryException, because the runtime wraps what the connection callback throws:

ServerSideRequestForgeryException? rejection = null;
try
{
    // 127.0.0.1 is in the default UnsafeIpNetworks
    using var response = await httpClient.GetAsync(new Uri("https://127.0.0.1/"), cancellationToken);
}
catch (HttpRequestException ex) when (ex.InnerException is ServerSideRequestForgeryException ssrf)
{
    rejection = ssrf;
}

Assert.NotNull(rejection);

An HTTP/3 request is rejected before it reaches the SocketsHttpHandler, so that one arrives as a ServerSideRequestForgeryException directly. See Handling rejections.

Configure a SocketsHttpHandler directly

ConfigureSsrf applies the same validation to a SocketsHttpHandler without the wrapping handler, and returns the handler:

var handler = new SocketsHttpHandler { UseProxy = false };
handler.ConfigureSsrf(options);

Prefer ServerSideRequestForgeryClientHandler: ConfigureSsrf alone does not reject the requests that may use HTTP/3, which are not validated at all. See Proxies and HTTP/3.

Both work by setting SocketsHttpHandler.ConnectCallback. A handler that already has a ConnectCallback throws an InvalidOperationException, rather than losing the callback silently.

Next steps