Runtime: Possible bug in AuthenticationHelper

Created on 18 May 2020  路  18Comments  路  Source: dotnet/runtime

I think that there might be a bug in the following file:

https://github.com/dotnet/runtime/blob/master/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs

Lines 100-118 is where the Spn is being calculated.

It seems that the port is not taken into account when calculating the spn.

That is, you get something like HTTP/machine.domain.lab instead of HTTP/machine.domain.lab:5000

The reason I am posting this is that I am troubleshooting a WCF client problem that happens in .NET Core but not in .NET framework.

The Spn I am passing to System.ServiceModel.ClientBase is being ignored. I am passing HTTP/machine.domain.lab:5000, but it seems that the code in AuthenticationHelper.NtAuth recalculates the SPN and does not use the port. This causes an exception to be thrown. The exception message is "The target principal name is incorrect". I am assuming that the SPN is the issue.

I think that another issue is the fact that the Spn I am passing from WCF is being ignored.

I am using .NET Core 3.1 and System.ServiceModel.Http 4.7.

area-System.Net.Http bug tenet-compatibility

Most helpful comment

Thank you @davidsh and @mconnew

All 18 comments

Tagging subscribers to this area: @dotnet/ncl
Notify danmosemsft if you want to be subscribed.

Tagging subscribers to this area: @dotnet/ncl
Notify danmosemsft if you want to be subscribed.

Thank you for the bug report. This is likely a bug in .NET Core. In .NET Framework, we do add the port number to the SPN calculation if the port is non-standard (i.e. not '80' nor '443').

Thanks. Is there a work around?

Thanks. Is there a work around?

You might try explicitly adding a 'Host' header to the HTTP request. Doing so will result in using that header value exactly for the 'hostname' of the SPN.

See: https://github.com/dotnet/runtime/blob/d9b006ba9a27dce2f19217c00f843337a4428805/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs#L86-L99

However, since you are using WCF you might not have direct control over adding that 'Host' request header since WCF is calling HttpClient itself.

Thanks. In code you showed, can request.Headers.Host contain the port? Or just the host name?

Thanks. In code you showed, can request.Headers.Host contain the port? Or just the host name?

It should contain hostname and port.

I have tried to add a Host header via a custom implementation of IClientMessageInspector in Wcf.

It doesn't work because HttpChannelFactory explicitly doesn't take the Host header from the Message:

https://github.com/dotnet/wcf/blob/42b5572be18cb6d0d60718f24143a541b5dea755/src/dotnet-svcutil/lib/src/FrameworkFork/System.ServiceModel/System/ServiceModel/Channels/HttpChannelFactory.cs#L1109

I was able to add custom headers (just for testing), and they show up all the way down the call stack, but the Host header gets ignored explicitly by HttpChannelFactory.

We have migrated one product from .NET Framework to .NET Core, and this issue might force us to go back to .NET Framework until a fix is released.

Are you running .NET Core 3.1?

If so, try switching to the legacy HTTP stack. Add this line of code to the beginning of your program.

```c#
AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", false);


Or you can also set this environment variable as well:

set DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER=0
```

Yes I am using .NET Core 3.1.

I tried this with and without adding the Host header and it did not work. HttpChannelFactory is still involved and it removes the Host header. Now the error is:

The HTTP request is unauthorized with client authentication scheme 'Negotiate'. The authentication header received from the server was 'Negotiate oWYwZKADCgEB....

I guess this is still the SPN issue, right?

Please try my suggestion to disable SocketsHttpHandler and switch back to the legacy HTTP handler.

You also don't need to do the 'Host' header suggestion.

@davidsh , I did try your suggestion as I meant to tell you in my previous comment.

I called AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", false); at the application startup.

I tried with and without setting the Host header via IClientMessageInspector.

I am now getting:

The HTTP request is unauthorized with client authentication scheme 'Negotiate'. The authentication header received from the server was 'Negotiate oWYwZKADCgEB....

Instead of:

The target principal name is incorrect.

And by debugging, I can still see that HttpChannelFactory is still involved and ignoring the added Host header.

