Skip to content

Commit 1e1c891

Browse files
committed
Merged PR 21247: Fix partial chunked cookies 70242
# Fix partial chunked cookies MSRC # 70242: Fix exceptions and allocations when the cookie chunk count is not accurate ## Description Browsers have limits on how long cookies can be, as low as 4kb. It's common for TempData and CookieAuth to get above that limit, so cookies are split into chunks with the following format: MyCookie=chunks-3 MyCookieC1=(Base64EncodedData) MyCookieC2=(Base64EncodedData) MyCookieC3=(Base64EncodedData) Fixes MSRC # 70242 ## Customer Impact A malicious client could send `MyCookie=chunks-2147483647` without the actual cookie chunks and cause large allocations, exceptions, and excess CPU utilization on the server when it tried to read or delete that many chunks. This flaw comes from the original implementation in Microsoft.Owin, but is much worse in AspNetCore when adopted by TempData due to it automatically calling Delete if reading the cookie fails. I'll backport this to 5.0, 3.1, 2.1, and Microsoft.Owin once reviewed. ## Regression? - [ ] Yes - [x] No ## Risk - [ ] High - [ ] Medium - [x] Low Easy to reproduce and test. ## Verification - [x] Manual (required) - [x] Automated ## Packaging changes reviewed? - [ ] Yes - [ ] No - [x] N/A
1 parent df5b35f commit 1e1c891

File tree

2 files changed

+47
-5
lines changed

2 files changed

+47
-5
lines changed

src/Security/CookiePolicy/test/CookieChunkingTests.cs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ public void GetLargeChunkedCookieWithMissingChunk_ThrowingDisabled_NotReassemble
129129
public void DeleteChunkedCookieWithOptions_AllDeleted()
130130
{
131131
HttpContext context = new DefaultHttpContext();
132-
context.Request.Headers.Append("Cookie", "TestCookie=chunks-7");
132+
context.Request.Headers.Append("Cookie", "TestCookie=chunks-7;TestCookieC1=1;TestCookieC2=2;TestCookieC3=3;TestCookieC4=4;TestCookieC5=5;TestCookieC6=6;TestCookieC7=7");
133133

134134
new ChunkingCookieManager().DeleteCookie(context, "TestCookie", new CookieOptions() { Domain = "foo.com", Secure = true });
135135
var cookies = context.Response.Headers["Set-Cookie"];
@@ -147,7 +147,40 @@ public void DeleteChunkedCookieWithOptions_AllDeleted()
147147
}, cookies);
148148
}
149149

150+
[Fact]
151+
public void DeleteChunkedCookieWithMissingRequestCookies_OnlyPresentCookiesDeleted()
152+
{
153+
HttpContext context = new DefaultHttpContext();
154+
context.Request.Headers.Append("Cookie", "TestCookie=chunks-7;TestCookieC1=1;TestCookieC2=2");
155+
156+
new ChunkingCookieManager().DeleteCookie(context, "TestCookie", new CookieOptions() { Domain = "foo.com", Secure = true });
157+
var cookies = context.Response.Headers["Set-Cookie"];
158+
Assert.Equal(3, cookies.Count);
159+
Assert.Equal(new[]
160+
{
161+
"TestCookie=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/; secure",
162+
"TestCookieC1=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/; secure",
163+
"TestCookieC2=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/; secure",
164+
}, cookies);
165+
}
150166

167+
[Fact]
168+
public void DeleteChunkedCookieWithMissingRequestCookies_StopsAtMissingChunk()
169+
{
170+
HttpContext context = new DefaultHttpContext();
171+
// C3 is missing so we don't try to delete C4 either.
172+
context.Request.Headers.Append("Cookie", "TestCookie=chunks-7;TestCookieC1=1;TestCookieC2=2;TestCookieC4=4");
173+
174+
new ChunkingCookieManager().DeleteCookie(context, "TestCookie", new CookieOptions() { Domain = "foo.com", Secure = true });
175+
var cookies = context.Response.Headers["Set-Cookie"];
176+
Assert.Equal(3, cookies.Count);
177+
Assert.Equal(new[]
178+
{
179+
"TestCookie=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/; secure",
180+
"TestCookieC1=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/; secure",
181+
"TestCookieC2=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/; secure",
182+
}, cookies);
183+
}
151184

152185
[Fact]
153186
public void DeleteChunkedCookieWithOptionsAndResponseCookies_AllDeleted()

src/Shared/ChunkingCookieManager/ChunkingCookieManager.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ private static int ParseChunksCount(string? value)
103103
var chunksCount = ParseChunksCount(value);
104104
if (chunksCount > 0)
105105
{
106-
var chunks = new string[chunksCount];
106+
var chunks = new List<string>(10); // chunksCount may be wrong, don't trust it.
107107
for (var chunkId = 1; chunkId <= chunksCount; chunkId++)
108108
{
109109
var chunk = requestCookies[key + ChunkKeySuffix + chunkId.ToString(CultureInfo.InvariantCulture)];
@@ -128,7 +128,7 @@ private static int ParseChunksCount(string? value)
128128
return value;
129129
}
130130

131-
chunks[chunkId - 1] = chunk;
131+
chunks.Add(chunk);
132132
}
133133

134134
return string.Join(string.Empty, chunks);
@@ -254,13 +254,22 @@ public void DeleteCookie(HttpContext context, string key, CookieOptions options)
254254
key + "="
255255
};
256256

257-
var requestCookie = context.Request.Cookies[key];
258-
var chunks = ParseChunksCount(requestCookie);
257+
var requestCookies = context.Request.Cookies;
258+
var requestCookie = requestCookies[key];
259+
long chunks = ParseChunksCount(requestCookie);
259260
if (chunks > 0)
260261
{
261262
for (var i = 1; i <= chunks + 1; i++)
262263
{
263264
var subkey = key + ChunkKeySuffix + i.ToString(CultureInfo.InvariantCulture);
265+
266+
// Only delete cookies we received. We received the chunk count cookie so we should have received the others too.
267+
if (string.IsNullOrEmpty(requestCookies[subkey]))
268+
{
269+
chunks = i - 1;
270+
break;
271+
}
272+
264273
keys.Add(subkey + "=");
265274
}
266275
}

0 commit comments

Comments
 (0)