Getting started
editInstall the package
dotnet package add Meziantou.Framework.Http.ServerSideRequestForgeryThe 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
httpsandwssschemes only. See Configuration. UseProxy = falsematters: a request sent through a proxy cannot be validated, so it is rejected.SocketsHttpHandleruses the proxy of theHTTP_PROXY,HTTPS_PROXYandALL_PROXYenvironment variables by default. See Proxies and HTTP/3.- The constructor configures the
SocketsHttpHandleryou 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
- Configuration: allow an internal host, change the blocked networks.
- Handling rejections: log and count the rejected requests.