Since you are still getting an authentication error, perhaps the problem is something else?

Can you show any logs of what SPN is being attempted? If it works with .NET Framework then it should work when you disable SocketsHttpHandler.

ServiceModel* classes are part of WCF. Adding @mconnew to comment as well.

On .NET Framework, we don't allow the Host header being added, we explicitly remove it. We're trying our best to maintain identical behavior with .NET Framework so the same behavior is there for .NET Core. WCF does have the feature to provide an SpnEndpointIdentity to override the SPN, but I haven't implemented it yet as I'm still waiting on support for using a UPN with SocketsHttpHandler. You can't provide a UPN value in the Host header, so I consider the Host header mechanism a hack. And I see yet again the UPN feature issue #25320 has been bumped again beyond .NET 5.0.
I'm going to revisit the Spn use case as it's actually an important one for WSTrust with ADFS which we're about to release support for.
As for being able to manipulate the Host header, I've created a way how to modify the Http request/response on it's way in/out by providing a mechanism to provide a custom MessageHandler which can be a delegating handler. A blog post describing the general idea can be found here. An example implementation which modifies the request/response can be found in our tests here.

@davidsh , without disabling UseSocketsHttpHandler, I was able to make it work in a hacky way by forcing the system to use the correct spn (of course I will not do that in production, I did that just to confirm that this is an Spn issue). This means that I did not break something else when I migrated from .NET Framework to .NET Core.

I am telling you this because when I disable UseSocketsHttpHandler, it does not work. I get the following error:

The HTTP request is unauthorized with client authentication scheme 'Negotiate'. The authentication header received from the server was 'Negotiate oWYwZKADCgEB....

Any way, I added the Host header the way @mconnew suggested and it worked. I will go with that option until the bug is fixed. (UPDATE: this is while UseSocketsHttpHandler is enabled).

Thank you @davidsh and @mconnew

I found this issue looking for a solution to a related problem.

We have servers running on Linux with Kerberos SPNs and we set the Host request header in outgoing requests.
With .Net Framework we get identical behaviour with and without the host header (and with or without port numbers in the host header.)
When trying to use .Net Core setting the host header to hostname:port results in failed authentication. It seems the client is sending an NTLM packet in response to the Negotiate challenge rather than the Kerberos one the server is expecting.

Here is a small example program that demonstrates the problem:

```c#
using System;
using System.Net;
using System.Net.Http;

public class SendHttp
{
public static void Main(string[] args)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

  var uri = new Uri(args[0]);
  var request = new HttpRequestMessage(HttpMethod.Get, uri);
  if (args.Length > 1)      
  {
     request.Headers.Host = args[1];
  }
  var handler = new HttpClientHandler{
     Credentials = CredentialCache.DefaultNetworkCredentials };
  using var client = new HttpClient(handler);
  Console.WriteLine($"Result: {client.SendAsync(request).Result}");

}
}
```
With this program:

SendHttp https://server:1234/ - works with both .Net Framework and .Net Core

SendHttp https://server:1234/ server:1234 - works with .Net Framework, sends NTLM-style Authorization header with .Net Core

SendHttp https://server:1234/ server - works with both .Net Framework and .Net Core

For a bit of context The target principal name is incorrect means that a service principal was actually found based on the SPN, but the server barfed on it because the key the KDC used to encrypt the ticket didn't match. This is because the found service principal was most likely not the correct account and the passwords didn't match. The server responded with a specific error KRB_ERR_AP_MODIFIED and SSPI raised the error code.

Then the new issue of The HTTP request is unauthorized with client authentication scheme 'Negotiate'. The authentication header received from the server was 'Negotiate oWYwZKADCgEB.... means that it negotiated NTLM (helpful to see the full ticket so its possible to decode) because the SPN wasn't found _at all_ so the client fell back to NTLM. Unclear why NTLM would fail, but that requires troubleshooting the server side code.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jchannon picture jchannon  路  3Comments

btecu picture btecu  路  3Comments

v0l picture v0l  路  3Comments

yahorsi picture yahorsi  路  3Comments

matty-hall picture matty-hall  路  3Comments