1

I want a server application to send webhooks (i.e. outgoing http-post requests) to user-controlled http(s) URLs. A naive implementation of that functionality is vulnerable to server-side request forgery (SSRF), since the target host could be within the same private network as the server. To prevent that, I would like to blacklist private IP addresses.

On classic .NET, HttpWebRequest supported this via the ServicePoint.BindIPEndPointDelegate, which received the IP address the client connects to. However in .NET Core, HttpWebRequest was turned into a wrapper over HttpClient, and this callback is ignored completely. This introduces an SSRF vulnerability into previously secure applications.

But even migrating from the deprecated using HttpWebRequest to using HttpClient directly doesn't appear to help. I could not find any callback in HttpClientHandler that received the IP address before connecting to it.

Resolving the domain to an IP address before passing the original URL to HttpClient doesn't work reliably either. This is complex since it needs to handle multiple returned IP addresses, and it's vulnerable to ToC/ToU, since the name resolution might return a different response for the check and the request execution. This also requires manual handling of HTTP redirects, since the IP addresses of the redirect target(s) need to be checked as well.

Another idea is to resolve the domain, fill the host-to-connect-to with the IP, and the original domain in the http host header (if HttpClient even supports that). This also requires manual handling of HTTP redirects, making it complex and fragile.

There is also the option to handle it outside the application (via proxies, firewalls, etc.), but that doesn't feel like a clean solution either and increases infrastructure cost and complexity.

Is there a reasonable way to support sending webhooks using HttpClient without suffering from SSRF? This feels like a pretty common use-case, which shouldn't require complex workarounds.

4
  • This question is similar to: How to bind HttpClient to a specific (source) ip address in .NET 5. If you believe it’s different, please edit the question, make it clear how it’s different and/or how the answers on that question are not helpful for your problem. Commented Jul 25, 2024 at 15:13
  • @sa-es-ir That callback mentioned in that question looks like it can be used to fix my problem. The question itself is different however, since I care about the checking remote-ip, while the other question cared about choosing the local-ip. Commented Jul 25, 2024 at 16:19
  • That callback looks promising. I overlooked it because it's on SocketsHttpHandler not on HttpClientHandler. I'll verify tomorrow if that callback actually works for my use-cases. Commented Jul 25, 2024 at 16:24
  • I think would solve the problem hopefully. Commented Jul 25, 2024 at 16:29

1 Answer 1

0

I retracted my vote to duplicate the question because that question doesn't answer the question but the solution is similar.

I've tried this way and was able to get the remote IP before getting data from it.


SocketsHttpHandler handler = new SocketsHttpHandler();

handler.ConnectCallback = async (context, cancellationToken) =>
{
    Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp);

    try
    {
        await socket.ConnectAsync(context.DnsEndPoint, cancellationToken).ConfigureAwait(false);

        var erp = socket.RemoteEndPoint;

        var remoteAddr = ((IPEndPoint)erp!).Address;

        var ipV4 = remoteAddr.MapToIPv4().ToString();
        Console.WriteLine($"IP V4: {ipV4}");

        var ipV6 = remoteAddr.MapToIPv6().ToString();

        Console.WriteLine($"IP V6: {ipV6}");

        //----> If the IPs is in black list, we can throw an exception here

        return new NetworkStream(socket, true);
    }
    catch
    {
        socket.Dispose();

        throw;
    }
};

var client = new HttpClient(handler);

var response = await client.GetAsync("https://stackoverflow.com");

If you do a ping stackoverflow.com it's possible to check the IPs.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.