Move to main query pack

This commit is contained in:
Joe Farebrother
2025-10-20 10:18:24 +01:00
parent 71ad5a340f
commit a87a03cfa8
11 changed files with 590 additions and 0 deletions

View File

@@ -0,0 +1,195 @@
/**
* Provides classes and predicates for detecting insecure cookies.
*/
import csharp
import semmle.code.csharp.frameworks.microsoft.AspNetCore
/**
* Holds if the expression is a variable with a sensitive name.
*/
predicate isCookieWithSensitiveName(Expr cookieExpr) {
exists(DataFlow::Node sink |
AuthCookieName::flowTo(sink) and
sink.asExpr() = cookieExpr
)
}
/**
* Configuration for tracking if a variable with a sensitive name is used as an argument.
*/
private module AuthCookieNameConfig implements DataFlow::ConfigSig {
private predicate isAuthVariable(Expr expr) {
exists(string val |
(
val = expr.getValue() or
val = expr.(Access).getTarget().getName()
) and
val.regexpMatch("(?i).*(session|login|token|user|auth|credential).*") and
not val.regexpMatch("(?i).*(xsrf|csrf|forgery).*")
)
}
predicate isSource(DataFlow::Node source) { isAuthVariable(source.asExpr()) }
predicate isSink(DataFlow::Node sink) { exists(Call c | sink.asExpr() = c.getAnArgument()) }
}
/**
* Tracks if a variable with a sensitive name is used as an argument.
*/
private module AuthCookieName = DataFlow::Global<AuthCookieNameConfig>;
/**
* Configuration module tracking creation of `CookieOptions` to `IResponseCookies.Append(String, String, CookieOptions)`
* calls as a third parameter.
*/
private module CookieOptionsTrackingConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source.asExpr().(ObjectCreation).getType() instanceof MicrosoftAspNetCoreHttpCookieOptions
}
predicate isSink(DataFlow::Node sink) {
exists(MicrosoftAspNetCoreHttpResponseCookies iResponse, MethodCall mc |
iResponse.getAppendMethod() = mc.getTarget() and
mc.getArgument(2) = sink.asExpr()
)
}
}
/**
* Tracking creation of `CookieOptions` to `IResponseCookies.Append(String, String, CookieOptions)`
* calls as a third parameter.
*/
module CookieOptionsTracking = DataFlow::Global<CookieOptionsTrackingConfig>;
/**
* Looks for property value of `CookiePolicyOptions` passed to `app.UseCookiePolicy` in `Startup.Configure`.
*/
Expr getAValueForCookiePolicyProp(string prop) {
exists(Method m, MethodCall mc, ObjectCreation oc, Expr val |
m.getName() = "Configure" and
m.getDeclaringType().getName() = "Startup" and
m.getBody().getAChild+() = mc and
mc.getTarget() =
any(MicrosoftAspNetCoreBuilderCookiePolicyAppBuilderExtensions e).getUseCookiePolicyMethod() and
oc.getType() instanceof MicrosoftAspNetCoreBuilderCookiePolicyOptions and
getAValueForProp(oc, _, prop) = val and
result = val
)
}
/**
* A simplistic points-to alternative: given an object creation and a property name, get the values that property can be assigned.
*
* Assumptions:
* - we don't reassign the variable that the creation is stored in
* - we always access the creation through the same variable it is initially assigned to
*
* This should cover most typical patterns...
*/
Expr getAValueForProp(ObjectCreation create, Assignment a, string prop) {
// values set in object init
exists(MemberInitializer init, Expr src, PropertyAccess pa |
a.getLValue() = pa and
pa.getTarget().hasName(prop) and
init = create.getInitializer().(ObjectInitializer).getAMemberInitializer() and
init.getLValue() = pa and
DataFlow::localExprFlow(src, init.getRValue()) and
result = src
)
or
// values set on var that create is assigned to
exists(Expr src, PropertyAccess pa |
a.getLValue() = pa and
pa.getTarget().hasName(prop) and
DataFlow::localExprFlow(create, pa.getQualifier()) and
DataFlow::localExprFlow(src, a.getRValue()) and
result = src
)
}
/**
* Checks if the given property was explicitly set to a value.
*/
predicate isPropertySet(ObjectCreation oc, string prop) { exists(getAValueForProp(oc, _, prop)) }
private signature string propertyName();
/**
* Configuration for tracking if a callback used in `OnAppendCookie` sets a cookie property to `true`.
*/
private module OnAppendCookieTrackingConfig<propertyName/0 getPropertyName> implements
DataFlow::ConfigSig
{
/**
* Specifies the cookie property name to track.
*/
predicate isSource(DataFlow::Node source) {
exists(PropertyWrite pw, Assignment delegateAssign, Callable c |
pw.getProperty().getName() = "OnAppendCookie" and
pw.getProperty().getDeclaringType() instanceof MicrosoftAspNetCoreBuilderCookiePolicyOptions and
delegateAssign.getLValue() = pw and
(
exists(LambdaExpr lambda |
delegateAssign.getRValue() = lambda and
lambda = c
)
or
exists(DelegateCreation delegate |
delegateAssign.getRValue() = delegate and
delegate.getArgument().(CallableAccess).getTarget() = c
)
) and
c.getParameter(0) = source.asParameter()
)
}
predicate isSink(DataFlow::Node sink) {
exists(PropertyWrite pw, Assignment a |
pw.getProperty().getDeclaringType() instanceof MicrosoftAspNetCoreHttpCookieOptions and
pw.getProperty().getName() = getPropertyName() and
a.getLValue() = pw and
exists(Expr val |
DataFlow::localExprFlow(val, a.getRValue()) and
val.getValue() = "true"
) and
sink.asExpr() = pw.getQualifier()
)
}
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
node2.asExpr() =
any(PropertyRead pr |
pr.getQualifier() = node1.asExpr() and
pr.getProperty().getDeclaringType() instanceof
MicrosoftAspNetCoreCookiePolicyAppendCookieContext
)
}
}
private string getPropertyNameSecure() { result = "Secure" }
/**
* Configuration module for tracking if a callback used in `OnAppendCookie` sets `Secure` to `true`.
*/
private module OnAppendCookieSecureTrackingConfig =
OnAppendCookieTrackingConfig<getPropertyNameSecure/0>;
/**
* Tracks if a callback used in `OnAppendCookie` sets `Secure` to `true`.
*/
module OnAppendCookieSecureTracking = DataFlow::Global<OnAppendCookieSecureTrackingConfig>;
private string getPropertyNameHttpOnly() { result = "HttpOnly" }
/**
* Configuration module for tracking if a callback used in `OnAppendCookie` sets `HttpOnly` to `true`.
*/
private module OnAppendCookieHttpOnlyTrackingConfig =
OnAppendCookieTrackingConfig<getPropertyNameHttpOnly/0>;
/**
* Tracks if a callback used in `OnAppendCookie` sets `HttpOnly` to `true`.
*/
module OnAppendCookieHttpOnlyTracking = DataFlow::Global<OnAppendCookieHttpOnlyTrackingConfig>;

