mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
Implement cookie write concepts and httponly query
This commit is contained in:
@@ -380,4 +380,96 @@ module Http {
|
|||||||
/** Gets a node that is used in a check that is tested before this handler is run. */
|
/** Gets a node that is used in a check that is tested before this handler is run. */
|
||||||
predicate guardedBy(DataFlow::Node check) { super.guardedBy(check) }
|
predicate guardedBy(DataFlow::Node check) { super.guardedBy(check) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Provides a class for modelling HTTP response cookie writes. */
|
||||||
|
module CookieWrite {
|
||||||
|
/**
|
||||||
|
* An write of an HTTP Cookie to an HTTP response.
|
||||||
|
*
|
||||||
|
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||||
|
* extend `HTTP::CookieWrite` instead.
|
||||||
|
*/
|
||||||
|
abstract class Range extends DataFlow::Node {
|
||||||
|
/** Gets the name of the cookie written. */
|
||||||
|
abstract DataFlow::Node getName();
|
||||||
|
|
||||||
|
/** Gets the value of the cookie written. */
|
||||||
|
abstract DataFlow::Node getValue();
|
||||||
|
|
||||||
|
/** Gets the `Secure` attribute of the cookie written. */
|
||||||
|
abstract DataFlow::Node getSecure();
|
||||||
|
|
||||||
|
/** Gets the `HttpOnly` attribute of the cookie written. */
|
||||||
|
abstract DataFlow::Node getHttpOnly();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An write of an HTTP Cookie to an HTTP response.
|
||||||
|
*
|
||||||
|
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||||
|
* extend `HTTP::CookieWrite::Range` instead.
|
||||||
|
*/
|
||||||
|
class CookieWrite extends DataFlow::Node instanceof CookieWrite::Range {
|
||||||
|
/** Gets the name of the cookie written. */
|
||||||
|
DataFlow::Node getName() { result = super.getName() }
|
||||||
|
|
||||||
|
/** Gets the value of the cookie written. */
|
||||||
|
DataFlow::Node getValue() { result = super.getValue() }
|
||||||
|
|
||||||
|
/** Gets the `Secure` attribute of the cookie written. */
|
||||||
|
DataFlow::Node getSecure() { result = super.getSecure() }
|
||||||
|
|
||||||
|
/** Gets the `HttpOnly` attribute of the cookie written. */
|
||||||
|
DataFlow::Node getHttpOnly() { result = super.getHttpOnly() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Provides a class for modelling the options of an HTTP cookie. */
|
||||||
|
module CookieOptions {
|
||||||
|
/**
|
||||||
|
* An HTTP Cookie object.
|
||||||
|
*
|
||||||
|
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||||
|
* extend `HTTP::CookieOptions` instead.
|
||||||
|
*/
|
||||||
|
abstract class Range extends DataFlow::Node {
|
||||||
|
/** Gets the node representing the cookie object for the options being set. */
|
||||||
|
abstract DataFlow::Node getCookieOutput();
|
||||||
|
|
||||||
|
/** Gets the name of the cookie represented. */
|
||||||
|
abstract DataFlow::Node getName();
|
||||||
|
|
||||||
|
/** Gets the value of the cookie represented. */
|
||||||
|
abstract DataFlow::Node getValue();
|
||||||
|
|
||||||
|
/** Gets the `Secure` attribute of the cookie represented. */
|
||||||
|
abstract DataFlow::Node getSecure();
|
||||||
|
|
||||||
|
/** Gets the `HttpOnly` attribute of the cookie represented. */
|
||||||
|
abstract DataFlow::Node getHttpOnly();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An HTTP Cookie.
|
||||||
|
*
|
||||||
|
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||||
|
* extend `HTTP::CookieOptions::Range` instead.
|
||||||
|
*/
|
||||||
|
class CookieOptions extends DataFlow::Node instanceof CookieOptions::Range {
|
||||||
|
/** Gets the node representing the cookie object for the options being set. */
|
||||||
|
DataFlow::Node getCookieOutput() { result = super.getCookieOutput() }
|
||||||
|
|
||||||
|
/** Gets the name of the cookie represented. */
|
||||||
|
DataFlow::Node getName() { result = super.getName() }
|
||||||
|
|
||||||
|
/** Gets the value of the cookie represented. */
|
||||||
|
DataFlow::Node getValue() { result = super.getValue() }
|
||||||
|
|
||||||
|
/** Gets the `Secure` attribute of the cookie represented. */
|
||||||
|
DataFlow::Node getSecure() { result = super.getSecure() }
|
||||||
|
|
||||||
|
/** Gets the `HttpOnly` attribute of the cookie represented. */
|
||||||
|
DataFlow::Node getHttpOnly() { result = super.getHttpOnly() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -293,4 +293,38 @@ module NetHttp {
|
|||||||
|
|
||||||
override DataFlow::Node getAPathArgument() { result = this.getArgument(2) }
|
override DataFlow::Node getAPathArgument() { result = this.getArgument(2) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CookieWrite extends Http::CookieWrite::Range, DataFlow::CallNode {
|
||||||
|
CookieWrite() { this.getTarget().hasQualifiedName(package("net/http", ""), "SetCookie") }
|
||||||
|
|
||||||
|
override DataFlow::Node getName() { result = this.getArgument(1) }
|
||||||
|
|
||||||
|
override DataFlow::Node getValue() { result = this.getArgument(1) }
|
||||||
|
|
||||||
|
override DataFlow::Node getSecure() { result = this.getArgument(1) }
|
||||||
|
|
||||||
|
override DataFlow::Node getHttpOnly() { result = this.getArgument(1) }
|
||||||
|
}
|
||||||
|
|
||||||
|
class CookieFieldWrite extends Http::CookieOptions::Range {
|
||||||
|
Write w;
|
||||||
|
Field f;
|
||||||
|
DataFlow::Node written;
|
||||||
|
string fieldName;
|
||||||
|
|
||||||
|
CookieFieldWrite() {
|
||||||
|
f.hasQualifiedName(package("net/http", ""), "Cookie", fieldName) and
|
||||||
|
w.writesField(this, f, written)
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getCookieOutput() { result = this }
|
||||||
|
|
||||||
|
override DataFlow::Node getName() { fieldName = "Name" and result = written }
|
||||||
|
|
||||||
|
override DataFlow::Node getValue() { fieldName = "Value" and result = written }
|
||||||
|
|
||||||
|
override DataFlow::Node getSecure() { fieldName = "Secure" and result = written }
|
||||||
|
|
||||||
|
override DataFlow::Node getHttpOnly() { fieldName = "HttpOnly" and result = written }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
79
go/ql/lib/semmle/go/security/SecureCookies.qll
Normal file
79
go/ql/lib/semmle/go/security/SecureCookies.qll
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/** Provides classes and predicates for identifying HTTP cookies with insecure attributes. */
|
||||||
|
|
||||||
|
import go
|
||||||
|
import semmle.go.concepts.HTTP
|
||||||
|
import semmle.go.dataflow.DataFlow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if the expression or its value has a sensitive name
|
||||||
|
*/
|
||||||
|
private predicate isSensitiveExpr(Expr expr, string val) {
|
||||||
|
(
|
||||||
|
val = expr.getStringValue() or
|
||||||
|
val = expr.(Name).getTarget().getName()
|
||||||
|
) and
|
||||||
|
val.regexpMatch("(?i).*(session|login|token|user|auth|credential).*") and
|
||||||
|
not val.regexpMatch("(?i).*(xsrf|csrf|forgery).*")
|
||||||
|
}
|
||||||
|
|
||||||
|
private module SensitiveCookieNameConfig implements DataFlow::ConfigSig {
|
||||||
|
predicate isSource(DataFlow::Node source) { isSensitiveExpr(source.asExpr(), _) }
|
||||||
|
|
||||||
|
predicate isSink(DataFlow::Node sink) { exists(Http::CookieWrite cw | sink = cw.getName()) }
|
||||||
|
|
||||||
|
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||||
|
exists(Http::CookieOptions co | co.getName() = pred and co.getCookieOutput() = succ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tracks flow from sensitive names to HTTP cookie writes. */
|
||||||
|
module SensitiveCookieNameFlow = DataFlow::Global<SensitiveCookieNameConfig>;
|
||||||
|
|
||||||
|
private module BooleanCookieSecureConfig implements DataFlow::ConfigSig {
|
||||||
|
predicate isSource(DataFlow::Node source) { exists(source.asExpr().getBoolValue()) }
|
||||||
|
|
||||||
|
predicate isSink(DataFlow::Node sink) { exists(Http::CookieWrite cw | sink = cw.getSecure()) }
|
||||||
|
|
||||||
|
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||||
|
exists(Http::CookieOptions co | co.getSecure() = pred and co.getCookieOutput() = succ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tracks flow from boolean expressions to the `Secure` attribute HTTP cookie writes. */
|
||||||
|
module BooleanCookieSecureFlow = DataFlow::Global<BooleanCookieSecureConfig>;
|
||||||
|
|
||||||
|
private module BooleanCookieHttpOnlyConfig implements DataFlow::ConfigSig {
|
||||||
|
predicate isSource(DataFlow::Node source) { exists(source.asExpr().getBoolValue()) }
|
||||||
|
|
||||||
|
predicate isSink(DataFlow::Node sink) { exists(Http::CookieWrite cw | sink = cw.getHttpOnly()) }
|
||||||
|
|
||||||
|
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||||
|
exists(Http::CookieOptions co | co.getHttpOnly() = pred and co.getCookieOutput() = succ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tracks flow from boolean expressions to the `HttpOnly` attribute HTTP cookie writes. */
|
||||||
|
module BooleanCookieHttpOnlyFlow = DataFlow::Global<BooleanCookieHttpOnlyConfig>;
|
||||||
|
|
||||||
|
predicate isInsecureDefault(Http::CookieWrite cw) {
|
||||||
|
not BooleanCookieSecureFlow::flow(_, cw.getSecure())
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isNonHttpOnlyDefault(Http::CookieWrite cw) {
|
||||||
|
not BooleanCookieHttpOnlyFlow::flow(_, cw.getHttpOnly())
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isInsecureDirect(Http::CookieWrite cw, Expr boolFalse) {
|
||||||
|
BooleanCookieSecureFlow::flow(DataFlow::exprNode(boolFalse), cw.getSecure()) and
|
||||||
|
boolFalse.getBoolValue() = false
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isNonHttpOnlyDirect(Http::CookieWrite cw, Expr boolFalse) {
|
||||||
|
BooleanCookieHttpOnlyFlow::flow(DataFlow::exprNode(boolFalse), cw.getHttpOnly()) and
|
||||||
|
boolFalse.getBoolValue() = false
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isSensitiveCookie(Http::CookieWrite cw, Expr nameExpr, string name) {
|
||||||
|
SensitiveCookieNameFlow::flow(DataFlow::exprNode(nameExpr), cw.getName()) and
|
||||||
|
isSensitiveExpr(nameExpr, name)
|
||||||
|
}
|
||||||
28
go/ql/src/Security/CWE-1004/CookieWithoutHttpOnly.ql
Normal file
28
go/ql/src/Security/CWE-1004/CookieWithoutHttpOnly.ql
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* @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 go/cookie-httponly-not-set
|
||||||
|
* @tags security
|
||||||
|
* experimental
|
||||||
|
* external/cwe/cwe-1004
|
||||||
|
*/
|
||||||
|
|
||||||
|
import go
|
||||||
|
import semmle.go.security.SecureCookies
|
||||||
|
import semmle.go.concepts.HTTP
|
||||||
|
|
||||||
|
from Http::CookieWrite cw, Expr sensitiveNameExpr, string name
|
||||||
|
where
|
||||||
|
isSensitiveCookie(cw, sensitiveNameExpr, name) and
|
||||||
|
(
|
||||||
|
isNonHttpOnlyDefault(cw)
|
||||||
|
or
|
||||||
|
isNonHttpOnlyDirect(cw, _)
|
||||||
|
)
|
||||||
|
select cw, "Sensitive cookie $@ does not set HttpOnly to true", sensitiveNameExpr, name
|
||||||
Reference in New Issue
Block a user