-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WindowsIdentity S4U logon does not achieve impersonation #28460
Comments
Comparing logon events and source code for the logon it appears everything is the same: Looking at a network capture of the HttpClientHandler exchange, no negotiate credentials are ever sent in response to 401 from the server.
|
What version of .NET Core are you running? There were several different bugs regarding Windows auth in HttpClient API that were fixed in servicing releases. Please show "dotnet --info" output. And also, try latest .NET Core 2.1 or 2.2. |
It's the latest release I see listed on corefx repo:
|
More research: HttpClientHandler (WinHTTP) SocketsHttpHandler I'll have to modify COREFX to see if matching the .NET framework SPN behavior will get the SocketsHttpHandler working. |
We have an issue in CoreFx repo about this. SocketsHttpHandler should be able to use the 'Host' request header for the default SPN calculation instead of the authority portion of the Uri itself. In fact, .NET Framework goes even further by providing an AuthenticationManager class and CustomTargetDictionary for SPN lookup. We currently don't have in .NET Core. |
According to the documentation, WinHTTP is supposed to do impersonation. See: https://docs.microsoft.com/en-us/windows/desktop/WinHttp/winhttp-vs-wininet I would suggest following up with the WinHTTP team about that. |
Thanks for the info @davidsh. WinHTTP is working with impersonation when the access token level is Impersonation or Delegation which is most likely what they are referring to in the documentation. I'm using a very specialized scenario of S4U2Self logon without SeTCB privilege which only provides an Identification level token on the local system. I'm going to focus on getting the SocketsHttpHandler approach to work since that is the default and preferred handler in .NET Core now. I believe #25320 is the issue regarding the SPN calculation. |
As a test I patched corefx SocketsHttpHandler to use the host header for SPN calculation. I was then able to complete requests using the same DNS workaround I used for .NET Framework (pre-resolving it and using IP in the request URI). So to summarize: the use case is making HTTP requests under an identify level access token. The case arises from using an S4U logon to acheive constrained delegation to network services while retaining least privilege on the host computer. If #25320 is addressed, then you can achieve functional parity with .NET Framework and CoreFX by using SocketsHttpHandler. It is not possible under the older (now non-default) WinHTTP-based handler due to limitations of the underlying WinHTTP library. This is probably fine. While it would be possible to achieve parity with .NET Framework, it's still very limited support for S4U. The DNS workaround only works in my scenario because I'm making a single request and not allowing redirects. Anything triggering DNS resolution would fail. What I'm going to do for now is carry a patch to CoreFX (and deploy my app self contained) that allows me to narrow the impersonation context down to the smallest segement of code possible. Essentially you just need S4U impersonation when you acquire the SSPI Kerberos package credential handle. I know this patch is not reasonable to be merged and I'm not sure the best way to frame it as a mergeable feature, but I'm curious if anyone has any thoughts. I considered for example, a new ICredentials implementation for S4U that the handler would have to specialize for. |
I looked at the .NET Framework code and how we call the Negotiate SSPI. It turns out that in .NET Framework, the WebRequest/HttpWebRequest (and thus HttpClient) objects use a default of these attributes: m_ImpersonationLevel = TokenImpersonationLevel.Delegation;
m_AuthenticationLevel= AuthenticationLevel.MutualAuthRequested; These default values thus cause a set of corresponding SSPI flags to be passed in. It seems the MutualAuth one happens anyways by default. But the Delegation flag is never being set when call the Negotiate SSPI. So that is why this is probably working on .NET Framework but not on .NET Core. |
hey @davidsh. my S4U impersonation scenario above actually works. the problem for me is there is no first class support for using S4U, so it takes numerous hacks/work arounds to get the SocketsHttpHandler working under an Identify token.
These arguments to configure SSPI are important to a client trying to pass a token to a service, but not necessarily important to the middleware service (unless it calls yet another middleware). You're correct that SocketsHttpHandler doesn't configure them. The reason you've probably not heard many complaints is that these are only required to pass a token to a service that uses unconstrained delegation. I'm guessing not many folks in their right mind are using unconstrained delegation anymore, especially not people who are able to use .NET Core. Also, they have WinHttpHandler as a work around option. Calling test middleware that uses unconstrained delegation to reach backend (SocketsHttpHandler fails because it doesn't setup SSPI to acquire a token with
Calling test middleware that uses constrained delegation to reach backend (both work fine):
|
Fixed SocketsHttpHandler so that it will use the request's Host header, if present, as part of building the Service Principal Name (SPN) when doing Kerberos authentication. This now matches .NET Framework behavior. Contributes to #34697 and #27745
Fixed SocketsHttpHandler so that it will use the request's Host header, if present, as part of building the Service Principal Name (SPN) when doing Kerberos authentication. This now matches .NET Framework behavior. Contributes to #34697 and #27745
We recently merged PR dotnet/corefx#38465 which allows SocketsHttpHandler to use a specified 'Host:' request header as part of the SPN calculation. This PR change is available for .NET Core 3.0. As you mentioned above, .NET Framework is only able to achieve this scenario in cases where the DNS name is pre-resolved and cached. In general, both .NET Framework and .NET Core have similar lack of first-class support for S4U and related identify tokens in this scenario. Due to the upcoming timeline for .NET Core 3.0, moving this issue to Future. |
@davidsh Thanks so much for the update. I currently build my project with a patched CoreFX2.x to hack in first class S4U2Self support to SocketsHttpHandler. In my super specialized case (I don't need to handle redirects) I can probably now get away with pre-resolving the IP (along with a few other permission work-arounds) with stock CoreFX3.0 build, like i did with NetFX. However I've been mulling over for sometime what would be a reasonable non-API breaking change to turn my hack in to a real .NET feature. The best approach I've thought of is a new ICredentials implementation "S4uCredentials" or some better name. The only constructor argument is a UPN. APIs taking ICredentials can add support for it over time. The first would SocketsHttpHandler. It can check is the credential type is S4uCredentials and use it to impersonate just the SSPI credential call (as I do in my hack.). This could actually be supported on Linux easier than Windows. On Windows you must talk to LSA with LogonUser and then use SSPI under that token. On Linux there is an actual GSSAPI call specifically for S4U2Self. Do you have any thoughts on this design be accepted? |
Thanks for the design ideas! Generally speaking, it sounds like a nice simple design that could support this scenario. In terms of it being "accepted" in CoreFx, there are two parts to it. One is an API review if a new type (i.e. The second is the design/implementation/test of this. That would be in the form of a PR that could be reviewed. We welcome contributions. From a timing perspective, I think a new API/feature won't make it into .NET Core 3.0. But it certainly could for the next release, .NET 5. |
It seems like a simpler solution with no API changes may be possible. I'm going to summarize my understanding in case I've got something wrong. When you use SocketsHttpHandler under an impersonated identity, it fails to do the DNS lookup as the current identity is preventing it somehow (which seems odd as DNS is unauthenticated, but apparently the same happens on .NET Framework so I'm just going to accept that as it is). The solution being proposed is that a new type of credential is passed in which basically wraps the identity token and SocketsHttpHandler then uses that for impersonation while creating the NTAuthentication object that's used for the Negotiate auth. if (!WindowsIdentity.GetCurrent().ImpersonationLevel == TokenImpersonationLevel.None)
{
WindowsIdentity.RunImpersonated(WindowsIdentity.GetAnonymous().AccessToken,
() =>
{
DoDnsLookup();
});
}
else
{
DoDnsLookup();
} That should drop the impersonated identity to successfully do the DNS lookup, then restore it again to continue execution and wouldn't require any new API's. If the impersonation is handed off to SocketsHttpHandler as a solution to the DNS problem, what do you do when you need to impersonate the call because the HttpContent isn't a static buffer and needs the impersonation when building the request body? |
It's possible the DNS problem is due to this issue on .NET Core dotnet/corefx#38646 |
@mconnew So the problem with that approach is that this goes beyond the DNS resolution. The reason this is complicated even in NETFX is it's a core Windows OS issue. In the case of S4U2Self impersonation (aka Constrained Delegation Any Protocol in AD DS UI speak), if the calling process does not have SeTcbPrivilege*, then LogonUser API will give you a thread impersonation token with SECURITY_IMPERSONATION_LEVEL == SecurityIdentification. This is an extremely constrained access level that can't open files or registry entries. So all kinds of stuff breaks, DNS is just one of the things I wasn't able to work around in .NET Core. I had to put in a ton of workarounds just to get 1 simple HTTP request to run under this token even with NETFX. I had to preload assemblies, because the framework can't touch the filesystem to load assemblies. I had to disable all proxy capability because it can't read proxy settings from the registry. Windows/Win32 API was never really designed for anything to be done with this token other than ACL checks. It's an undocumented historical oddity (or a good Raymond Chen question) as far as I can tell that one of the things you can do with this token is use the SSPI API to bootstrap an S4U2Self Kerberos ticket.
I think a positive aspect of the S4UCredential approach is it doesn't interfere with any local impersonation or other stuff you may doing around the SocketsHttpHandler, within a stream or message handler or whatever. It completely isolates the impersonation to 1 Win32 API call that establishes the remote identity, which is really what the Credential property of SocketsHttpHandler is all about. The thing with S4U2Self, it's a Kerberos thing (created by Microsoft, but the AD people not the Windows people). You don't want or need to be impersonating the user on the local machine. The only goal is to impersonate the user on the remote machine. The local impersonation is purely an artifact of the LSA and SSPI API design in Windows. It would make more sense if the pszPrincipal argument of AcquireCredentialsHandle** worked for S4U2Self and no impersonation was needed. That would be more in line with how GSSAPI on Linux does it. In Linux S4U2Self is purely a Kerberos thing. In the entire exchange, the only Window thread impersonation actually necessary is during the call to AcquireCredentialsHandle which is happening via the NTAuthentication constructor. So... rather than try make the entire SocketsHttpHandler stack work under an Identification token, which is pretty much impossible, I'm hoping to some how introduce an elegant API to scope the impersonation down to that one call. S4UCredential seemed nice because it doesn't change the public API of SocketsHttpHandler at all. Setting an @davidsh * Essentially you are running as SYSTEM. It allows you to arbitrarily impersonate any SID within the bounds of the local machine. |
@wpbrown, I understand now and agree about not trying to make SocketsHttpHandler work in the face of a "hostile" identity. But I do have another suggestion for you. As this won't be going into .NET Core 3.0 but would have to wait until 3.1, there is an open issue (#36896) delayed until 3.1 which I expect will get done due to a dependency in ASP.NET Core which could enable an alternative solution. |
Due to lack of recent activity, this issue has been marked as a candidate for backlog cleanup. It will be closed if no further activity occurs within 14 more days. Any new comment (by anyone, not necessarily the author) will undo this process. This process is part of our issue cleanup automation. |
Creating a WindowsIdentity with the UPN argument to logon and impersonate a user at a remote computer (Constrained delegation for 'any protocol') is working as expected in NETFX. With NETFX no special privileges are required (e.g. SeTcbPrivilege, SeImpersonatePrivilege). To ensure NETFX is not enabling these privileges, I did not grant the rights for the NETFX test.
However in COREFX, it only works when the process enables SeTcbPrivilege.
In .NET Core the failure mode depends on how the network call is being made:
Minimal Reproduction Case:
Versions: .NET Core 2.2 vs .NET Framework 4.6.1
Program: testimper
Sample Output: log
Environment
I have created an ARM template that will deploy a fully functioning AD DS environment for testing this issue here.
Notes
My guess of why the impersonation level is 'Identification' in the working NETFX test case: that is all the token is good for on the local machine. The token can be used for impersonation at the remote machine as configured in the directory.
Related:
The text was updated successfully, but these errors were encountered: