dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.13k stars 4.7k forks source link

Unable to use HttpClient configured with UseDefaultCredentials true when the process is running under a restricted token with restricted SIDs #102692

Open davidterins opened 4 months ago

davidterins commented 4 months ago

Description

I want to run a process with a restricted set of SIDs and in that process/application use HttpClient configured with UseDefaultCredentials to do request to a backend web api with the following flow:

From process 1

  1. Call method CreateRestrictedToken (provide a list a restricted sids as parameter)
  2. Call method CreateProcessAsUser providing the restricted token for the new process

From Process 2 that was launched from process 1's step 2

  1. Use the HttpClient to call some backend web api using the Negotiate authentication scheme using the credentials of the process by configuring HttpClient with UseDefaultCredentials: true

Reproduction Steps

Create an application representing each process described and then... From process 1

  1. Call method CreateRestrictedToken
    CreateRestrictedToken(
                    Token, // <-- current token
                    0,
                    0, IntPtr.Zero,
                    0, IntPtr.Zero,
                    (uint)restrictedSidStrings.Count, restrictedSidsPtr,  // <-- Provide some valid SID data to these two parameters
                    out SafeTokenHandle restrictedToken))
  2. Call method CreateProcessAsUser providing the restricted token for the new process providing the restricted token from step 1
    CreateProcessAsUser(
                    restrictedToken,
                    appToRun,
                    IntPtr.Zero,
                    IntPtr.Zero,
                    false,
                    0,
                    IntPtr.Zero,
                    startupFolder,
                    ref startupInfo,
                    out PROCESS_INFORMATION processInfo))

From Process 2 that was launched from process 1's step 2

  1. Use the HttpClient configured with UseDefaultCredentials: true
    var handler = new HttpClientHandler();
    var httpClient = new HttpClient(handler);
    var result = await httpClient.GetAsync(url);

Expected behavior

The api request is successfully sent from the client.

Actual behavior

When calling httpClient.GetAsync(url) the following exception + stacktrace is produced

Attempting to do http request to endpoint: https://localhost:7254/weatherforecast System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception. ---> System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception. ---> System.ComponentModel.Win32Exception (0x8009030E): No credentials are available in the security package at System.Net.SSPIWrapper.AcquireCredentialsHandle(ISSPIInterface secModule, String package, CredentialUse intent, SCH_CREDENTIALS scc) at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(CredentialUse credUsage, SCH_CREDENTIALS secureCredential) at System.Net.Security.SslStreamPal.AcquireCredentialsHandleSchCredentials(SslAuthenticationOptions authOptions) at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(SslAuthenticationOptions sslAuthenticationOptions, Boolean newCredentialsRequested) --- End of inner exception stack trace --- at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(SslAuthenticationOptions sslAuthenticationOptions, Boolean newCredentialsRequested) at System.Net.Security.SslStream.AcquireCredentialsHandle(SslAuthenticationOptions sslAuthenticationOptions, Boolean newCredentialsRequested) at System.Net.Security.SslStream.AcquireClientCredentials(Byte[]& thumbPrint, Boolean newCredentialsRequested) at System.Net.Security.SslStream.GenerateToken(ReadOnlySpan1 inputBuffer, Byte[]& output) at System.Net.Security.SslStream.NextMessage(ReadOnlySpan1 incomingBuffer, ProtocolToken& token) at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](Boolean receiveFirst, Byte[] reAuthenticationData, CancellationToken cancellationToken) at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken) --- End of inner exception stack trace --- at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.AddHttp11ConnectionAsync(QueueItem queueItem) at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken) at System.Net.Http.AuthenticationHelper.SendWithAuthAsync(HttpRequestMessage request, Uri authUri, Boolean async, ICredentials credentials, Boolean preAuthenticate, Boolean isProxyAuth, Boolean doRequestAuth, HttpConnectionPool pool, CancellationToken cancellationToken) at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpClient.g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)

Regression?

Unkown

Known Workarounds

If enough security for one's use case to just rely on disdabled sids (deny only attribute), then it is possible to not specify any restricted SIDs and just pass disabled sids to the parameters of CreateRestrictedToken

CreateRestrictedToken(
                    Token, // <-- current token
                    0,
                    (uint)disabledSids.Count, disabledSidsPtr,  // <-- Provide some valid SID data to these two parameters
                    0, IntPtr.Zero,
                    0, IntPtr.Zero, // <-- Do not pass any restricted SIDs
                    out SafeTokenHandle restrictedToken))

Configuration

Which version of .NET is the code running on? - net8 (net8-windows) What OS and version, and what distro if applicable? - Windows 11 - 10.0.22621 Build 22621 What is the architecture (x64, x86, ARM, ARM64)? - x64 Do you know whether it is specific to that configuration? - No If you're using Blazor, which web browser(s) do you see this issue in? - No

Other information

No response

dotnet-policy-service[bot] commented 4 months ago

Tagging subscribers to this area: @dotnet/ncl, @bartonjs, @vcsjones See info in area-owners.md if you want to be subscribed.

rzikm commented 4 months ago

The callstack looks to be from SslStream, which handles SSL/TLS, not Negotiate. Did you try connecting to non-Negotiate endpoint?

Can you also share a minimal self-contained repro example?

wfurt commented 4 months ago

and what .NET version do you use? In Windows the "credentials" usually build down to problem with certificates. Does the TLS henshake work without the restricted token?

davidterins commented 4 months ago

@rzikm here is a repo to reproduce the issue: https://github.com/davidterins/restricted-token-experiments. I am 99% sure it worked with a non-Negotiate endpoint, was a few days ago...

@wfurt The issue does not seem to in the the restricted token per say, the issue only appears if providing "restricted sids" in the following parameter when creating a restricted token. image

rzikm commented 4 months ago

I investigated a bit and can reproduce it with SslStream only, It's weird to me that a restricted token can't create anonymous credentials (since no client certificates are at play here). I need to investigate further.

rzikm commented 4 months ago

Triage: needs further investigation, does not seem critical for 9.0

rzikm commented 2 months ago

Support ticket opened for Schannel team to investigate