runtime: SocketsHttpHandler throws exception when authenticating proxy or server closes first 407 response
Found this bug while investigating dotnet/runtime#26397.
Many servers and proxy servers that require authentication will send ‘Connection: close’ (and close the TCP connection) for the first 407 response. This is frequently true for hardware firewalls, etc.
See: https://www.cisco.com/c/en/us/support/docs/security/web-security-appliance/117931-technote-ntml.pdf
8 The proxy FINs this TCP socket. This is correct and normal.
SocketsHttpHandler doesn’t have a problem with this for Basic or Digest schemes. But for Windows auth schemes such as Negotiate or NTLM, it will throw an exception:
System.Net.Http.HttpRequestException: Authentication failed because the connection could not be reused at System.Net.Http.HttpConnection.DrainResponseAsync(HttpResponseMessage response) in s:\GitHub\corefx\src\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\HttpConnection.cs
It is true that Windows auth schemes require the connection to stay alive during the multi-leg challenge/response between client and server (or proxy). But that is only true once the challenge/response process starts. And that is after the client responds to the first 407 and sends a ‘Proxy-Authorization’ (or ‘Authorization’) header with a based64-encoded blob with the Negotiate or NTLM scheme.
Repro code showing an authenticating proxy and resulting in an exception while trying to connect to a destination HTTP server. Note: this doesn’t repro if the destination server is HTTPS and thus CONNECT tunneling would be used.
static void Main()
{
Console.WriteLine($"(Framework: {Path.GetDirectoryName(typeof(object).Assembly.Location)})");
Socket listener = null;
// Start a "proxy" server in the background.
listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
listener.Bind(new IPEndPoint(IPAddress.Loopback, 0));
listener.Listen(int.MaxValue);
var ep = (IPEndPoint)listener.LocalEndPoint;
var proxyUri = new Uri($"http://{ep.Address}:{ep.Port}/");
Task.Run(async () =>
{
while (true)
{
Socket s = await listener.AcceptAsync();
var ignored = Task.Run(() =>
{
using (var ns = new NetworkStream(s))
using (var reader = new StreamReader(ns))
using (var writer = new StreamWriter(ns) { AutoFlush = true })
{
int request = 1;
while (true)
{
string line = null;
while (!string.IsNullOrEmpty(line = reader.ReadLine()))
{
Console.WriteLine($" [request:{request}] Server received: {line}");
}
Console.WriteLine($" Server sending response\r\n");
writer.Write(
"HTTP/1.1 407 Proxy Authentication Required\r\n" +
"Proxy-Authenticate: NEGOTIATE\r\n" +
"Proxy-Authenticate: NTLM\r\n" +
"Cache-Control: no-cache\r\n" +
"Pragma: no-cache\r\n" +
"Proxy-Connection: close\r\n" +
"Connection: close\r\n" +
"Content-Length: 0\r\n\r\n");
request++;
}
}
});
}
});
var serverUri = new Uri("http://corefx-net.cloudapp.net/echo.ashx/");
var handler = new HttpClientHandler();
handler.Proxy = new WebProxy(proxyUri);
handler.Proxy.Credentials = CredentialCache.DefaultCredentials;
using (var client = new HttpClient(handler))
{
Console.WriteLine($"Doing GET for {serverUri}");
HttpResponseMessage response = client.GetAsync(serverUri).GetAwaiter().GetResult();
}
}
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 1
- Comments: 29 (21 by maintainers)
@los93sol I’ve been able to get around that on my local dev machine by using Fiddler with Automatically Authenticate turned on.
@talanc the bug will be fixed even in 2.1.x servicing see PR dotnet/corefx#31589 - it has been already approved for 2.1.5 release. All 2.1.x changes will flow into release/2.2 automatically, it just didn’t happen yet.
@davidsh thanks – i’ve tested the latest sdk and it works – 3.0.0-preview1-26814-05
Removing label until we are ready to take to big shiproom.
This one is higher priority, @geoffkizer is working on it. We will let shiproom know once we have a fix. In the worst case 2.1.4+ 😦