View File

@@ -0,0 +1,51 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Cookies without <code>HttpOnly</code> flag are accessible to JavaScript running in the same origin. In case of
Cross-Site Scripting (XSS) vulnerability the cookie can be stolen by malicious script.
</p>
</overview>
<recommendation>
<p>
Protect sensitive cookies, such as related to authentication, by setting <code>HttpOnly</code> to <code>true</code> to make
them not accessible to JavaScript. In ASP.NET case it is also possible to set the attribute via <code>&lt;httpCookies&gt;</code> element
of <code>web.config</code> with the attribute <code>httpOnlyCookies="true"</code>.
</p>
</recommendation>
<example>
<p>
In the example below <code>Microsoft.AspNetCore.Http.CookieOptions.HttpOnly</code> is set to <code>true</code>.
</p>
<sample src="httponlyflagcore.cs" />
<p>
In the following example <code>CookiePolicyOptions</code> are set programmatically to configure defaults.
</p>
<sample src="cookiepolicyoptions.cs" />
<p>
In the example below <code>System.Web.HttpCookie.HttpOnly</code> is set to <code>true</code>.
</p>
<sample src="httponlyflag.cs" />
</example>
<references>
<li><a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.cookieoptions.httponly">CookieOptions.HttpOnly Property,</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie">Set-Cookie</a> Header,</li>
<li><a href="https://msdn.microsoft.com/en-us/library/system.web.httpcookie.httponly(v=vs.110).aspx">HttpCookie.HttpOnly Property,</a></li>
<li><a href="https://msdn.microsoft.com/library/ms228262%28v=vs.100%29.aspx">httpCookies Element,</a></li>
</references>
</qhelp>

