mirror of
https://github.com/github/codeql.git
synced 2026-04-30 19:26:02 +02:00
port experimental cookie models to non-experimental
This commit is contained in:
@@ -13,10 +13,12 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import experimental.semmle.javascript.security.InsecureCookie::Cookie as ExperimentalCookie
|
||||
import experimental.semmle.javascript.security.InsecureCookie::Cookie as ExperimentalCookie // TODO: Remove.
|
||||
|
||||
from DataFlow::Node node
|
||||
where
|
||||
// TODO: Only for sensitive cookies? (e.g. auth cookies)
|
||||
// TODO: Give all descriptions, qlhelp, qldocs, an overhaul. Consider precisions, severity, cwes.
|
||||
exists(ExperimentalCookie::CookieWrite cookie | cookie = node |
|
||||
cookie.isSensitive() and not cookie.isHttpOnly()
|
||||
)
|
||||
@@ -11,7 +11,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import experimental.semmle.javascript.security.InsecureCookie::Cookie as ExperimentalCookie
|
||||
import experimental.semmle.javascript.security.InsecureCookie::Cookie as ExperimentalCookie // TODO: Remove
|
||||
|
||||
from DataFlow::Node node
|
||||
where
|
||||
@@ -41,198 +41,4 @@ module Cookie {
|
||||
*/
|
||||
abstract predicate isSensitive();
|
||||
}
|
||||
|
||||
/**
|
||||
* A cookie set using the `express` module `cookie-session` (https://github.com/expressjs/cookie-session).
|
||||
*/
|
||||
class InsecureCookieSession extends ExpressLibraries::CookieSession::MiddlewareInstance,
|
||||
CookieWrite {
|
||||
private DataFlow::Node getCookieFlagValue(string flag) {
|
||||
result = this.getOptionArgument(0, flag)
|
||||
}
|
||||
|
||||
override predicate isSecure() {
|
||||
// The flag `secure` is set to `false` by default for HTTP, `true` by default for HTTPS (https://github.com/expressjs/cookie-session#cookie-options).
|
||||
// A cookie is secure if the `secure` flag is not explicitly set to `false`.
|
||||
not getCookieFlagValue(secureFlag()).mayHaveBooleanValue(false)
|
||||
}
|
||||
|
||||
override predicate isSensitive() {
|
||||
any() // It is a session cookie, likely auth sensitive
|
||||
}
|
||||
|
||||
override predicate isHttpOnly() {
|
||||
// The flag `httpOnly` is set to `true` by default (https://github.com/expressjs/cookie-session#cookie-options).
|
||||
// A cookie is httpOnly if the `httpOnly` flag is not explicitly set to `false`.
|
||||
not getCookieFlagValue(httpOnlyFlag()).mayHaveBooleanValue(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cookie set using the `express` module `express-session` (https://github.com/expressjs/session).
|
||||
*/
|
||||
class InsecureExpressSessionCookie extends ExpressLibraries::ExpressSession::MiddlewareInstance,
|
||||
CookieWrite {
|
||||
private DataFlow::Node getCookieFlagValue(string flag) {
|
||||
result = this.getOption("cookie").getALocalSource().getAPropertyWrite(flag).getRhs()
|
||||
}
|
||||
|
||||
override predicate isSecure() {
|
||||
// The flag `secure` is not set by default (https://github.com/expressjs/session#Cookiesecure).
|
||||
// The default value for cookie options is { path: '/', httpOnly: true, secure: false, maxAge: null }.
|
||||
// A cookie is secure if there are the cookie options with the `secure` flag set to `true` or to `auto`.
|
||||
getCookieFlagValue(secureFlag()).mayHaveBooleanValue(true) or
|
||||
getCookieFlagValue(secureFlag()).mayHaveStringValue("auto")
|
||||
}
|
||||
|
||||
override predicate isSensitive() {
|
||||
any() // It is a session cookie, likely auth sensitive
|
||||
}
|
||||
|
||||
override predicate isHttpOnly() {
|
||||
// The flag `httpOnly` is set by default (https://github.com/expressjs/session#Cookiesecure).
|
||||
// The default value for cookie options is { path: '/', httpOnly: true, secure: false, maxAge: null }.
|
||||
// A cookie is httpOnly if the `httpOnly` flag is not explicitly set to `false`.
|
||||
not getCookieFlagValue(httpOnlyFlag()).mayHaveBooleanValue(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cookie set using `response.cookie` from `express` module (https://expressjs.com/en/api.html#res.cookie).
|
||||
*/
|
||||
class InsecureExpressCookieResponse extends CookieWrite, DataFlow::MethodCallNode {
|
||||
InsecureExpressCookieResponse() { this.calls(any(Express::ResponseExpr r).flow(), "cookie") }
|
||||
|
||||
private DataFlow::Node getCookieFlagValue(string flag) {
|
||||
result = this.getOptionArgument(this.getNumArgument() - 1, flag)
|
||||
}
|
||||
|
||||
override predicate isSecure() {
|
||||
// A cookie is secure if there are cookie options with the `secure` flag set to `true`.
|
||||
// The default is `false`.
|
||||
getCookieFlagValue(secureFlag()).mayHaveBooleanValue(true)
|
||||
}
|
||||
|
||||
override predicate isSensitive() {
|
||||
HeuristicNames::nameIndicatesSensitiveData(any(string s |
|
||||
this.getArgument(0).mayHaveStringValue(s)
|
||||
), _)
|
||||
or
|
||||
this.getArgument(0).asExpr() instanceof SensitiveExpr
|
||||
}
|
||||
|
||||
override predicate isHttpOnly() {
|
||||
// A cookie is httpOnly if there are cookie options with the `httpOnly` flag set to `true`.
|
||||
// The default is `false`.
|
||||
getCookieFlagValue(httpOnlyFlag()).mayHaveBooleanValue(true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cookie set using `Set-Cookie` header of an `HTTP` response.
|
||||
* (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie).
|
||||
* In case an array is passed `setHeader("Set-Cookie", [...]` it sets multiple cookies.
|
||||
* Each array element has its own attributes.
|
||||
*/
|
||||
class InsecureSetCookieHeader extends CookieWrite {
|
||||
InsecureSetCookieHeader() {
|
||||
this.asExpr() = any(HTTP::SetCookieHeader setCookie).getHeaderArgument() and
|
||||
not this instanceof DataFlow::ArrayCreationNode
|
||||
or
|
||||
this =
|
||||
any(HTTP::SetCookieHeader setCookie)
|
||||
.getHeaderArgument()
|
||||
.flow()
|
||||
.(DataFlow::ArrayCreationNode)
|
||||
.getAnElement()
|
||||
}
|
||||
|
||||
/**
|
||||
* A cookie is secure if the `secure` flag is specified in the cookie definition.
|
||||
* The default is `false`.
|
||||
*/
|
||||
override predicate isSecure() { hasCookieAttribute("secure") }
|
||||
|
||||
/**
|
||||
* A cookie is httpOnly if the `httpOnly` flag is specified in the cookie definition.
|
||||
* The default is `false`.
|
||||
*/
|
||||
override predicate isHttpOnly() { hasCookieAttribute(httpOnlyFlag()) }
|
||||
|
||||
/**
|
||||
* The predicate holds only if any element have the specified attribute.
|
||||
*/
|
||||
bindingset[attribute]
|
||||
private predicate hasCookieAttribute(string attribute) {
|
||||
exists(string s |
|
||||
this.mayHaveStringValue(s) and
|
||||
hasCookieAttribute(s, attribute)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The predicate holds only if any element has a sensitive name.
|
||||
*/
|
||||
override predicate isSensitive() {
|
||||
HeuristicNames::nameIndicatesSensitiveData(getCookieName([
|
||||
any(string s | this.mayHaveStringValue(s)),
|
||||
this.(StringOps::ConcatenationRoot).getConstantStringParts()
|
||||
]), _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets cookie name from a `Set-Cookie` header value.
|
||||
* The header value always starts with `<cookie-name>=<cookie-value>` optionally followed by attributes:
|
||||
* `<cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly`
|
||||
*/
|
||||
bindingset[s]
|
||||
private string getCookieName(string s) {
|
||||
result = s.regexpCapture("\\s*\\b([^=\\s]*)\\b\\s*=.*", 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the `Set-Cookie` header value contains the specified attribute
|
||||
* 1. The attribute is case insensitive
|
||||
* 2. It always starts with a pair `<cookie-name>=<cookie-value>`.
|
||||
* If the attribute is present there must be `;` after the pair.
|
||||
* Other attributes like `Domain=`, `Path=`, etc. may come after the pair:
|
||||
* `<cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly`
|
||||
* See `https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie`
|
||||
*/
|
||||
bindingset[s, attribute]
|
||||
private predicate hasCookieAttribute(string s, string attribute) {
|
||||
s.regexpMatch("(?i).*;\\s*" + attribute + "\\b\\s*;?.*$")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cookie set using `js-cookie` library (https://github.com/js-cookie/js-cookie).
|
||||
*/
|
||||
class InsecureJsCookie extends CookieWrite {
|
||||
InsecureJsCookie() {
|
||||
this =
|
||||
[
|
||||
DataFlow::globalVarRef("Cookie"),
|
||||
DataFlow::globalVarRef("Cookie").getAMemberCall("noConflict"),
|
||||
DataFlow::moduleImport("js-cookie")
|
||||
].getAMemberCall("set")
|
||||
}
|
||||
|
||||
DataFlow::SourceNode getCookieOptionsArgument() {
|
||||
result = this.(DataFlow::CallNode).getAnArgument().getALocalSource()
|
||||
}
|
||||
|
||||
DataFlow::Node getCookieFlagValue(string flag) {
|
||||
result = this.getCookieOptionsArgument().getAPropertyWrite(flag).getRhs()
|
||||
}
|
||||
|
||||
override predicate isSecure() {
|
||||
// A cookie is secure if there are cookie options with the `secure` flag set to `true`.
|
||||
getCookieFlagValue(secureFlag()).mayHaveBooleanValue(true)
|
||||
}
|
||||
|
||||
override predicate isSensitive() { none() }
|
||||
|
||||
override predicate isHttpOnly() { none() } // js-cookie is browser side library and doesn't support HttpOnly
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user