Coding Stephan

Nuke the man-in-the-middle attack

Everybody should take security seriously! Let’s see what you can do to do the extra step to protect your applications. In this post we will have a look at how you can protect your application from Man-in-the-middle attacks.

Man-in-the-middle

What is a man-in-the-middle attack? It means the attacker has found a way to intercept the traffic between two parties. This is a serious problem, because the attacker can read, modify and even delete the traffic. This is a serious problem for users, but also for applications.

We are using HTTPS, so we are safe, right?

By default .NET Core will validate the SSL/TLS certificate, for all requests to https addresses. This is a good thing, but it is not enough. The default validation will check against the trusted root certificates on that machine. So if the machine does not trust the certificate, the request will fail. This seems like the best protection, but what if the machine where your application is running on is compromised? Then the attacker can add a new root certificate to the trusted root certificates and your application will trust the certificate. This opens the door to man-in-the-middle attacks, meaning the attacker can intercept the traffic between your application and the server.

What is the problem?

Let’s say you have an application that uses some secure way to get a token for the Graph API, with some highly privileged permissions. If an attacker can intercept the traffic between your application and the server, the attacker can get the token and use it to do whatever the token allows. This is a serious problem, and you as a developer should do something about it.

I would rather have my application fail (or even crash) then that it would be responsible for a security breach, where our application would just send such a privileged token to an attacker. And in fact, I can see a lot of other reasons why I don’t want legitimate users of the application to inspect the traffic between the application and the server. One being, none of their business.

ServerCertificateCustomValidationCallback to the rescue

The defacto way to do http requests in .NET Core is by using the HttpClient, and fortunately the HttpClient has a way to do extra certificate validation. In the background the HttpClient uses a HttpClientHandler which is responsible for the actual request. The HttpClientHandler has a property called ServerCertificateCustomValidationCallback which is a Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool>. This callback is called when the HttpClient is validating the certificate. If the callback returns true, the certificate is valid, if it returns false the certificate is not valid.

If you don’t set the ServerCertificateCustomValidationCallback the default validation will be used, which means the certificate is checked against the trusted root certificates on that machine.

How to use it?

This code just shows how to configure the ServerCertificateCustomValidationCallback, (without any other checks):

var httpClientHandler = new HttpClientHandler();
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
{
    // not sure if this is the default, but it's a good idea to check for the default errors
    return errors == System.Net.Security.SslPolicyErrors.None;
};
var httpClient = new HttpClient(httpClientHandler);
// Use your httpClient like you're used to.
...

You’re still not protected against mitm attacks. Let’s do some extra validation.

Validate requests to Microsoft Graph API

Microsoft uses DigiCert as their certificate authority, so we can use this information to do extra validation. We know the thumbprint of the DigiCert root certificate, this is A8985D3A65E5E5C4B2D7D66D40C6DD2FB19C5436 (as of writing this post). And will probably not change any time soon. For graph we can expect that the last certificate in the checked certificate chain is the DigiCert root certificate. The code below only validates requests to the Microsoft Graph API, but you can adjust this code to suite your needs.

var httpClientHandler = new HttpClientHandler();
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
{
  // Can someone tell me if this is the default?
  bool defaultResult = errors == System.Net.Security.SslPolicyErrors.None;
  if (!defaultResult) {
    return false; //Fail fast, if the default validation fails.
  }

  if (message.RequestUri!.Host == "graph.microsoft.com")
  {
    // Microsoft uses DigiCert, so we can check the thumbprint of the root certificate.
    bool graphResult = chain!.ChainElements.Last().Certificate.Thumbprint == "A8985D3A65E5E5C4B2D7D66D40C6DD2FB19C5436";
    if (!graphResult)
    {
        Console.WriteLine("Got cert with name {0} and thumb {1} for Graph", cert!.Subject, cert.Thumbprint);
        foreach (var element in chain!.ChainElements)
        {
            Console.WriteLine("Element: {0} thumb: {1}", element.Certificate.Subject, element.Certificate.Thumbprint);
        }
    }
    return graphResult;
  }

  return true;
};
var httpClient = new HttpClient(httpClientHandler);

What to validate?

You can use the ServerCertificateCustomValidationCallback to validate all sorts of things. I recommend to check something in the chain, because you’re probably going to rollover the certificate at some point. By relying on the chain, you can rollover the certificate without changing the code. Root certificates are not changed that often and are a good candidate for validation. That is, if there is a trusted root certificate.

You can also check the thumbprint of the certificate itself (even if it’s just a self-signed certificate). That is all up to you.

But there is one important thing to remember, if the certificate you’re validating against changes, your application will stop working! So make sure you have a plan in place to update your application when the certificate changes.

I recommend against loading the validation data from an external source, then you’re just moving the problem to another place. If the external source is compromised, the attacker can change the validation data and your application will trust the wrong certificate. So I recommend to hardcode the validation data in your application, if you have a plan in place to update your application when the certificate changes.

Exceptions

Any request to an url where you specified extra validation that does not pass your validation will result in a HttpRequestException with the message The SSL connection could not be established, see inner exception.. The inner exception is of type System.Security.Authentication.AuthenticationException with the message The remote certificate is invalid according to the validation procedure.. This is not a http status code, so the request will throw this exception. You have to catch these errors in your application and then show the correct error message to the user.

Legitimate reasons to intercept traffic

There are legitimate reasons to intercept traffic, but this is not something you should do without the user knowing. This guide is about informing developers about the fact that it’s possible to intercept traffic. Even legitimate users could intercept the traffic, and may change requests before they are send to the server. Extra validations will help you to block intercepting traffic.

Dev Proxy

Microsoft built this awesome Dev Proxy to guide developers in the right direction when developing their applications (that use graph). They do this by “intercepting” the traffic and showing the developer what could be done better (like using the $select parameter) and to help you test your app resilience against remote errors.

I think this is a great tool for developers, which all developers should use to test their apps, but it also shows everybody (even the bad guys) how easy it is to intercept https traffic. If you implemented the above code, and try to use the Dev Proxy, you won’t see any traffic in the Dev Proxy. And any requests against the graph api will throw an exception.

SSL Exception with dev proxy running

Conclusion

Protecting your application against Man-in-the-middle attacks is pretty easy (if you ask me). So let’s all add a task to the next sprint to add extra ssl validation to all your applications. If you’re not allowed to make such a decision, share this post with your manager and at least have the conversation about it.

Maybe even setup the dev proxy to intercept traffic to your own applications, and show the rest of the team how easy it is to intercept (and modify) traffic. And that you only need a few lines of code to protect against it.

As always, if you have any questions, feel free to ask then on any of my socials.