View File

@@ -0,0 +1,119 @@
/**
* @name 'HttpOnly' attribute is not set to true
* @description Omitting the 'HttpOnly' attribute for security sensitive data allows
* malicious JavaScript to steal it in case of XSS vulnerability. Always set
* 'HttpOnly' to 'true' to authentication related cookie to make it
* not accessible by JavaScript.
* @kind problem
* @problem.severity warning
* @precision high
* @id cs/web/cookie-httponly-not-set
* @tags security
* experimental
* external/cwe/cwe-1004
*/
import csharp
import semmle.code.asp.WebConfig
import semmle.code.csharp.frameworks.system.Web
import semmle.code.csharp.frameworks.microsoft.AspNetCore
import semmle.code.csharp.security.auth.SecureCookies
predicate cookieAppendHttpOnlyByDefault() {
// default is set to `Always`
getAValueForCookiePolicyProp("HttpOnly").getValue() = "1"
or
// there is an `OnAppendCookie` callback that sets `HttpOnly` to true
not OnAppendCookieHttpOnlyTracking::flowTo(_)
}
predicate httpOnlyFalse(ObjectCreation oc) {
exists(Assignment a |
getAValueForProp(oc, a, "HttpOnly") = a.getRValue() and
a.getRValue().getValue() = "false"
)
}
predicate httpOnlyFalseOrNotSet(ObjectCreation oc) {
httpOnlyFalse(oc)
or
not isPropertySet(oc, "HttpOnly")
}
predicate nonHttpOnlyCookieOptionsCreation(ObjectCreation oc, MethodCall append) {
// `HttpOnly` property in `CookieOptions` passed to IResponseCookies.Append(...) wasn't set
oc.getType() instanceof MicrosoftAspNetCoreHttpCookieOptions and
httpOnlyFalseOrNotSet(oc) and
exists(DataFlow::Node creation, DataFlow::Node sink |
CookieOptionsTracking::flow(creation, sink) and
creation.asExpr() = oc and
sink.asExpr() = append.getArgument(2)
)
}
predicate nonHttpOnlySensitiveCookieCreation(ObjectCreation oc) {
oc.getType() instanceof SystemWebHttpCookie and
isCookieWithSensitiveName(oc.getArgument(0)) and
(
httpOnlyFalse(oc)
or
// the property wasn't explicitly set, so a default value from config is used
not isPropertySet(oc, "HttpOnly") and
// the default in config is not set to `true`
not exists(XmlElement element |
element instanceof HttpCookiesElement and
element.(HttpCookiesElement).isHttpOnlyCookies()
)
)
}
predicate sensitiveCookieAppend(MethodCall mc) {
exists(MicrosoftAspNetCoreHttpResponseCookies iResponse |
iResponse.getAppendMethod() = mc.getTarget() and
isCookieWithSensitiveName(mc.getArgument(0))
)
}
predicate nonHttpOnlyCookieCall(Call c) {
(
not cookieAppendHttpOnlyByDefault() and
exists(MethodCall mc |
sensitiveCookieAppend(mc) and
(
nonHttpOnlyCookieOptionsCreation(c, mc)
or
// IResponseCookies.Append(String, String) was called, `HttpOnly` is set to `false` by default
mc = c and
mc.getNumberOfArguments() < 3
)
)
or
nonHttpOnlySensitiveCookieCreation(c)
)
}
predicate nonHttpOnlyPolicyAssignment(Assignment a, Expr val) {
val.getValue() = "false" and
exists(PropertyWrite pw |
(
pw.getProperty().getDeclaringType() instanceof MicrosoftAspNetCoreHttpCookieBuilder or
pw.getProperty().getDeclaringType() instanceof
MicrosoftAspNetCoreAuthenticationCookiesCookieAuthenticationOptions
) and
pw.getProperty().getName() = "HttpOnly" and
a.getLValue() = pw and
DataFlow::localExprFlow(val, a.getRValue())
)
}
from Expr httpOnlySink
where
(
nonHttpOnlyCookieCall(httpOnlySink)
or
exists(Assignment a |
httpOnlySink = a.getRValue() and
nonHttpOnlyPolicyAssignment(a, _)
)
)
select httpOnlySink, "Cookie attribute 'HttpOnly' is not set to true."

