Review cookies created without HttpOnly and Secure flags

Rule ND3117 Security

Matches methods that instantiate CookieOptions, CookieBuilder or HttpCookie without assigning both HttpOnly and Secure (or SecurePolicy), and methods calling Response.Cookies.Append(name, value) without options while the application does not use UseCookiePolicy(). The rule cannot see the assigned value: it reports the missing assignment, which in practice means the insecure default.

This is CWE-1004 Sensitive Cookie Without 'HttpOnly' Flag and CWE-614 Sensitive Cookie in HTTPS Session Without 'Secure' Attribute.

Flagged Code
public void RememberSession(string token)
{
   var options = new CookieOptions { Expires = DateTimeOffset.UtcNow.AddDays(7) };
   Response.Cookies.Append("session", token, options);  // flagged: HttpOnly and Secure not set
}
Fixed Code
public void RememberSession(string token)
{
   var options = new CookieOptions
   {
      Expires = DateTimeOffset.UtcNow.AddDays(7),
      HttpOnly = true,
      Secure = true,
      SameSite = SameSiteMode.Strict
   };
   Response.Cookies.Append("session", token, options);
}

How to fix

Set HttpOnly = true, Secure = true (or SecurePolicy = CookieSecurePolicy.Always) and a SameSite mode on every cookie carrying session or identity data, or apply them globally with app.UseCookiePolicy(new CookiePolicyOptions { HttpOnly = HttpOnlyPolicy.Always, Secure = CookieSecurePolicy.Always }). Suppress the issue for cookies that scripts legitimately need to read (e.g. a UI preference).

CQLinq source

This rule is written in CQLinq, C# LINQ over the NDepend code model. It ships with every new NDepend project: you can read it, adapt it, or use it as a starting point for your own custom rules.

// <Name>Review cookies created without HttpOnly and Secure flags</Name>
// <Id>ND3117:ReviewCookiesCreatedWithoutHttpOnlyAndSecureFlags</Id>
warnif count > 0

// Third-party members are part of the code model only when the application uses them.
let cookieTypes = ThirdParty.Types.WithFullNameIn(
  "Microsoft.AspNetCore.Http.CookieOptions",
  "Microsoft.AspNetCore.Http.CookieBuilder",
  "System.Web.HttpCookie").ToArray()
where cookieTypes.Any()

let cookieCtors = cookieTypes.ChildMethods().Where(m => m.IsConstructor).ToHashSetEx()
let httpOnlySetters = cookieTypes.ChildMethods().Where(m => m.SimpleName == "set_HttpOnly").ToHashSetEx()
let secureSetters = cookieTypes.ChildMethods().Where(m => m.SimpleNameEqualsAny("set_Secure", "set_SecurePolicy")).ToHashSetEx()

// response.Cookies.Append(name, value) without options gets neither flag,
// unless a cookie policy middleware applies them globally.
let appUsesCookiePolicy = Application.Methods.Any(m =>
   m.IsUsing("Microsoft.AspNetCore.Builder.CookiePolicyAppBuilderExtensions.UseCookiePolicy(IApplicationBuilder)".AllowNoMatch()) ||
   m.IsUsing("Microsoft.AspNetCore.Builder.CookiePolicyAppBuilderExtensions.UseCookiePolicy(IApplicationBuilder,CookiePolicyOptions)".AllowNoMatch()))
let appendWithoutOptions = appUsesCookiePolicy ? new IMethod[0] :
   ThirdParty.Types.WithFullName("Microsoft.AspNetCore.Http.IResponseCookies")
     .ChildMethods().Where(m => m.SimpleName == "Append" && m.NbParameters == 2).ToArray()

from m in Application.Methods
let createsCookie = m.MethodsCalled.Intersect(cookieCtors).Any()
let missingHttpOnly = createsCookie && !m.MethodsCalled.Intersect(httpOnlySetters).Any()
let missingSecure = createsCookie && !m.MethodsCalled.Intersect(secureSetters).Any()
let appendsWithoutOptions = m.MethodsCalled.Intersect(appendWithoutOptions).Any()
where missingHttpOnly || missingSecure || appendsWithoutOptions
select new {
  m,
  missing = appendsWithoutOptions ? "options (HttpOnly and Secure)" :
            missingHttpOnly && missingSecure ? "HttpOnly and Secure" :
            missingHttpOnly ? "HttpOnly" : "Secure",
  Debt = 10.ToMinutes().ToDebt(),
  Severity = Severity.Medium
}

//<Expl>
// The *{0}* creates a cookie without setting {1}. Without *HttpOnly* a
// script injected in the page can read the cookie; without *Secure* it is
// sent in clear over HTTP.
//</Expl>

//<Desc>
// Matches methods that instantiate *CookieOptions*, *CookieBuilder* or
// *HttpCookie* without assigning both *HttpOnly* and *Secure* (or
// *SecurePolicy*), and methods calling *Response.Cookies.Append(name, value)*
// without options while the application does not use
// *UseCookiePolicy()*. The rule cannot see the assigned value: it reports
// the missing assignment, which in practice means the insecure default.
//
// This is CWE-1004 *Sensitive Cookie Without 'HttpOnly' Flag* and CWE-614
// *Sensitive Cookie in HTTPS Session Without 'Secure' Attribute*.
//
// **Flagged Code**
// ```csharp
// public void RememberSession(string token)
// {
//    var options = new CookieOptions { Expires = DateTimeOffset.UtcNow.AddDays(7) };
//    Response.Cookies.Append("session", token, options);  // flagged: HttpOnly and Secure not set
// }
// ```
//
// **Fixed Code**
// ```csharp
// public void RememberSession(string token)
// {
//    var options = new CookieOptions
//    {
//       Expires = DateTimeOffset.UtcNow.AddDays(7),
//       HttpOnly = true,
//       Secure = true,
//       SameSite = SameSiteMode.Strict
//    };
//    Response.Cookies.Append("session", token, options);
// }
// ```
//</Desc>

//<HowToFix>
// Set *HttpOnly = true*, *Secure = true* (or *SecurePolicy = CookieSecurePolicy.Always*)
// and a *SameSite* mode on every cookie carrying session or identity data,
// or apply them globally with *app.UseCookiePolicy(new CookiePolicyOptions { HttpOnly = HttpOnlyPolicy.Always, Secure = CookieSecurePolicy.Always })*.
// Suppress the issue for cookies that scripts legitimately need to read
// (e.g. a UI preference).
//</HowToFix>

Security: all 39 items