-
Notifications
You must be signed in to change notification settings - Fork 863
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
Mitigation of wrong cookie value separator #451
Mitigation of wrong cookie value separator #451
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like a good start. Most of my comments are about simplifying the logic and tests.
6b227ba
to
65708d0
Compare
65708d0
to
3b590b5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fix looks good. Some minor comments left on the tests.
@@ -430,6 +430,13 @@ private static void CopyRequestHeaders(HttpContext context, HttpRequestMessage d | |||
// as long as they appear in one (and only one, otherwise they would be duplicated). | |||
static void AddHeader(HttpRequestMessage request, string headerName, StringValues value) | |||
{ | |||
// HttpClient wrongly uses comma (",") instead of semi-colon (";") as a separator for Cookie headers. | |||
// To mitigate this, we concatenate them manually and put them back as a single header value. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// To mitigate this, we concatenate them manually and put them back as a single header value. | |
// To mitigate this, we concatenate them manually and put them back as a single header value. | |
// A multi-header cookie header is invalid, but we get one because of | |
// https://github.com/dotnet/aspnetcore/issues/26461 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see this change in your PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, my bad, should be there now.
} | ||
public class HttpProxyCookieTests_Http1 : HttpProxyCookieTests |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | |
public class HttpProxyCookieTests_Http1 : HttpProxyCookieTests | |
} | |
public class HttpProxyCookieTests_Http1 : HttpProxyCookieTests |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
// Ensure that CookieA is the first and CookieB the last. | ||
Assert.True(context.Request.Headers.TryGetValue(HeaderNames.Cookie, out var headerValues)); | ||
Assert.StartsWith(CookieA, headerValues); | ||
Assert.EndsWith(CookieB, headerValues); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Careful what you're asserting here, you're expecting different results for HTTP/1 and HTTP/2.
headerValues
is a StringValues, but you're passing it to StartsWith which takes a string. There's an implicit converter from StringValues to string which concatenates with commas (just like the original bug). Rather than using StartsWith, you want to check the count and value of each StringValues entry to make sure it's what you expect. E.g. Count should be 1 for HTTP/1 and 2 for HTTP/2.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added different set of asserts per context.Request.Protocol
.
{ | ||
public override HttpProtocols HttpProtocol => HttpProtocols.Http1; | ||
|
||
// Using simple TcpClient since HttpClient will always concatenate cookies with comma separator. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Using simple TcpClient since HttpClient will always concatenate cookies with comma separator. |
{ | ||
using var client = new HttpClient(); | ||
using var message = new HttpRequestMessage(HttpMethod.Get, proxyHostUri); | ||
message.Headers.Add(HeaderNames.Cookie, $"{Cookies}"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
message.Headers.Add(HeaderNames.Cookie, $"{Cookies}"); | |
message.Headers.Add(HeaderNames.Cookie, Cookies); |
HttpClient
wrongly uses comma (,
) as a separator for cookie header values: dotnet/runtime#42856. As well as Kestrel probably doesn't merge cookie header frames for HTTP/2: dotnet/aspnetcore#26461.This PR mitigates this by collecting all cookie headers while copying them. And concatenating them manually with semi-colon (
;
) before adding it as a single header to the destination request.Fixes #437