View File

@@ -0,0 +1,12 @@
public class Startup
{
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseCookiePolicy(new CookiePolicyOptions()
{
Secure = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always,
HttpOnly = Microsoft.AspNetCore.CookiePolicy.HttpOnlyPolicy.Always
});
}
}

View File

@@ -0,0 +1,7 @@
class MyController : Controller
{
void Login()
{
var cookie = new System.Web.HttpCookie("cookieName") { HttpOnly = true };
}
}

View File

@@ -0,0 +1,8 @@
class MyController : Controller
{
void Login()
{
var cookieOptions = new Microsoft.AspNetCore.Http.CookieOptions() { HttpOnly = true };
Response.Cookies.Append("auth", "secret", cookieOptions);
}
}

View File

@@ -0,0 +1,55 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Sensitive data that is transmitted using HTTP is vulnerable to being read by a third party. By default,
cookies are sent via HTTP, not HTTPS.
</p>
</overview>
<recommendation>
<p>
In ASP.NET case when using cookies ensure that HTTPS is used by setting the property <code>Microsoft.AspNetCore.Http.CookieOptions.Secure</code> to <code>true</code>.
</p>
<p>
In ASP.NET Core case when using cookies, ensure that HTTPS is used, either via the <code>&lt;forms&gt;</code> attribute above, or
the <code>&lt;httpCookies&gt;</code> element, with the attribute <code>requireSSL="true"</code>. It is also possible to require cookies
to use HTTPS programmatically, by setting the property <code>System.Web.HttpCookie.Secure</code> to <code>true</code>.
</p>
</recommendation>
<example>
<p>
In the example below <code>Microsoft.AspNetCore.Http.CookieOptions.Secure</code> is set to <code>true</code> programmatically.
</p>
<sample src="secureflagcore.cs" />
<p>
In the following example <code>CookiePolicyOptions</code> are set programmatically to configure defaults.
</p>
<sample src="cookiepolicyoptions.cs" />
<p>
In the example below <code>System.Web.HttpCookie.Secure</code> is set to <code>true</code> programmatically.
</p>
<sample src="secureflag.cs" />
</example>
<references>
<li><a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.cookieoptions.secure">CookieOptions.Secure Property,</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie">Set-Cookie</a> Header,</li>
<li><a href="https://msdn.microsoft.com/en-us/library/system.web.security.formsauthentication.requiressl(v=vs.110).aspx">FormsAuthentication.RequireSSL Property,</a></li>
<li><a href="https://msdn.microsoft.com/en-us/library/1d3t3c61(v=vs.100).aspx">forms Element for authentication,</a></li>
<li><a href="https://msdn.microsoft.com/library/ms228262%28v=vs.100%29.aspx">httpCookies Element,</a></li>
</references>
</qhelp>

View File

@@ -0,0 +1,116 @@
/**
* @name 'Secure' attribute is not set to true
* @description Omitting the 'Secure' attribute allows data to be transmitted insecurely
* using HTTP. Always set 'Secure' to 'true' to ensure that HTTPS
* is used at all times.
* @kind problem
* @problem.severity error
* @precision high
* @id cs/web/cookie-secure-not-set
* @tags security
* external/cwe/cwe-319
* external/cwe/cwe-614
*/
import csharp
import semmle.code.asp.WebConfig
import semmle.code.csharp.frameworks.system.Web
import semmle.code.csharp.frameworks.microsoft.AspNetCore
import semmle.code.csharp.security.auth.SecureCookies
predicate cookieAppendSecureByDefault() {
// default is set to `Always` or `SameAsRequest`
(
getAValueForCookiePolicyProp("Secure").getValue() = "0" or
getAValueForCookiePolicyProp("Secure").getValue() = "1"
)
or
//callback `OnAppendCookie` that sets `Secure` to true
OnAppendCookieSecureTracking::flowTo(_)
}
predicate secureFalse(ObjectCreation oc) {
exists(Assignment a |
getAValueForProp(oc, a, "Secure") = a.getRValue() and
a.getRValue().getValue() = "false"
)
}
predicate secureFalseOrNotSet(ObjectCreation oc) {
secureFalse(oc)
or
not isPropertySet(oc, "Secure")
}
predicate insecureCookieOptionsCreation(ObjectCreation oc) {
// `Secure` property in `CookieOptions` passed to IResponseCookies.Append(...) wasn't set
oc.getType() instanceof MicrosoftAspNetCoreHttpCookieOptions and
secureFalseOrNotSet(oc) and
exists(DataFlow::Node creation |
CookieOptionsTracking::flow(creation, _) and
creation.asExpr() = oc
)
}
predicate insecureCookieAppend(Expr sink) {
// IResponseCookies.Append(String, String) was called, `Secure` is set to `false` by default
exists(MethodCall mc, MicrosoftAspNetCoreHttpResponseCookies iResponse |
mc = sink and
iResponse.getAppendMethod() = mc.getTarget() and
mc.getNumberOfArguments() < 3
)
}
predicate insecureCookieCreation(ObjectCreation oc) {
oc.getType() instanceof SystemWebHttpCookie and
(
secureFalse(oc)
or
// `Secure` property in `System.Web.HttpCookie` wasn't set, so a default value from config is used
isPropertySet(oc, "Secure") and
// the default in config is not set to `true`
// the `exists` below covers the `cs/web/requiressl-not-set`
not exists(XmlElement element |
element instanceof FormsElement and
element.(FormsElement).isRequireSsl()
or
element instanceof HttpCookiesElement and
element.(HttpCookiesElement).isRequireSsl()
)
)
}
predicate insecureCookieCall(Call c) {
not cookieAppendSecureByDefault() and
(
insecureCookieOptionsCreation(c)
or
insecureCookieAppend(c)
)
or
insecureCookieCreation(c)
}
predicate insecureSecurePolicyAssignment(Assignment a, Expr val) {
exists(PropertyWrite pw |
(
pw.getProperty().getDeclaringType() instanceof MicrosoftAspNetCoreHttpCookieBuilder or
pw.getProperty().getDeclaringType() instanceof
MicrosoftAspNetCoreAuthenticationCookiesCookieAuthenticationOptions
) and
pw.getProperty().getName() = "SecurePolicy" and
a.getLValue() = pw and
DataFlow::localExprFlow(val, a.getRValue()) and
val.getValue() = "2" // None
)
}
from Expr secureSink
where
insecureCookieCall(secureSink)
or
exists(Assignment a |
secureSink = a.getRValue() and
insecureSecurePolicyAssignment(a, _)
)
select secureSink, "Cookie attribute 'Secure' is not set to true."

View File

@@ -0,0 +1,12 @@
public class Startup
{
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseCookiePolicy(new CookiePolicyOptions()
{
Secure = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always,
HttpOnly = Microsoft.AspNetCore.CookiePolicy.HttpOnlyPolicy.Always
});
}
}

View File

@@ -0,0 +1,7 @@
class MyController : Controller
{
void Login()
{
var cookie = new System.Web.HttpCookie("cookieName") { Secure = true };
}
}

View File

@@ -0,0 +1,8 @@
class MyController : Controller
{
void Login()
{
var cookieOptions = new Microsoft.AspNetCore.Http.CookieOptions() { Secure = true };
Response.Cookies.Append("auth", "secret", cookieOptions);
}
}