mirror of
https://github.com/github/codeql.git
synced 2025-12-18 01:33:15 +01:00
Merge branch 'main' into extractBigReg
This commit is contained in:
4
javascript/change-notes/2021-10-01-ldap.md
Normal file
4
javascript/change-notes/2021-10-01-ldap.md
Normal file
@@ -0,0 +1,4 @@
|
||||
lgtm,codescanning
|
||||
* The `js/sql-injection` now recognizes the `ldapjs` library as a sink.
|
||||
Affected packages are
|
||||
[ldapjs](https://www.npmjs.com/package/ldapjs)
|
||||
2
javascript/change-notes/2021-10-26-cookie-queries.md
Normal file
2
javascript/change-notes/2021-10-26-cookie-queries.md
Normal file
@@ -0,0 +1,2 @@
|
||||
lgtm,codescanning
|
||||
* The `js/clear-text-cookie` and `js/client-exposed-cookie` queries have been renamed and moved into non-experimental.
|
||||
@@ -126,10 +126,7 @@ DataFlow::Node getASubexpressionWithinQuery(DataFlow::Node query) {
|
||||
exists(DataFlow::SourceNode receiver |
|
||||
receiver.flowsTo(getASubexpressionWithinQuery*(query.getALocalSource())) and
|
||||
result =
|
||||
[
|
||||
receiver.(DataFlow::SourceNode).getAPropertyWrite().getRhs(),
|
||||
receiver.(DataFlow::ArrayCreationNode).getAnElement()
|
||||
]
|
||||
[receiver.getAPropertyWrite().getRhs(), receiver.(DataFlow::ArrayCreationNode).getAnElement()]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -99,6 +99,7 @@ import semmle.javascript.frameworks.History
|
||||
import semmle.javascript.frameworks.Immutable
|
||||
import semmle.javascript.frameworks.Knex
|
||||
import semmle.javascript.frameworks.LazyCache
|
||||
import semmle.javascript.frameworks.LdapJS
|
||||
import semmle.javascript.frameworks.LodashUnderscore
|
||||
import semmle.javascript.frameworks.Logging
|
||||
import semmle.javascript.frameworks.HttpFrameworks
|
||||
|
||||
@@ -63,29 +63,25 @@ module DOM {
|
||||
/**
|
||||
* An HTML element, viewed as an `ElementDefinition`.
|
||||
*/
|
||||
private class HtmlElementDefinition extends ElementDefinition, @xmlelement {
|
||||
HtmlElementDefinition() { this instanceof HTML::Element }
|
||||
|
||||
override string getName() { result = this.(HTML::Element).getName() }
|
||||
private class HtmlElementDefinition extends ElementDefinition, @xmlelement instanceof HTML::Element {
|
||||
override string getName() { result = HTML::Element.super.getName() }
|
||||
|
||||
override AttributeDefinition getAttribute(int i) {
|
||||
result = this.(HTML::Element).getAttribute(i)
|
||||
result = HTML::Element.super.getAttribute(i)
|
||||
}
|
||||
|
||||
override ElementDefinition getParent() { result = this.(HTML::Element).getParent() }
|
||||
override ElementDefinition getParent() { result = HTML::Element.super.getParent() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A JSX element, viewed as an `ElementDefinition`.
|
||||
*/
|
||||
private class JsxElementDefinition extends ElementDefinition, @jsx_element {
|
||||
JsxElementDefinition() { this instanceof JSXElement }
|
||||
private class JsxElementDefinition extends ElementDefinition, @jsx_element instanceof JSXElement {
|
||||
override string getName() { result = JSXElement.super.getName() }
|
||||
|
||||
override string getName() { result = this.(JSXElement).getName() }
|
||||
override AttributeDefinition getAttribute(int i) { result = JSXElement.super.getAttribute(i) }
|
||||
|
||||
override AttributeDefinition getAttribute(int i) { result = this.(JSXElement).getAttribute(i) }
|
||||
|
||||
override ElementDefinition getParent() { result = this.(JSXElement).getJsxParent() }
|
||||
override ElementDefinition getParent() { result = super.getJsxParent() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,14 +127,12 @@ module DOM {
|
||||
/**
|
||||
* An HTML attribute, viewed as an `AttributeDefinition`.
|
||||
*/
|
||||
private class HtmlAttributeDefinition extends AttributeDefinition, @xmlattribute {
|
||||
HtmlAttributeDefinition() { this instanceof HTML::Attribute }
|
||||
private class HtmlAttributeDefinition extends AttributeDefinition, @xmlattribute instanceof HTML::Attribute {
|
||||
override string getName() { result = HTML::Attribute.super.getName() }
|
||||
|
||||
override string getName() { result = this.(HTML::Attribute).getName() }
|
||||
override string getStringValue() { result = super.getValue() }
|
||||
|
||||
override string getStringValue() { result = this.(HTML::Attribute).getValue() }
|
||||
|
||||
override ElementDefinition getElement() { result = this.(HTML::Attribute).getElement() }
|
||||
override ElementDefinition getElement() { result = HTML::Attribute.super.getElement() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -658,7 +658,7 @@ abstract class ReExportDeclaration extends ExportDeclaration {
|
||||
cached
|
||||
Module getReExportedModule() {
|
||||
Stages::Imports::ref() and
|
||||
result.getFile() = getEnclosingModule().resolve(getImportedPath().(PathExpr))
|
||||
result.getFile() = getEnclosingModule().resolve(getImportedPath())
|
||||
or
|
||||
result = resolveFromTypeRoot()
|
||||
}
|
||||
|
||||
@@ -699,7 +699,7 @@ module PrintHTML {
|
||||
childIndex = -1 and result.(HTMLAttributesNodes).getElement() = element
|
||||
or
|
||||
exists(HTML::Element child | result.(HTMLElementNode).getElement() = child |
|
||||
child = element.(HTML::Element).getChild(childIndex)
|
||||
child = element.getChild(childIndex)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,17 +61,15 @@ class ParameterNode extends DataFlow::SourceNode {
|
||||
* new Array(16)
|
||||
* ```
|
||||
*/
|
||||
class InvokeNode extends DataFlow::SourceNode {
|
||||
InvokeNode() { this instanceof DataFlow::Impl::InvokeNodeDef }
|
||||
|
||||
class InvokeNode extends DataFlow::SourceNode instanceof DataFlow::Impl::InvokeNodeDef {
|
||||
/** Gets the syntactic invoke expression underlying this function invocation. */
|
||||
InvokeExpr getInvokeExpr() { result = this.(DataFlow::Impl::InvokeNodeDef).getInvokeExpr() }
|
||||
InvokeExpr getInvokeExpr() { result = super.getInvokeExpr() }
|
||||
|
||||
/** Gets the name of the function or method being invoked, if it can be determined. */
|
||||
string getCalleeName() { result = this.(DataFlow::Impl::InvokeNodeDef).getCalleeName() }
|
||||
string getCalleeName() { result = super.getCalleeName() }
|
||||
|
||||
/** Gets the data flow node specifying the function to be called. */
|
||||
DataFlow::Node getCalleeNode() { result = this.(DataFlow::Impl::InvokeNodeDef).getCalleeNode() }
|
||||
DataFlow::Node getCalleeNode() { result = super.getCalleeNode() }
|
||||
|
||||
/**
|
||||
* Gets the data flow node corresponding to the `i`th argument of this invocation.
|
||||
@@ -92,10 +90,10 @@ class InvokeNode extends DataFlow::SourceNode {
|
||||
* but the position of `z` cannot be determined, hence there are no first and second
|
||||
* argument nodes.
|
||||
*/
|
||||
DataFlow::Node getArgument(int i) { result = this.(DataFlow::Impl::InvokeNodeDef).getArgument(i) }
|
||||
DataFlow::Node getArgument(int i) { result = super.getArgument(i) }
|
||||
|
||||
/** Gets the data flow node corresponding to an argument of this invocation. */
|
||||
DataFlow::Node getAnArgument() { result = this.(DataFlow::Impl::InvokeNodeDef).getAnArgument() }
|
||||
DataFlow::Node getAnArgument() { result = super.getAnArgument() }
|
||||
|
||||
/** Gets the data flow node corresponding to the last argument of this invocation. */
|
||||
DataFlow::Node getLastArgument() { result = getArgument(getNumArgument() - 1) }
|
||||
@@ -112,12 +110,10 @@ class InvokeNode extends DataFlow::SourceNode {
|
||||
* ```
|
||||
* .
|
||||
*/
|
||||
DataFlow::Node getASpreadArgument() {
|
||||
result = this.(DataFlow::Impl::InvokeNodeDef).getASpreadArgument()
|
||||
}
|
||||
DataFlow::Node getASpreadArgument() { result = super.getASpreadArgument() }
|
||||
|
||||
/** Gets the number of arguments of this invocation, if it can be determined. */
|
||||
int getNumArgument() { result = this.(DataFlow::Impl::InvokeNodeDef).getNumArgument() }
|
||||
int getNumArgument() { result = super.getNumArgument() }
|
||||
|
||||
Function getEnclosingFunction() { result = getBasicBlock().getContainer() }
|
||||
|
||||
@@ -258,15 +254,13 @@ class InvokeNode extends DataFlow::SourceNode {
|
||||
* Math.abs(x)
|
||||
* ```
|
||||
*/
|
||||
class CallNode extends InvokeNode {
|
||||
CallNode() { this instanceof DataFlow::Impl::CallNodeDef }
|
||||
|
||||
class CallNode extends InvokeNode instanceof DataFlow::Impl::CallNodeDef {
|
||||
/**
|
||||
* Gets the data flow node corresponding to the receiver expression of this method call.
|
||||
*
|
||||
* For example, the receiver of `x.m()` is `x`.
|
||||
*/
|
||||
DataFlow::Node getReceiver() { result = this.(DataFlow::Impl::CallNodeDef).getReceiver() }
|
||||
DataFlow::Node getReceiver() { result = super.getReceiver() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -279,11 +273,9 @@ class CallNode extends InvokeNode {
|
||||
* Math.abs(x)
|
||||
* ```
|
||||
*/
|
||||
class MethodCallNode extends CallNode {
|
||||
MethodCallNode() { this instanceof DataFlow::Impl::MethodCallNodeDef }
|
||||
|
||||
class MethodCallNode extends CallNode instanceof DataFlow::Impl::MethodCallNodeDef {
|
||||
/** Gets the name of the invoked method, if it can be determined. */
|
||||
string getMethodName() { result = this.(DataFlow::Impl::MethodCallNodeDef).getMethodName() }
|
||||
string getMethodName() { result = super.getMethodName() }
|
||||
|
||||
/**
|
||||
* Holds if this data flow node calls method `methodName` on receiver node `receiver`.
|
||||
|
||||
@@ -53,21 +53,18 @@ abstract class RefinementCandidate extends Expr {
|
||||
* A refinement candidate that references at most one variable, and hence
|
||||
* can be used to refine the abstract values inferred for that variable.
|
||||
*/
|
||||
class Refinement extends Expr {
|
||||
Refinement() {
|
||||
this instanceof RefinementCandidate and
|
||||
count(this.(RefinementCandidate).getARefinedVar()) <= 1
|
||||
}
|
||||
class Refinement extends Expr instanceof RefinementCandidate {
|
||||
Refinement() { count(this.(RefinementCandidate).getARefinedVar()) <= 1 }
|
||||
|
||||
/**
|
||||
* Gets the variable refined by this expression, if any.
|
||||
*/
|
||||
SsaSourceVariable getRefinedVar() { result = this.(RefinementCandidate).getARefinedVar() }
|
||||
SsaSourceVariable getRefinedVar() { result = super.getARefinedVar() }
|
||||
|
||||
/**
|
||||
* Gets a refinement value inferred for this expression in context `ctxt`.
|
||||
*/
|
||||
RefinementValue eval(RefinementContext ctxt) { result = this.(RefinementCandidate).eval(ctxt) }
|
||||
RefinementValue eval(RefinementContext ctxt) { result = super.eval(ctxt) }
|
||||
}
|
||||
|
||||
/** A literal, viewed as a refinement expression. */
|
||||
|
||||
@@ -8,7 +8,7 @@ pragma[nomagic]
|
||||
predicate isAnalyzedParameter(Parameter p) {
|
||||
exists(FunctionWithAnalyzedParameters f, int parmIdx | p = f.getParameter(parmIdx) |
|
||||
// we cannot track flow into rest parameters
|
||||
not p.(Parameter).isRestParameter()
|
||||
not p.isRestParameter()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -199,7 +199,7 @@ private class AnalyzedNewExpr extends DataFlow::AnalyzedValueNode {
|
||||
*/
|
||||
private predicate isIndefinite() {
|
||||
exists(DataFlow::AnalyzedNode callee, AbstractValue calleeVal |
|
||||
callee = astNode.(NewExpr).getCallee().analyze() and
|
||||
callee = astNode.getCallee().analyze() and
|
||||
calleeVal = callee.getALocalValue()
|
||||
|
|
||||
calleeVal.isIndefinite(_) or
|
||||
@@ -217,7 +217,7 @@ private class NewInstance extends DataFlow::AnalyzedValueNode {
|
||||
|
||||
override AbstractValue getALocalValue() {
|
||||
exists(DataFlow::AnalyzedNode callee |
|
||||
callee = astNode.(NewExpr).getCallee().analyze() and
|
||||
callee = astNode.getCallee().analyze() and
|
||||
result = TAbstractInstance(callee.getALocalValue())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,97 @@
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Classes and predicates for reasoning about writes to cookies.
|
||||
*/
|
||||
module CookieWrites {
|
||||
/**
|
||||
* A write to a cookie.
|
||||
*/
|
||||
abstract class CookieWrite extends DataFlow::Node {
|
||||
/**
|
||||
* Holds if this cookie is secure, i.e. only transmitted over SSL.
|
||||
*/
|
||||
abstract predicate isSecure();
|
||||
|
||||
/**
|
||||
* Holds if this cookie is HttpOnly, i.e. not accessible by JavaScript.
|
||||
*/
|
||||
abstract predicate isHttpOnly();
|
||||
|
||||
/**
|
||||
* Holds if the cookie likely is an authentication cookie or otherwise sensitive.
|
||||
*/
|
||||
abstract predicate isSensitive();
|
||||
|
||||
/**
|
||||
* Holds if the cookie write happens on a server, i.e. the `httpOnly` flag is relevant.
|
||||
*/
|
||||
predicate isServerSide() {
|
||||
any() // holds by default. Client-side cookie writes should extend ClientSideCookieWrite.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A client-side write to a cookie.
|
||||
*/
|
||||
abstract class ClientSideCookieWrite extends CookieWrite {
|
||||
final override predicate isHttpOnly() { none() }
|
||||
|
||||
final override predicate isServerSide() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The flag that indicates that a cookie is secure.
|
||||
*/
|
||||
string secure() { result = "secure" }
|
||||
|
||||
/**
|
||||
* The flag that indicates that a cookie is HttpOnly.
|
||||
*/
|
||||
string httpOnly() { result = "httpOnly" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` looks like it can contain a sensitive cookie.
|
||||
*
|
||||
* Heuristics:
|
||||
* - `node` contains a string value that looks like a sensitive cookie name
|
||||
* - `node` is a sensitive expression
|
||||
*/
|
||||
private predicate canHaveSensitiveCookie(DataFlow::Node node) {
|
||||
exists(string s |
|
||||
node.mayHaveStringValue(s) or
|
||||
s = node.(StringOps::ConcatenationRoot).getConstantStringParts()
|
||||
|
|
||||
HeuristicNames::nameIndicatesSensitiveData([s, getCookieName(s)], _)
|
||||
)
|
||||
or
|
||||
node.asExpr() instanceof SensitiveExpr
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cookie name of 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("([^=]*)=.*", 1).trim() }
|
||||
|
||||
/**
|
||||
* 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 model of the `js-cookie` library (https://github.com/js-cookie/js-cookie).
|
||||
*/
|
||||
@@ -25,12 +116,22 @@ private module JsCookie {
|
||||
}
|
||||
}
|
||||
|
||||
class WriteAccess extends PersistentWriteAccess, DataFlow::CallNode {
|
||||
class WriteAccess extends PersistentWriteAccess, DataFlow::CallNode,
|
||||
CookieWrites::ClientSideCookieWrite {
|
||||
WriteAccess() { this = libMemberCall("set") }
|
||||
|
||||
string getKey() { getArgument(0).mayHaveStringValue(result) }
|
||||
|
||||
override DataFlow::Node getValue() { result = getArgument(1) }
|
||||
|
||||
override predicate isSecure() {
|
||||
// A cookie is secure if there are cookie options with the `secure` flag set to `true`.
|
||||
exists(DataFlow::Node value | value = this.getOptionArgument(2, CookieWrites::secure()) |
|
||||
not value.mayHaveBooleanValue(false) // anything but `false` is accepted as being maybe true
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSensitive() { canHaveSensitiveCookie(this.getArgument(0)) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,12 +154,25 @@ private module BrowserCookies {
|
||||
}
|
||||
}
|
||||
|
||||
class WriteAccess extends PersistentWriteAccess, DataFlow::CallNode {
|
||||
class WriteAccess extends PersistentWriteAccess, DataFlow::CallNode,
|
||||
CookieWrites::ClientSideCookieWrite {
|
||||
WriteAccess() { this = libMemberCall("set") }
|
||||
|
||||
string getKey() { getArgument(0).mayHaveStringValue(result) }
|
||||
|
||||
override DataFlow::Node getValue() { result = getArgument(1) }
|
||||
|
||||
override predicate isSecure() {
|
||||
// A cookie is secure if there are cookie options with the `secure` flag set to `true`.
|
||||
exists(DataFlow::Node value | value = this.getOptionArgument(2, CookieWrites::secure()) |
|
||||
not value.mayHaveBooleanValue(false) // anything but `false` is accepted as being maybe true
|
||||
)
|
||||
or
|
||||
// or, an explicit default has been set
|
||||
exists(DataFlow::moduleMember("browser-cookies", "defaults").getAPropertyWrite("secure"))
|
||||
}
|
||||
|
||||
override predicate isSensitive() { canHaveSensitiveCookie(this.getArgument(0)) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,11 +195,174 @@ private module LibCookie {
|
||||
override PersistentWriteAccess getAWrite() { key = result.(WriteAccess).getKey() }
|
||||
}
|
||||
|
||||
class WriteAccess extends PersistentWriteAccess, DataFlow::CallNode {
|
||||
class WriteAccess extends PersistentWriteAccess, DataFlow::CallNode,
|
||||
CookieWrites::ClientSideCookieWrite {
|
||||
WriteAccess() { this = libMemberCall("serialize") }
|
||||
|
||||
string getKey() { getArgument(0).mayHaveStringValue(result) }
|
||||
|
||||
override DataFlow::Node getValue() { result = getArgument(1) }
|
||||
|
||||
override predicate isSecure() {
|
||||
// A cookie is secure if there are cookie options with the `secure` flag set to `true`.
|
||||
exists(DataFlow::Node value | value = this.getOptionArgument(2, CookieWrites::secure()) |
|
||||
not value.mayHaveBooleanValue(false) // anything but `false` is accepted as being maybe true
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSensitive() { canHaveSensitiveCookie(this.getArgument(0)) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A model of cookies in an express application.
|
||||
*/
|
||||
private module ExpressCookies {
|
||||
/**
|
||||
* A cookie set using `response.cookie` from `express` module (https://expressjs.com/en/api.html#res.cookie).
|
||||
*/
|
||||
private class InsecureExpressCookieResponse extends CookieWrites::CookieWrite,
|
||||
DataFlow::MethodCallNode {
|
||||
InsecureExpressCookieResponse() { this.asExpr() instanceof Express::SetCookie }
|
||||
|
||||
override predicate isSecure() {
|
||||
// A cookie is secure if there are cookie options with the `secure` flag set to `true`.
|
||||
// The default is `false`.
|
||||
exists(DataFlow::Node value | value = this.getOptionArgument(2, CookieWrites::secure()) |
|
||||
not value.mayHaveBooleanValue(false) // anything but `false` is accepted as being maybe true
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSensitive() { canHaveSensitiveCookie(this.getArgument(0)) }
|
||||
|
||||
override predicate isHttpOnly() {
|
||||
// A cookie is httpOnly if there are cookie options with the `httpOnly` flag set to `true`.
|
||||
// The default is `false`.
|
||||
exists(DataFlow::Node value | value = this.getOptionArgument(2, CookieWrites::httpOnly()) |
|
||||
not value.mayHaveBooleanValue(false) // anything but `false` is accepted as being maybe true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cookie set using the `express` module `cookie-session` (https://github.com/expressjs/cookie-session).
|
||||
*/
|
||||
class InsecureCookieSession extends ExpressLibraries::CookieSession::MiddlewareInstance,
|
||||
CookieWrites::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(CookieWrites::secure()).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(CookieWrites::httpOnly()).mayHaveBooleanValue(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cookie set using the `express` module `express-session` (https://github.com/expressjs/session).
|
||||
*/
|
||||
class InsecureExpressSessionCookie extends ExpressLibraries::ExpressSession::MiddlewareInstance,
|
||||
CookieWrites::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 }.
|
||||
exists(DataFlow::Node value | value = getCookieFlagValue(CookieWrites::secure()) |
|
||||
not value.mayHaveBooleanValue(false) // anything but `false` is accepted as being maybe true
|
||||
)
|
||||
}
|
||||
|
||||
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(CookieWrites::httpOnly()).mayHaveBooleanValue(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cookie set using `Set-Cookie` header of an `HTTP` response, where a raw header is used.
|
||||
* (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie).
|
||||
* This class does not model the Express implementation of `HTTP::CookieDefintion`
|
||||
* as the express implementation does not use raw headers.
|
||||
*
|
||||
* In case an array is passed `setHeader("Set-Cookie", [...]` it sets multiple cookies.
|
||||
* We model a `CookieWrite` for each array element.
|
||||
*/
|
||||
private class HTTPCookieWrite extends CookieWrites::CookieWrite {
|
||||
string header;
|
||||
|
||||
HTTPCookieWrite() {
|
||||
exists(HTTP::CookieDefinition setCookie |
|
||||
this.asExpr() = setCookie.getHeaderArgument() and
|
||||
not this instanceof DataFlow::ArrayCreationNode
|
||||
or
|
||||
this = setCookie.getHeaderArgument().flow().(DataFlow::ArrayCreationNode).getAnElement()
|
||||
) and
|
||||
header =
|
||||
[
|
||||
any(string s | this.mayHaveStringValue(s)),
|
||||
this.(StringOps::ConcatenationRoot).getConstantStringParts()
|
||||
]
|
||||
}
|
||||
|
||||
override predicate isSecure() {
|
||||
// A cookie is secure if the `secure` flag is specified in the cookie definition.
|
||||
// The default is `false`.
|
||||
hasCookieAttribute(header, CookieWrites::secure())
|
||||
}
|
||||
|
||||
override predicate isHttpOnly() {
|
||||
// A cookie is httpOnly if the `httpOnly` flag is specified in the cookie definition.
|
||||
// The default is `false`.
|
||||
hasCookieAttribute(header, CookieWrites::httpOnly())
|
||||
}
|
||||
|
||||
override predicate isSensitive() { canHaveSensitiveCookie(this) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A write to `document.cookie`.
|
||||
*/
|
||||
private class DocumentCookieWrite extends CookieWrites::ClientSideCookieWrite {
|
||||
string cookie;
|
||||
DataFlow::PropWrite write;
|
||||
|
||||
DocumentCookieWrite() {
|
||||
this = write and
|
||||
write = DOM::documentRef().getAPropertyWrite("cookie") and
|
||||
cookie =
|
||||
[
|
||||
any(string s | write.getRhs().mayHaveStringValue(s)),
|
||||
write.getRhs().(StringOps::ConcatenationRoot).getConstantStringParts()
|
||||
]
|
||||
}
|
||||
|
||||
override predicate isSecure() {
|
||||
// A cookie is secure if the `secure` flag is specified in the cookie definition.
|
||||
// The default is `false`.
|
||||
hasCookieAttribute(cookie, CookieWrites::secure())
|
||||
}
|
||||
|
||||
override predicate isSensitive() { canHaveSensitiveCookie(write.getRhs()) }
|
||||
}
|
||||
|
||||
71
javascript/ql/lib/semmle/javascript/frameworks/LdapJS.qll
Normal file
71
javascript/ql/lib/semmle/javascript/frameworks/LdapJS.qll
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Provides classes for working with [LDAPjs](https://www.npmjs.com/package/ldapjs)
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* A module providing sinks and sanitizers for LDAP injection.
|
||||
*/
|
||||
module LdapJS {
|
||||
/** Gets a reference to the ldapjs library. */
|
||||
API::Node ldapjs() { result = API::moduleImport("ldapjs") }
|
||||
|
||||
/** Gets an LDAPjs client. */
|
||||
private API::Node ldapClient() { result = ldapjs().getMember("createClient").getReturn() }
|
||||
|
||||
/** A call to a LDAPjs Client API method. */
|
||||
class ClientCall extends API::CallNode {
|
||||
string methodName;
|
||||
|
||||
ClientCall() {
|
||||
methodName = ["add", "bind", "compare", "del", "modify", "modifyDN", "search"] and
|
||||
this = ldapClient().getMember(methodName).getACall()
|
||||
}
|
||||
|
||||
/** Gets the name of the LDAPjs Client API method. */
|
||||
string getMethodName() { result = methodName }
|
||||
}
|
||||
|
||||
/** A reference to a LDAPjs client `search` options. */
|
||||
class SearchOptions extends API::Node {
|
||||
ClientCall call;
|
||||
|
||||
SearchOptions() { call.getMethodName() = "search" and this = call.getParameter(1) }
|
||||
}
|
||||
|
||||
/** A creation of an LDAPjs filter, or object containing a filter, that doesn't sanitizes the input. */
|
||||
abstract class TaintPreservingLdapFilterStep extends DataFlow::Node {
|
||||
/** The input that creates (part of) an LDAPjs filter. */
|
||||
abstract DataFlow::Node getInput();
|
||||
|
||||
/** The resulting LDAPjs filter. */
|
||||
abstract DataFlow::Node getOutput();
|
||||
}
|
||||
|
||||
/** A call to the LDAPjs utility method "parseFilter". */
|
||||
private class ParseFilter extends TaintPreservingLdapFilterStep, API::CallNode {
|
||||
ParseFilter() { this = ldapjs().getMember("parseFilter").getACall() }
|
||||
|
||||
override DataFlow::Node getInput() { result = this.getArgument(0) }
|
||||
|
||||
override DataFlow::Node getOutput() { result = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter used in call to "search" on an LDAPjs client.
|
||||
* We model that as a step from the ".filter" write to the options object itself.
|
||||
*/
|
||||
private class SearchFilter extends TaintPreservingLdapFilterStep {
|
||||
SearchOptions options;
|
||||
|
||||
SearchFilter() {
|
||||
options = ldapClient().getMember("search").getACall().getParameter(1) and
|
||||
this = options.getARhs()
|
||||
}
|
||||
|
||||
override DataFlow::Node getInput() { result = options.getMember("filter").getARhs() }
|
||||
|
||||
override DataFlow::Node getOutput() { result = this }
|
||||
}
|
||||
}
|
||||
@@ -139,7 +139,7 @@ private class JQueryDomElementDefinition extends DOM::ElementDefinition, @call_e
|
||||
JQueryDomElementDefinition() {
|
||||
this = call and
|
||||
call = jquery().getACall().asExpr() and
|
||||
exists(string s | s = call.getArgument(0).(Expr).getStringValue() |
|
||||
exists(string s | s = call.getArgument(0).getStringValue() |
|
||||
// match an opening angle bracket followed by a tag name, followed by arbitrary
|
||||
// text and a closing angle bracket, potentially with whitespace in between
|
||||
tagName = s.regexpCapture("\\s*<\\s*(\\w+)\\b[^>]*>\\s*", 1).toLowerCase()
|
||||
|
||||
@@ -14,11 +14,14 @@ import semmle.javascript.security.internal.SensitiveDataHeuristics
|
||||
private import HeuristicNames
|
||||
|
||||
/** An expression that might contain sensitive data. */
|
||||
cached
|
||||
abstract class SensitiveExpr extends Expr {
|
||||
/** Gets a human-readable description of this expression for use in alert messages. */
|
||||
cached
|
||||
abstract string describe();
|
||||
|
||||
/** Gets a classification of the kind of sensitive data this expression might contain. */
|
||||
cached
|
||||
abstract SensitiveDataClassification getClassification();
|
||||
}
|
||||
|
||||
|
||||
@@ -317,7 +317,7 @@ module PrettyPrintCatCall {
|
||||
*/
|
||||
string createFileThatIsReadFromCommandList(CommandCall call) {
|
||||
exists(DataFlow::ArrayCreationNode array, DataFlow::Node element |
|
||||
array = call.getArgumentList().(DataFlow::ArrayCreationNode) and
|
||||
array = call.getArgumentList() and
|
||||
array.getSize() = 1 and
|
||||
element = array.getElement(0)
|
||||
|
|
||||
|
||||
@@ -41,4 +41,35 @@ module SqlInjection {
|
||||
class GraphqlInjectionSink extends Sink {
|
||||
GraphqlInjectionSink() { this instanceof GraphQL::GraphQLString }
|
||||
}
|
||||
|
||||
/**
|
||||
* An LDAPjs sink.
|
||||
*/
|
||||
class LdapJSSink extends Sink {
|
||||
LdapJSSink() {
|
||||
// A distinguished name (DN) used in a call to the client API.
|
||||
this = any(LdapJS::ClientCall call).getArgument(0)
|
||||
or
|
||||
// A search options object, which contains a filter and a baseDN.
|
||||
this = any(LdapJS::SearchOptions opt).getARhs()
|
||||
or
|
||||
// A call to "parseDN", which parses a DN from a string.
|
||||
this = LdapJS::ldapjs().getMember("parseDN").getACall().getArgument(0)
|
||||
}
|
||||
}
|
||||
|
||||
import semmle.javascript.security.IncompleteBlacklistSanitizer as IncompleteBlacklistSanitizer
|
||||
|
||||
/**
|
||||
* A chain of replace calls that replaces all unsafe chars for ldap injection.
|
||||
* For simplicity it's used as a sanitizer for all of `js/sql-injection`.
|
||||
*/
|
||||
class LdapStringSanitizer extends Sanitizer,
|
||||
IncompleteBlacklistSanitizer::StringReplaceCallSequence {
|
||||
LdapStringSanitizer() {
|
||||
forall(string char | char = ["*", "(", ")", "\\", "/"] |
|
||||
this.getAMember().getAReplacedString() = char
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,4 +24,11 @@ class Configuration extends TaintTracking::Configuration {
|
||||
super.isSanitizer(node) or
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(LdapJS::TaintPreservingLdapFilterStep filter |
|
||||
pred = filter.getInput() and
|
||||
succ = filter.getOutput()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,9 +526,7 @@ module ReflectedXss {
|
||||
* ```
|
||||
*/
|
||||
predicate isLocalHeaderDefinition(HTTP::HeaderDefinition header) {
|
||||
exists(ReachableBasicBlock headerBlock |
|
||||
headerBlock = header.getBasicBlock().(ReachableBasicBlock)
|
||||
|
|
||||
exists(ReachableBasicBlock headerBlock | headerBlock = header.getBasicBlock() |
|
||||
1 =
|
||||
strictcount(HTTP::ResponseSendArgument sender |
|
||||
sender.getRouteHandler() = header.getRouteHandler() and
|
||||
|
||||
@@ -58,7 +58,7 @@ module HeuristicNames {
|
||||
*/
|
||||
string maybeAccountInfo() {
|
||||
result = "(?is).*acc(ou)?nt.*" or
|
||||
result = "(?is).*(puid|username|userid).*" or
|
||||
result = "(?is).*(puid|username|userid|session(id|key)).*" or
|
||||
result = "(?s).*([uU]|^|_|[a-z](?=U))([uU][iI][dD]).*"
|
||||
}
|
||||
|
||||
|
||||
@@ -47,10 +47,8 @@ module PolynomialReDoS {
|
||||
* A remote input to a server, seen as a source for polynomial
|
||||
* regular expression denial-of-service vulnerabilities.
|
||||
*/
|
||||
class RequestInputAccessAsSource extends Source {
|
||||
RequestInputAccessAsSource() { this instanceof HTTP::RequestInputAccess }
|
||||
|
||||
override string getKind() { result = this.(HTTP::RequestInputAccess).getKind() }
|
||||
class RequestInputAccessAsSource extends Source instanceof HTTP::RequestInputAccess {
|
||||
override string getKind() { result = HTTP::RequestInputAccess.super.getKind() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import javascript
|
||||
|
||||
/** Holds if `base` declares or inherits method `m` with the given `name`. */
|
||||
predicate hasMethod(ClassDefinition base, string name, MethodDefinition m) {
|
||||
predicate hasMethod(ClassDefinition base, string name, MethodDeclaration m) {
|
||||
m = base.getMethod(name) or
|
||||
hasMethod(base.getSuperClassDefinition(), name, m)
|
||||
}
|
||||
@@ -22,7 +22,7 @@ predicate hasMethod(ClassDefinition base, string name, MethodDefinition m) {
|
||||
* where `fromMethod` and `toMethod` are of kind `fromKind` and `toKind`, respectively.
|
||||
*/
|
||||
predicate isLocalMethodAccess(
|
||||
PropAccess access, MethodDefinition fromMethod, string fromKind, MethodDefinition toMethod,
|
||||
PropAccess access, MethodDefinition fromMethod, string fromKind, MethodDeclaration toMethod,
|
||||
string toKind
|
||||
) {
|
||||
hasMethod(fromMethod.getDeclaringClass(), access.getPropertyName(), toMethod) and
|
||||
@@ -32,7 +32,7 @@ predicate isLocalMethodAccess(
|
||||
toKind = getKind(toMethod)
|
||||
}
|
||||
|
||||
string getKind(MethodDefinition m) {
|
||||
string getKind(MethodDeclaration m) {
|
||||
if m.isStatic() then result = "static" else result = "instance"
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
* @id js/sql-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-089
|
||||
* external/cwe/cwe-090
|
||||
* external/cwe/cwe-943
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Authentication cookies stored by a server can be accessed by a client if the <code>httpOnly</code> flag is not set.
|
||||
</p>
|
||||
<p>
|
||||
An attacker that manages a cross-site scripting (XSS) attack can read the cookie and hijack the session.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Set the <code>httpOnly</code> flag on all cookies that are not needed by the client.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The following example stores an authentication token in a cookie that can
|
||||
be viewed by the client.
|
||||
</p>
|
||||
<sample src="examples/ClientExposedCookieGood.js"/>
|
||||
<p>
|
||||
To force the cookie to be transmitted using SSL, set the <code>secure</code>
|
||||
attribute on the cookie.
|
||||
</p>
|
||||
<sample src="examples/ClientExposedCookieBad.js"/>
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>ExpressJS: <a href="https://expressjs.com/en/advanced/best-practice-security.html#use-cookies-securely">Use cookies securely</a>.</li>
|
||||
<li>OWASP: <a href="https://cheatsheetseries.owasp.org/cheatsheets/Nodejs_Security_Cheat_Sheet.html#set-cookie-flags-appropriately">Set cookie flags appropriately</a>.</li>
|
||||
<li>Mozilla: <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie">Set-Cookie</a>.</li>
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
20
javascript/ql/src/Security/CWE-1004/ClientExposedCookie.ql
Normal file
20
javascript/ql/src/Security/CWE-1004/ClientExposedCookie.ql
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @name Sensitive server cookie exposed to the client
|
||||
* @description Sensitive cookies set by a server can be read by the client if the `httpOnly` flag is not set.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 5.0
|
||||
* @precision high
|
||||
* @id js/client-exposed-cookie
|
||||
* @tags security
|
||||
* external/cwe/cwe-1004
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
from CookieWrites::CookieWrite cookie
|
||||
where
|
||||
cookie.isSensitive() and
|
||||
cookie.isServerSide() and
|
||||
not cookie.isHttpOnly()
|
||||
select cookie, "Sensitive server cookie is missing 'httpOnly' flag."
|
||||
@@ -0,0 +1,7 @@
|
||||
const http = require('http');
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader("Set-Cookie", `authKey=${makeAuthkey()}`);
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||||
res.end('<h2>Hello world</h2>');
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
const http = require('http');
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader("Set-Cookie", `authKey=${makeAuthkey()}; secure; httpOnly`);
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||||
res.end('<h2>Hello world</h2>');
|
||||
});
|
||||
@@ -10,6 +10,7 @@
|
||||
* @id js/stack-trace-exposure
|
||||
* @tags security
|
||||
* external/cwe/cwe-209
|
||||
* external/cwe/cwe-497
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
38
javascript/ql/src/Security/CWE-614/ClearTextCookie.qhelp
Normal file
38
javascript/ql/src/Security/CWE-614/ClearTextCookie.qhelp
Normal file
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Cookies that are transmitted in clear text can be intercepted by an attacker.
|
||||
If sensitive cookies are intercepted, the attacker can read the cookie and
|
||||
use it to perform actions on the user's behalf.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Always transmit sensitive cookies using SSL by setting the <code>secure</code>
|
||||
attribute on the cookie.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The following example stores an authentication token in a cookie that can
|
||||
be transmitted in clear text.
|
||||
</p>
|
||||
<sample src="examples/ClearTextCookieBad.js"/>
|
||||
<p>
|
||||
To force the cookie to be transmitted using SSL, set the <code>secure</code>
|
||||
attribute on the cookie.
|
||||
</p>
|
||||
<sample src="examples/ClearTextCookieGood.js"/>
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>ExpressJS: <a href="https://expressjs.com/en/advanced/best-practice-security.html#use-cookies-securely">Use cookies securely</a>.</li>
|
||||
<li>OWASP: <a href="https://cheatsheetseries.owasp.org/cheatsheets/Nodejs_Security_Cheat_Sheet.html#set-cookie-flags-appropriately">Set cookie flags appropriately</a>.</li>
|
||||
<li>Mozilla: <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie">Set-Cookie</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
21
javascript/ql/src/Security/CWE-614/ClearTextCookie.ql
Normal file
21
javascript/ql/src/Security/CWE-614/ClearTextCookie.ql
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @name Clear text transmission of sensitive cookie
|
||||
* @description Sending sensitive information in a cookie without requring SSL encryption
|
||||
* can expose the cookie to an attacker.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 5.0
|
||||
* @precision high
|
||||
* @id js/clear-text-cookie
|
||||
* @tags security
|
||||
* external/cwe/cwe-614
|
||||
* external/cwe/cwe-311
|
||||
* external/cwe/cwe-312
|
||||
* external/cwe/cwe-319
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
from CookieWrites::CookieWrite cookie
|
||||
where cookie.isSensitive() and not cookie.isSecure()
|
||||
select cookie, "Sensitive cookie sent without enforcing SSL encryption"
|
||||
@@ -0,0 +1,7 @@
|
||||
const http = require('http');
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader("Set-Cookie", `authKey=${makeAuthkey()}`);
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||||
res.end('<h2>Hello world</h2>');
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
const http = require('http');
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader("Set-Cookie", `authKey=${makeAuthkey()}; secure; httpOnly`);
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||||
res.end('<h2>Hello world</h2>');
|
||||
});
|
||||
@@ -1,50 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>If an LDAP query is built using string concatenation or string formatting, and the
|
||||
components of the concatenation include user input without any proper sanitization, a user
|
||||
is likely to be able to run malicious LDAP queries.</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>If user input must be included in an LDAP query, it should be escaped to
|
||||
avoid a malicious user providing special characters that change the meaning
|
||||
of the query. In NodeJS, it is possible to build the LDAP query using frameworks like <code>ldapjs</code>.
|
||||
The library provides a <code>Filter API</code>, however it's still possibile to pass a string version of an LDAP filter.
|
||||
A good practice is to escape filter characters that could change the meaning of the query (https://tools.ietf.org/search/rfc4515#section-3).</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>In the following examples, the code accepts a <code>username</code> from the user, which it uses in a LDAP query.</p>
|
||||
|
||||
<p>The first and the second example uses the unsanitized user input directly
|
||||
in the search filter for the LDAP query.
|
||||
A malicious user could provide special characters to change the meaning of these
|
||||
queries, and search for a completely different set of values.
|
||||
</p>
|
||||
<sample src="examples/example_bad1.js" />
|
||||
<sample src="examples/example_bad2.js" />
|
||||
|
||||
|
||||
<p>In the third example the <code>username</code> is sanitized before it is included in the search filters.
|
||||
This ensures the meaning of the query cannot be changed by a malicious user.</p>
|
||||
|
||||
<sample src="examples/example_good1.js" />
|
||||
|
||||
<p>In the fourth example the <code>username</code> is passed to an <code>OrFilter</code> filter before it is included in the search filters.
|
||||
This ensures the meaning of the query cannot be changed by a malicious user.</p>
|
||||
|
||||
<sample src="examples/example_good2.js" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>OWASP: <a href="https://cheatsheetseries.owasp.org/cheatsheets/LDAP_Injection_Prevention_Cheat_Sheet.html">LDAP Injection Prevention Cheat Sheet</a>.</li>
|
||||
<li>LDAPjs: <a href="http://ldapjs.org/index.html">Documentation for LDAPjs</a>.</li>
|
||||
<li>Github: <a href="https://github.com/ldapjs/node-ldapjs">ldapjs</a>.</li>
|
||||
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/LDAP_injection">LDAP injection</a>.</li>
|
||||
<li>BlackHat: <a href="https://www.blackhat.com/presentations/bh-europe-08/Alonso-Parada/Whitepaper/bh-eu-08-alonso-parada-WP.pdf">LDAP Injection and Blind LDAP Injection</a>.</li>
|
||||
<li>LDAP: <a href="https://ldap.com/2018/05/04/understanding-and-defending-against-ldap-injection-attacks/">Understanding and Defending Against LDAP Injection Attacks</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* @name LDAP query built from user-controlled sources
|
||||
* @description Building an LDAP query from user-controlled sources is vulnerable to insertion of
|
||||
* malicious LDAP code by the user.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id javascript/ldap-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-090
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
import LdapInjection::LdapInjection
|
||||
|
||||
from LdapInjectionConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "$@ might include code from $@.",
|
||||
sink.getNode().(Sink).getQueryCall(), "LDAP query call", source.getNode(), "user-provided value"
|
||||
@@ -1,25 +0,0 @@
|
||||
import javascript
|
||||
|
||||
module LdapInjection {
|
||||
import LdapInjectionCustomizations::LdapInjection
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for reasoning about LDAP injection vulnerabilities.
|
||||
*/
|
||||
class LdapInjectionConfiguration extends TaintTracking::Configuration {
|
||||
LdapInjectionConfiguration() { this = "LdapInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(LdapjsParseFilter filter |
|
||||
pred = filter.getArgument(0) and
|
||||
succ = filter
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for reasoning about
|
||||
* LDAP injection vulnerabilities, as well as extension points for
|
||||
* adding your own.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
module LdapInjection {
|
||||
import Ldapjs::Ldapjs
|
||||
|
||||
/**
|
||||
* A data flow source for LDAP injection vulnerabilities.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A data flow sink for LDAP injection vulnerabilities.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node {
|
||||
/**
|
||||
* Gets the LDAP query call that the sink flows into.
|
||||
*/
|
||||
abstract DataFlow::Node getQueryCall();
|
||||
}
|
||||
|
||||
/**
|
||||
* A sanitizer for LDAP injection vulnerabilities.
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source for LDAP injection.
|
||||
*/
|
||||
class RemoteSource extends Source {
|
||||
RemoteSource() { this instanceof RemoteFlowSource }
|
||||
}
|
||||
|
||||
/**
|
||||
* An LDAP filter for an API call that executes an operation against the LDAP server.
|
||||
*/
|
||||
class LdapjsSearchFilterAsSink extends Sink {
|
||||
LdapjsSearchFilterAsSink() { this instanceof LdapjsSearchFilter }
|
||||
|
||||
override DataFlow::InvokeNode getQueryCall() {
|
||||
result = this.(LdapjsSearchFilter).getQueryCall()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An LDAP DN argument for an API call that executes an operation against the LDAP server.
|
||||
*/
|
||||
class LdapjsDNArgumentAsSink extends Sink {
|
||||
LdapjsDNArgumentAsSink() { this instanceof LdapjsDNArgument }
|
||||
|
||||
override DataFlow::InvokeNode getQueryCall() { result = this.(LdapjsDNArgument).getQueryCall() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a function whose name suggests that it escapes LDAP search query parameter.
|
||||
*/
|
||||
class FilterOrDNSanitizationCall extends Sanitizer, DataFlow::CallNode {
|
||||
FilterOrDNSanitizationCall() {
|
||||
exists(string sanitize, string input |
|
||||
sanitize = "(?:escape|saniti[sz]e|validate|filter)" and
|
||||
input = "[Ii]nput?"
|
||||
|
|
||||
this.getCalleeName()
|
||||
.regexpMatch("(?i)(" + sanitize + input + ")" + "|(" + input + sanitize + ")")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/**
|
||||
* Provides classes for working with [ldapjs](https://github.com/ldapjs/node-ldapjs) (Client only)
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
module Ldapjs {
|
||||
/**
|
||||
* Gets a method name on an LDAPjs client that accepts a DN as the first argument.
|
||||
*/
|
||||
private string getLdapjsClientDNMethodName() {
|
||||
result = ["add", "bind", "compare", "del", "modify", "modifyDN", "search"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow source node for an LDAP client.
|
||||
*/
|
||||
abstract class LdapClient extends DataFlow::SourceNode { }
|
||||
|
||||
/**
|
||||
* Gets a data flow source node for the ldapjs library.
|
||||
*/
|
||||
private DataFlow::SourceNode ldapjs() { result = DataFlow::moduleImport("ldapjs") }
|
||||
|
||||
/**
|
||||
* Gets a data flow source node for the ldapjs client.
|
||||
*/
|
||||
class LdapjsClient extends LdapClient {
|
||||
LdapjsClient() { this = ldapjs().getAMemberCall("createClient") }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow node for the client `search` options.
|
||||
*/
|
||||
class LdapjsSearchOptions extends DataFlow::SourceNode {
|
||||
DataFlow::CallNode queryCall;
|
||||
|
||||
LdapjsSearchOptions() {
|
||||
queryCall = any(LdapjsClient client).getAMemberCall("search") and
|
||||
this = queryCall.getArgument(1).getALocalSource()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the LDAP query call that these options are used in.
|
||||
*/
|
||||
DataFlow::InvokeNode getQueryCall() { result = queryCall }
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter used in a `search` operation against the LDAP server.
|
||||
*/
|
||||
class LdapjsSearchFilter extends DataFlow::Node {
|
||||
LdapjsSearchOptions options;
|
||||
|
||||
LdapjsSearchFilter() { this = options.getAPropertyWrite("filter").getRhs() }
|
||||
|
||||
/**
|
||||
* Gets the LDAP query call that this filter is used in.
|
||||
*/
|
||||
DataFlow::InvokeNode getQueryCall() { result = options.getQueryCall() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the ldapjs Client API methods.
|
||||
*/
|
||||
class LdapjsClientAPICall extends DataFlow::CallNode {
|
||||
LdapjsClientAPICall() {
|
||||
this = any(LdapjsClient client).getAMemberCall(getLdapjsClientDNMethodName())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A distinguished name (DN) used in a Client API call against the LDAP server.
|
||||
*/
|
||||
class LdapjsDNArgument extends DataFlow::Node {
|
||||
LdapjsClientAPICall queryCall;
|
||||
|
||||
LdapjsDNArgument() { this = queryCall.getArgument(0) }
|
||||
|
||||
/**
|
||||
* Gets the LDAP query call that this DN is used in.
|
||||
*/
|
||||
DataFlow::InvokeNode getQueryCall() { result = queryCall }
|
||||
}
|
||||
|
||||
/**
|
||||
* Ldapjs parseFilter method call.
|
||||
*/
|
||||
class LdapjsParseFilter extends DataFlow::CallNode {
|
||||
LdapjsParseFilter() { this = ldapjs().getAMemberCall("parseFilter") }
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
const http = require('http');
|
||||
const url = require('url');
|
||||
const ldap = require('ldapjs');
|
||||
const client = ldap.createClient({
|
||||
url: 'ldap://127.0.0.1:1389'
|
||||
});
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
let q = url.parse(req.url, true);
|
||||
|
||||
let username = q.query.username;
|
||||
|
||||
var opts = {
|
||||
// BAD
|
||||
filter: `(|(name=${username})(username=${username}))`
|
||||
};
|
||||
|
||||
client.search('o=example', opts, function (err, res) {
|
||||
|
||||
});
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
const http = require('http');
|
||||
const url = require('url');
|
||||
const ldap = require('ldapjs');
|
||||
const client = ldap.createClient({
|
||||
url: 'ldap://127.0.0.1:1389'
|
||||
});
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
let q = url.parse(req.url, true);
|
||||
|
||||
let username = q.query.username;
|
||||
|
||||
// BAD
|
||||
client.search('o=example', { filter: `(|(name=${username})(username=${username}))` }, function (err, res) {
|
||||
});
|
||||
});
|
||||
@@ -1,31 +0,0 @@
|
||||
const http = require('http');
|
||||
const url = require('url');
|
||||
const ldap = require('ldapjs');
|
||||
const client = ldap.createClient({
|
||||
url: 'ldap://127.0.0.1:1389'
|
||||
});
|
||||
|
||||
|
||||
// https://github.com/vesse/node-ldapauth-fork/commit/3feea43e243698bcaeffa904a7324f4d96df60e4
|
||||
const sanitizeInput = function (input) {
|
||||
return input
|
||||
.replace(/\*/g, '\\2a')
|
||||
.replace(/\(/g, '\\28')
|
||||
.replace(/\)/g, '\\29')
|
||||
.replace(/\\/g, '\\5c')
|
||||
.replace(/\0/g, '\\00')
|
||||
.replace(/\//g, '\\2f');
|
||||
};
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
let q = url.parse(req.url, true);
|
||||
|
||||
let username = q.query.username;
|
||||
|
||||
// GOOD
|
||||
username = sanitizeInput(username);
|
||||
|
||||
client.search('o=example', { filter: `(|(name=${username})(username=${username}))` }, function (err, res) {
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
const http = require('http');
|
||||
const url = require('url');
|
||||
const ldap = require('ldapjs');
|
||||
const client = ldap.createClient({
|
||||
url: 'ldap://127.0.0.1:1389'
|
||||
});
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
let q = url.parse(req.url, true);
|
||||
|
||||
let username = q.query.username;
|
||||
|
||||
// GOOD (https://github.com/ldapjs/node-ldapjs/issues/181)
|
||||
let f = new OrFilter({
|
||||
filters: [
|
||||
new EqualityFilter({
|
||||
attribute: 'name',
|
||||
value: username
|
||||
}),
|
||||
new EqualityFilter({
|
||||
attribute: 'username',
|
||||
value: username
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
client.search('o=example', { filter: f }, function (err, res) {
|
||||
});
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
<!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 those related to authentication, by setting <code>HttpOnly</code> to <code>true</code> to make
|
||||
them not accessible to JavaScript.</p>
|
||||
|
||||
</recommendation>
|
||||
|
||||
<references>
|
||||
|
||||
<li>Production Best Practices: Security:<a href="https://expressjs.com/en/advanced/best-practice-security.html#use-cookies-securely">Use cookies securely</a>.</li>
|
||||
<li>NodeJS security cheat sheet:<a href="https://cheatsheetseries.owasp.org/cheatsheets/Nodejs_Security_Cheat_Sheet.html#set-cookie-flags-appropriately">Set cookie flags appropriately</a>.</li>
|
||||
<li>express-session:<a href="https://github.com/expressjs/session#cookiehttponly">cookie.httpOnly</a>.</li>
|
||||
<li>cookie-session:<a href="https://github.com/expressjs/cookie-session#cookie-options">Cookie Options</a>.</li>
|
||||
<li><a href="https://expressjs.com/en/api.html#res.cookie">express response.cookie</a>.</li>
|
||||
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie">Set-Cookie</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* @name 'HttpOnly' attribute is not set to true
|
||||
* @description Omitting the 'HttpOnly' attribute for security sensitive cookie data allows
|
||||
* malicious JavaScript to steal it in case of XSS vulnerabilities. Always set
|
||||
* 'HttpOnly' to 'true' for authentication related cookies to make them
|
||||
* inaccessible from JavaScript.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @precision high
|
||||
* @id js/cookie-httponly-not-set
|
||||
* @tags security
|
||||
* external/cwe/cwe-1004
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import experimental.semmle.javascript.security.InsecureCookie::Cookie
|
||||
|
||||
from Cookie cookie
|
||||
where cookie.isAuthNotHttpOnly()
|
||||
select cookie, "Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie."
|
||||
@@ -1,26 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>Failing to set the 'secure' flag on a cookie can cause it to be sent in cleartext.
|
||||
This makes it easier for an attacker to intercept.</p>
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Always set the <code>secure</code> flag to `true` on a cookie before adding it
|
||||
to an HTTP response (if the default value is `false`).</p>
|
||||
|
||||
</recommendation>
|
||||
|
||||
<references>
|
||||
|
||||
<li>Production Best Practices: Security:<a href="https://expressjs.com/en/advanced/best-practice-security.html#use-cookies-securely">Use cookies securely</a>.</li>
|
||||
<li>NodeJS security cheat sheet:<a href="https://cheatsheetseries.owasp.org/cheatsheets/Nodejs_Security_Cheat_Sheet.html#set-cookie-flags-appropriately">Set cookie flags appropriately</a>.</li>
|
||||
<li>express-session:<a href="https://github.com/expressjs/session#cookiesecure">cookie.secure</a>.</li>
|
||||
<li>cookie-session:<a href="https://github.com/expressjs/cookie-session#cookie-options">Cookie Options</a>.</li>
|
||||
<li><a href="https://expressjs.com/en/api.html#res.cookie">express response.cookie</a>.</li>
|
||||
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie">Set-Cookie</a>.</li>
|
||||
<li><a href="https://github.com/js-cookie/js-cookie">js-cookie</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* @name Failure to set secure cookies
|
||||
* @description Insecure cookies may be sent in cleartext, which makes them vulnerable to
|
||||
* interception.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id js/insecure-cookie
|
||||
* @tags security
|
||||
* external/cwe/cwe-614
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import experimental.semmle.javascript.security.InsecureCookie::Cookie
|
||||
|
||||
from Cookie cookie
|
||||
where not cookie.isSecure()
|
||||
select cookie, "Cookie is added to response without the 'secure' flag being set to true"
|
||||
@@ -1,323 +0,0 @@
|
||||
/**
|
||||
* Provides classes for reasoning about cookies added to response without the 'secure' or 'httponly' flag being set.
|
||||
* - A cookie without the 'secure' flag being set can be intercepted and read by a malicious user.
|
||||
* - A cookie without the 'httponly' flag being set can be read by maliciously injected JavaScript.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
module Cookie {
|
||||
/**
|
||||
* `secure` property of the cookie options.
|
||||
*/
|
||||
string secureFlag() { result = "secure" }
|
||||
|
||||
/**
|
||||
* `httpOnly` property of the cookie options.
|
||||
*/
|
||||
string httpOnlyFlag() { result = "httpOnly" }
|
||||
|
||||
/**
|
||||
* Abstract class to represent different cases of insecure cookie settings.
|
||||
*/
|
||||
abstract class Cookie extends DataFlow::Node {
|
||||
/**
|
||||
* Gets the name of the middleware/library used to set the cookie.
|
||||
*/
|
||||
abstract string getKind();
|
||||
|
||||
/**
|
||||
* Gets the options used to set this cookie, if any.
|
||||
*/
|
||||
abstract DataFlow::Node getCookieOptionsArgument();
|
||||
|
||||
/**
|
||||
* Holds if this cookie is secure.
|
||||
*/
|
||||
abstract predicate isSecure();
|
||||
|
||||
/**
|
||||
* Holds if this cookie is HttpOnly.
|
||||
*/
|
||||
abstract predicate isHttpOnly();
|
||||
|
||||
/**
|
||||
* Holds if the cookie is authentication sensitive and lacks HttpOnly.
|
||||
*/
|
||||
abstract predicate isAuthNotHttpOnly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the expression is a variable with a sensitive name.
|
||||
*/
|
||||
private predicate isAuthVariable(DataFlow::Node expr) {
|
||||
exists(string val |
|
||||
(
|
||||
val = expr.getStringValue() or
|
||||
val = expr.asExpr().(VarAccess).getName() or
|
||||
val = expr.(DataFlow::PropRead).getPropertyName()
|
||||
) and
|
||||
regexpMatchAuth(val)
|
||||
)
|
||||
or
|
||||
isAuthVariable(expr.getAPredecessor())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `val` looks related to authentication, without being an anti-forgery token.
|
||||
*/
|
||||
bindingset[val]
|
||||
private predicate regexpMatchAuth(string val) {
|
||||
val.regexpMatch("(?i).*(session|login|token|user|auth|credential).*") and
|
||||
not val.regexpMatch("(?i).*(xsrf|csrf|forgery).*")
|
||||
}
|
||||
|
||||
/**
|
||||
* A cookie set using the `express` module `cookie-session` (https://github.com/expressjs/cookie-session).
|
||||
*/
|
||||
class InsecureCookieSession extends ExpressLibraries::CookieSession::MiddlewareInstance, Cookie {
|
||||
override string getKind() { result = "cookie-session" }
|
||||
|
||||
override DataFlow::SourceNode getCookieOptionsArgument() { result.flowsTo(getArgument(0)) }
|
||||
|
||||
private DataFlow::Node getCookieFlagValue(string flag) {
|
||||
result = this.getCookieOptionsArgument().getAPropertyWrite(flag).getRhs()
|
||||
}
|
||||
|
||||
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 isAuthNotHttpOnly() {
|
||||
not isHttpOnly() // 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,
|
||||
Cookie {
|
||||
override string getKind() { result = "express-session" }
|
||||
|
||||
override DataFlow::SourceNode getCookieOptionsArgument() { result = this.getOption("cookie") }
|
||||
|
||||
private DataFlow::Node getCookieFlagValue(string flag) {
|
||||
result = this.getCookieOptionsArgument().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 isAuthNotHttpOnly() {
|
||||
not isHttpOnly() // 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 Cookie, DataFlow::MethodCallNode {
|
||||
InsecureExpressCookieResponse() { this.calls(any(Express::ResponseExpr r).flow(), "cookie") }
|
||||
|
||||
override string getKind() { result = "response.cookie" }
|
||||
|
||||
override DataFlow::SourceNode getCookieOptionsArgument() {
|
||||
result = this.getLastArgument().getALocalSource()
|
||||
}
|
||||
|
||||
private 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`.
|
||||
// The default is `false`.
|
||||
getCookieFlagValue(secureFlag()).mayHaveBooleanValue(true)
|
||||
}
|
||||
|
||||
override predicate isAuthNotHttpOnly() {
|
||||
isAuthVariable(this.getArgument(0)) and
|
||||
not isHttpOnly()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
private class AttributeToSetCookieHeaderTrackingConfig extends TaintTracking::Configuration {
|
||||
AttributeToSetCookieHeaderTrackingConfig() { this = "AttributeToSetCookieHeaderTrackingConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
exists(string s | source.mayHaveStringValue(s))
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof TemplateLiteral }
|
||||
}
|
||||
|
||||
private class SensitiveNameToSetCookieHeaderTrackingConfig extends TaintTracking::Configuration {
|
||||
SensitiveNameToSetCookieHeaderTrackingConfig() {
|
||||
this = "SensitiveNameToSetCookieHeaderTrackingConfig"
|
||||
}
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { isAuthVariable(source) }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof TemplateLiteral }
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Cookie {
|
||||
InsecureSetCookieHeader() {
|
||||
this.asExpr() = any(HTTP::SetCookieHeader setCookie).getHeaderArgument()
|
||||
}
|
||||
|
||||
override string getKind() { result = "set-cookie header" }
|
||||
|
||||
override DataFlow::Node getCookieOptionsArgument() {
|
||||
if this.asExpr() instanceof ArrayExpr
|
||||
then result.asExpr() = this.asExpr().(ArrayExpr).getAnElement()
|
||||
else result.asExpr() = this.asExpr()
|
||||
}
|
||||
|
||||
/**
|
||||
* A cookie is secure if the `secure` flag is specified in the cookie definition.
|
||||
* The default is `false`.
|
||||
*/
|
||||
override predicate isSecure() { allHaveCookieAttribute("secure") }
|
||||
|
||||
/**
|
||||
* A cookie is httpOnly if the `httpOnly` flag is specified in the cookie definition.
|
||||
* The default is `false`.
|
||||
*/
|
||||
override predicate isHttpOnly() { allHaveCookieAttribute(httpOnlyFlag()) }
|
||||
|
||||
/**
|
||||
* The predicate holds only if all elements have the specified attribute.
|
||||
*/
|
||||
bindingset[attribute]
|
||||
private predicate allHaveCookieAttribute(string attribute) {
|
||||
forall(DataFlow::Node n | n = getCookieOptionsArgument() |
|
||||
exists(string s |
|
||||
n.mayHaveStringValue(s) and
|
||||
hasCookieAttribute(s, attribute)
|
||||
)
|
||||
or
|
||||
exists(AttributeToSetCookieHeaderTrackingConfig cfg, DataFlow::Node source |
|
||||
cfg.hasFlow(source, n) and
|
||||
exists(string attr |
|
||||
source.mayHaveStringValue(attr) and
|
||||
attr.regexpMatch("(?i).*\\b" + attribute + "\\b.*")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The predicate holds only if any element has a sensitive name and
|
||||
* doesn't have the `httpOnly` flag.
|
||||
*/
|
||||
override predicate isAuthNotHttpOnly() {
|
||||
exists(DataFlow::Node n | n = getCookieOptionsArgument() |
|
||||
exists(string s |
|
||||
n.mayHaveStringValue(s) and
|
||||
(
|
||||
not hasCookieAttribute(s, httpOnlyFlag()) and
|
||||
regexpMatchAuth(getCookieName(s))
|
||||
)
|
||||
)
|
||||
or
|
||||
not exists(AttributeToSetCookieHeaderTrackingConfig cfg, DataFlow::Node source |
|
||||
cfg.hasFlow(source, n) and
|
||||
exists(string attr |
|
||||
source.mayHaveStringValue(attr) and
|
||||
attr.regexpMatch("(?i).*\\b" + httpOnlyFlag() + "\\b.*")
|
||||
)
|
||||
) and
|
||||
exists(SensitiveNameToSetCookieHeaderTrackingConfig cfg | cfg.hasFlow(_, n))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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*([^=\\s]*)\\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 + "\\s*;?.*$")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cookie set using `js-cookie` library (https://github.com/js-cookie/js-cookie).
|
||||
*/
|
||||
class InsecureJsCookie extends Cookie {
|
||||
InsecureJsCookie() {
|
||||
this =
|
||||
[
|
||||
DataFlow::globalVarRef("Cookie"),
|
||||
DataFlow::globalVarRef("Cookie").getAMemberCall("noConflict"),
|
||||
DataFlow::moduleImport("js-cookie")
|
||||
].getAMemberCall("set")
|
||||
}
|
||||
|
||||
override string getKind() { result = "js-cookie" }
|
||||
|
||||
override 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 isAuthNotHttpOnly() { none() }
|
||||
|
||||
override predicate isHttpOnly() { none() } // js-cookie is browser side library and doesn't support HttpOnly
|
||||
}
|
||||
}
|
||||
@@ -4,5 +4,5 @@ from ClassDefinition class_, FieldDefinition field
|
||||
where
|
||||
class_.getAField() = field and
|
||||
field.isStatic() and
|
||||
field.getInit().getFirstControlFlowNode().getAPredecessor*() = class_.(ControlFlowNode)
|
||||
field.getInit().getFirstControlFlowNode().getAPredecessor*() = class_
|
||||
select field, "Field initializer occurs after its class is created"
|
||||
|
||||
@@ -4,4 +4,4 @@ from FunctionWithAnalyzedParameters f, SimpleParameter p, AnalyzedVarDef var
|
||||
where
|
||||
f.argumentPassing(p, _) and
|
||||
var.getAVariable() = p.getVariable()
|
||||
select p, var.(AnalyzedVarDef).getAnAssignedValue()
|
||||
select p, var.getAnAssignedValue()
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
abstract class Q {
|
||||
abstract test();
|
||||
static test() {}
|
||||
|
||||
method() {
|
||||
this.test(); // OK
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,35 @@ nodes
|
||||
| json-schema-validator.js:59:22:59:26 | query |
|
||||
| json-schema-validator.js:61:22:61:26 | query |
|
||||
| json-schema-validator.js:61:22:61:26 | query |
|
||||
| ldap.js:20:7:20:34 | q |
|
||||
| ldap.js:20:11:20:34 | url.par ... , true) |
|
||||
| ldap.js:20:21:20:27 | req.url |
|
||||
| ldap.js:20:21:20:27 | req.url |
|
||||
| ldap.js:22:7:22:33 | username |
|
||||
| ldap.js:22:18:22:18 | q |
|
||||
| ldap.js:22:18:22:24 | q.query |
|
||||
| ldap.js:22:18:22:33 | q.query.username |
|
||||
| ldap.js:25:13:25:57 | `(\|(nam ... ame}))` |
|
||||
| ldap.js:25:24:25:31 | username |
|
||||
| ldap.js:25:46:25:53 | username |
|
||||
| ldap.js:28:30:28:34 | opts1 |
|
||||
| ldap.js:28:30:28:34 | opts1 |
|
||||
| ldap.js:32:5:32:61 | { filte ... e}))` } |
|
||||
| ldap.js:32:5:32:61 | { filte ... e}))` } |
|
||||
| ldap.js:32:15:32:59 | `(\|(nam ... ame}))` |
|
||||
| ldap.js:32:26:32:33 | username |
|
||||
| ldap.js:32:48:32:55 | username |
|
||||
| ldap.js:63:9:65:3 | parsedFilter |
|
||||
| ldap.js:63:24:65:3 | ldap.pa ... ))`\\n ) |
|
||||
| ldap.js:64:5:64:49 | `(\|(nam ... ame}))` |
|
||||
| ldap.js:64:16:64:23 | username |
|
||||
| ldap.js:64:38:64:45 | username |
|
||||
| ldap.js:66:30:66:53 | { filte ... ilter } |
|
||||
| ldap.js:66:30:66:53 | { filte ... ilter } |
|
||||
| ldap.js:66:40:66:51 | parsedFilter |
|
||||
| ldap.js:68:27:68:42 | `cn=${username}` |
|
||||
| ldap.js:68:27:68:42 | `cn=${username}` |
|
||||
| ldap.js:68:33:68:40 | username |
|
||||
| marsdb-flow-to.js:10:9:10:18 | query |
|
||||
| marsdb-flow-to.js:10:17:10:18 | {} |
|
||||
| marsdb-flow-to.js:11:17:11:24 | req.body |
|
||||
@@ -444,6 +473,37 @@ edges
|
||||
| json-schema-validator.js:50:23:50:48 | JSON.pa ... y.data) | json-schema-validator.js:50:15:50:48 | query |
|
||||
| json-schema-validator.js:50:34:50:47 | req.query.data | json-schema-validator.js:50:23:50:48 | JSON.pa ... y.data) |
|
||||
| json-schema-validator.js:50:34:50:47 | req.query.data | json-schema-validator.js:50:23:50:48 | JSON.pa ... y.data) |
|
||||
| ldap.js:20:7:20:34 | q | ldap.js:22:18:22:18 | q |
|
||||
| ldap.js:20:11:20:34 | url.par ... , true) | ldap.js:20:7:20:34 | q |
|
||||
| ldap.js:20:21:20:27 | req.url | ldap.js:20:11:20:34 | url.par ... , true) |
|
||||
| ldap.js:20:21:20:27 | req.url | ldap.js:20:11:20:34 | url.par ... , true) |
|
||||
| ldap.js:22:7:22:33 | username | ldap.js:25:24:25:31 | username |
|
||||
| ldap.js:22:7:22:33 | username | ldap.js:25:46:25:53 | username |
|
||||
| ldap.js:22:7:22:33 | username | ldap.js:32:26:32:33 | username |
|
||||
| ldap.js:22:7:22:33 | username | ldap.js:32:48:32:55 | username |
|
||||
| ldap.js:22:7:22:33 | username | ldap.js:64:16:64:23 | username |
|
||||
| ldap.js:22:7:22:33 | username | ldap.js:64:38:64:45 | username |
|
||||
| ldap.js:22:7:22:33 | username | ldap.js:68:33:68:40 | username |
|
||||
| ldap.js:22:18:22:18 | q | ldap.js:22:18:22:24 | q.query |
|
||||
| ldap.js:22:18:22:24 | q.query | ldap.js:22:18:22:33 | q.query.username |
|
||||
| ldap.js:22:18:22:33 | q.query.username | ldap.js:22:7:22:33 | username |
|
||||
| ldap.js:25:13:25:57 | `(\|(nam ... ame}))` | ldap.js:28:30:28:34 | opts1 |
|
||||
| ldap.js:25:13:25:57 | `(\|(nam ... ame}))` | ldap.js:28:30:28:34 | opts1 |
|
||||
| ldap.js:25:24:25:31 | username | ldap.js:25:13:25:57 | `(\|(nam ... ame}))` |
|
||||
| ldap.js:25:46:25:53 | username | ldap.js:25:13:25:57 | `(\|(nam ... ame}))` |
|
||||
| ldap.js:32:15:32:59 | `(\|(nam ... ame}))` | ldap.js:32:5:32:61 | { filte ... e}))` } |
|
||||
| ldap.js:32:15:32:59 | `(\|(nam ... ame}))` | ldap.js:32:5:32:61 | { filte ... e}))` } |
|
||||
| ldap.js:32:26:32:33 | username | ldap.js:32:15:32:59 | `(\|(nam ... ame}))` |
|
||||
| ldap.js:32:48:32:55 | username | ldap.js:32:15:32:59 | `(\|(nam ... ame}))` |
|
||||
| ldap.js:63:9:65:3 | parsedFilter | ldap.js:66:40:66:51 | parsedFilter |
|
||||
| ldap.js:63:24:65:3 | ldap.pa ... ))`\\n ) | ldap.js:63:9:65:3 | parsedFilter |
|
||||
| ldap.js:64:5:64:49 | `(\|(nam ... ame}))` | ldap.js:63:24:65:3 | ldap.pa ... ))`\\n ) |
|
||||
| ldap.js:64:16:64:23 | username | ldap.js:64:5:64:49 | `(\|(nam ... ame}))` |
|
||||
| ldap.js:64:38:64:45 | username | ldap.js:64:5:64:49 | `(\|(nam ... ame}))` |
|
||||
| ldap.js:66:40:66:51 | parsedFilter | ldap.js:66:30:66:53 | { filte ... ilter } |
|
||||
| ldap.js:66:40:66:51 | parsedFilter | ldap.js:66:30:66:53 | { filte ... ilter } |
|
||||
| ldap.js:68:33:68:40 | username | ldap.js:68:27:68:42 | `cn=${username}` |
|
||||
| ldap.js:68:33:68:40 | username | ldap.js:68:27:68:42 | `cn=${username}` |
|
||||
| marsdb-flow-to.js:10:9:10:18 | query | marsdb-flow-to.js:14:17:14:21 | query |
|
||||
| marsdb-flow-to.js:10:9:10:18 | query | marsdb-flow-to.js:14:17:14:21 | query |
|
||||
| marsdb-flow-to.js:10:17:10:18 | {} | marsdb-flow-to.js:10:9:10:18 | query |
|
||||
@@ -852,6 +912,10 @@ edges
|
||||
| json-schema-validator.js:55:22:55:26 | query | json-schema-validator.js:50:34:50:47 | req.query.data | json-schema-validator.js:55:22:55:26 | query | This query depends on $@. | json-schema-validator.js:50:34:50:47 | req.query.data | a user-provided value |
|
||||
| json-schema-validator.js:59:22:59:26 | query | json-schema-validator.js:50:34:50:47 | req.query.data | json-schema-validator.js:59:22:59:26 | query | This query depends on $@. | json-schema-validator.js:50:34:50:47 | req.query.data | a user-provided value |
|
||||
| json-schema-validator.js:61:22:61:26 | query | json-schema-validator.js:50:34:50:47 | req.query.data | json-schema-validator.js:61:22:61:26 | query | This query depends on $@. | json-schema-validator.js:50:34:50:47 | req.query.data | a user-provided value |
|
||||
| ldap.js:28:30:28:34 | opts1 | ldap.js:20:21:20:27 | req.url | ldap.js:28:30:28:34 | opts1 | This query depends on $@. | ldap.js:20:21:20:27 | req.url | a user-provided value |
|
||||
| ldap.js:32:5:32:61 | { filte ... e}))` } | ldap.js:20:21:20:27 | req.url | ldap.js:32:5:32:61 | { filte ... e}))` } | This query depends on $@. | ldap.js:20:21:20:27 | req.url | a user-provided value |
|
||||
| ldap.js:66:30:66:53 | { filte ... ilter } | ldap.js:20:21:20:27 | req.url | ldap.js:66:30:66:53 | { filte ... ilter } | This query depends on $@. | ldap.js:20:21:20:27 | req.url | a user-provided value |
|
||||
| ldap.js:68:27:68:42 | `cn=${username}` | ldap.js:20:21:20:27 | req.url | ldap.js:68:27:68:42 | `cn=${username}` | This query depends on $@. | ldap.js:20:21:20:27 | req.url | a user-provided value |
|
||||
| marsdb-flow-to.js:14:17:14:21 | query | marsdb-flow-to.js:11:17:11:24 | req.body | marsdb-flow-to.js:14:17:14:21 | query | This query depends on $@. | marsdb-flow-to.js:11:17:11:24 | req.body | a user-provided value |
|
||||
| marsdb.js:16:12:16:16 | query | marsdb.js:13:17:13:24 | req.body | marsdb.js:16:12:16:16 | query | This query depends on $@. | marsdb.js:13:17:13:24 | req.body | a user-provided value |
|
||||
| minimongo.js:18:12:18:16 | query | minimongo.js:15:17:15:24 | req.body | minimongo.js:18:12:18:16 | query | This query depends on $@. | minimongo.js:15:17:15:24 | req.body | a user-provided value |
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
const http = require("http");
|
||||
const url = require("url");
|
||||
const ldap = require("ldapjs");
|
||||
const client = ldap.createClient({
|
||||
url: "ldap://127.0.0.1:1389",
|
||||
});
|
||||
|
||||
// https://github.com/vesse/node-ldapauth-fork/commit/3feea43e243698bcaeffa904a7324f4d96df60e4
|
||||
const sanitizeInput = function (input) {
|
||||
return input
|
||||
.replace(/\*/g, "\\2a")
|
||||
.replace(/\(/g, "\\28")
|
||||
.replace(/\)/g, "\\29")
|
||||
.replace(/\\/g, "\\5c")
|
||||
.replace(/\0/g, "\\00")
|
||||
.replace(/\//g, "\\2f");
|
||||
};
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
let q = url.parse(req.url, true);
|
||||
|
||||
let username = q.query.username;
|
||||
|
||||
var opts1 = {
|
||||
filter: `(|(name=${username})(username=${username}))`,
|
||||
};
|
||||
|
||||
client.search("o=example", opts1, function (err, res) {}); // NOT OK
|
||||
|
||||
client.search(
|
||||
"o=example",
|
||||
{ filter: `(|(name=${username})(username=${username}))` }, // NOT OK
|
||||
function (err, res) {}
|
||||
);
|
||||
|
||||
// GOOD
|
||||
client.search(
|
||||
"o=example",
|
||||
{ // OK
|
||||
filter: `(|(name=${sanitizeInput(username)})(username=${sanitizeInput(
|
||||
username
|
||||
)}))`,
|
||||
},
|
||||
function (err, res) {}
|
||||
);
|
||||
|
||||
// GOOD (https://github.com/ldapjs/node-ldapjs/issues/181)
|
||||
let f = new OrFilter({
|
||||
filters: [
|
||||
new EqualityFilter({
|
||||
attribute: "name",
|
||||
value: username,
|
||||
}),
|
||||
new EqualityFilter({
|
||||
attribute: "username",
|
||||
value: username,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
client.search("o=example", { filter: f }, function (err, res) {});
|
||||
|
||||
const parsedFilter = ldap.parseFilter(
|
||||
`(|(name=${username})(username=${username}))`
|
||||
);
|
||||
client.search("o=example", { filter: parsedFilter }, function (err, res) {}); // NOT OK
|
||||
|
||||
const dn = ldap.parseDN(`cn=${username}`, function (err, dn) {}); // NOT OK
|
||||
});
|
||||
|
||||
server.listen(389, () => {});
|
||||
@@ -0,0 +1,20 @@
|
||||
| tst-httpOnly.js:11:9:15:2 | session ... BAD\\n}) | Sensitive server cookie is missing 'httpOnly' flag. |
|
||||
| tst-httpOnly.js:29:9:29:21 | session(sess) | Sensitive server cookie is missing 'httpOnly' flag. |
|
||||
| tst-httpOnly.js:38:9:38:22 | session(sess2) | Sensitive server cookie is missing 'httpOnly' flag. |
|
||||
| tst-httpOnly.js:47:9:47:22 | session(sess3) | Sensitive server cookie is missing 'httpOnly' flag. |
|
||||
| tst-httpOnly.js:51:9:55:2 | session ... BAD\\n}) | Sensitive server cookie is missing 'httpOnly' flag. |
|
||||
| tst-httpOnly.js:68:5:73:10 | res.coo ... }) | Sensitive server cookie is missing 'httpOnly' flag. |
|
||||
| tst-httpOnly.js:78:5:81:10 | res.coo ... }) | Sensitive server cookie is missing 'httpOnly' flag. |
|
||||
| tst-httpOnly.js:101:5:101:43 | res.coo ... ptions) | Sensitive server cookie is missing 'httpOnly' flag. |
|
||||
| tst-httpOnly.js:109:5:109:43 | res.coo ... ptions) | Sensitive server cookie is missing 'httpOnly' flag. |
|
||||
| tst-httpOnly.js:118:5:118:43 | res.coo ... ptions) | Sensitive server cookie is missing 'httpOnly' flag. |
|
||||
| tst-httpOnly.js:137:5:137:43 | res.coo ... ptions) | Sensitive server cookie is missing 'httpOnly' flag. |
|
||||
| tst-httpOnly.js:148:5:148:41 | res.coo ... ptions) | Sensitive server cookie is missing 'httpOnly' flag. |
|
||||
| tst-httpOnly.js:159:5:159:43 | res.coo ... ptions) | Sensitive server cookie is missing 'httpOnly' flag. |
|
||||
| tst-httpOnly.js:170:5:170:40 | res.coo ... ptions) | Sensitive server cookie is missing 'httpOnly' flag. |
|
||||
| tst-httpOnly.js:209:37:209:51 | "authKey=ninja" | Sensitive server cookie is missing 'httpOnly' flag. |
|
||||
| tst-httpOnly.js:229:38:229:52 | "authKey=ninja" | Sensitive server cookie is missing 'httpOnly' flag. |
|
||||
| tst-httpOnly.js:289:37:289:59 | `authKe ... {attr}` | Sensitive server cookie is missing 'httpOnly' flag. |
|
||||
| tst-httpOnly.js:303:9:307:2 | session ... BAD\\n}) | Sensitive server cookie is missing 'httpOnly' flag. |
|
||||
| tst-httpOnly.js:320:9:324:2 | session ... tter\\n}) | Sensitive server cookie is missing 'httpOnly' flag. |
|
||||
| tst-httpOnly.js:330:37:330:68 | "sessio ... onKey() | Sensitive server cookie is missing 'httpOnly' flag. |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-1004/ClientExposedCookie.ql
|
||||
@@ -1,20 +0,0 @@
|
||||
| test_cookie-session.js:12:9:16:2 | session ... BAD\\n}) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
|
||||
| test_cookie-session.js:30:9:30:21 | session(sess) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
|
||||
| test_cookie-session.js:39:9:39:22 | session(sess2) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
|
||||
| test_cookie-session.js:48:9:48:22 | session(sess3) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
|
||||
| test_cookie-session.js:52:9:56:2 | session ... BAD\\n}) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
|
||||
| test_express-session.js:11:9:15:2 | session ... BAD\\n}) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
|
||||
| test_express-session.js:28:9:32:2 | session ... tter\\n}) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
|
||||
| test_httpserver.js:7:37:7:48 | "auth=ninja" | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
|
||||
| test_httpserver.js:27:37:27:70 | ["auth= ... cript"] | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
|
||||
| test_httpserver.js:57:37:57:80 | ["auth= ... cript"] | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
|
||||
| test_httpserver.js:87:37:87:59 | `sessio ... {attr}` | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
|
||||
| test_responseCookie.js:15:5:20:10 | res.coo ... }) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
|
||||
| test_responseCookie.js:25:5:28:10 | res.coo ... }) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
|
||||
| test_responseCookie.js:48:5:48:43 | res.coo ... ptions) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
|
||||
| test_responseCookie.js:56:5:56:43 | res.coo ... ptions) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
|
||||
| test_responseCookie.js:65:5:65:43 | res.coo ... ptions) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
|
||||
| test_responseCookie.js:84:5:84:43 | res.coo ... ptions) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
|
||||
| test_responseCookie.js:95:5:95:41 | res.coo ... ptions) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
|
||||
| test_responseCookie.js:106:5:106:43 | res.coo ... ptions) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
|
||||
| test_responseCookie.js:117:5:117:40 | res.coo ... ptions) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
|
||||
@@ -1 +0,0 @@
|
||||
experimental/Security/CWE-1004/CookieWithoutHttpOnly.ql
|
||||
@@ -1,56 +0,0 @@
|
||||
const express = require('express')
|
||||
const app = express()
|
||||
const session = require('cookie-session')
|
||||
const expiryDate = new Date(Date.now() + 60 * 60 * 1000)
|
||||
|
||||
app.use(session({
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2'],
|
||||
httpOnly: true, // GOOD
|
||||
}))
|
||||
|
||||
app.use(session({
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2'],
|
||||
httpOnly: false // BAD
|
||||
}))
|
||||
|
||||
app.use(session({
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2'],
|
||||
secure: true // GOOD, httpOnly is true by default
|
||||
}))
|
||||
|
||||
var sess = {
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2'],
|
||||
}
|
||||
|
||||
sess.httpOnly = false;
|
||||
app.use(session(sess)) // BAD
|
||||
|
||||
var sess2 = {
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2'],
|
||||
httpOnly: true,
|
||||
}
|
||||
|
||||
sess2.httpOnly = false;
|
||||
app.use(session(sess2)) // BAD
|
||||
|
||||
var sess3 = {
|
||||
name: 'mycookie',
|
||||
keys: ['key1', 'key2'],
|
||||
httpOnly: true,
|
||||
}
|
||||
|
||||
sess3.httpOnly = false;
|
||||
app.use(session(sess3)) // BAD, It is a session cookie, name doesn't matter
|
||||
|
||||
var flag = false
|
||||
var flag2 = flag
|
||||
app.use(session({
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2'],
|
||||
httpOnly: flag2 // BAD
|
||||
}))
|
||||
@@ -1,32 +0,0 @@
|
||||
const express = require('express')
|
||||
const app = express()
|
||||
const session = require('express-session')
|
||||
|
||||
app.use(session({
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2'],
|
||||
cookie: { httpOnly: true }, // GOOD
|
||||
}))
|
||||
|
||||
app.use(session({
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2'],
|
||||
cookie: { httpOnly: false } // BAD
|
||||
}))
|
||||
|
||||
app.use(session({
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2'],
|
||||
cookie: { secure: true } // GOOD, httpOnly is true by default
|
||||
}))
|
||||
|
||||
app.use(session({ // GOOD, httpOnly is true by default
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2']
|
||||
}))
|
||||
|
||||
app.use(session({
|
||||
name: 'mycookie',
|
||||
keys: ['key1', 'key2'],
|
||||
cookie: { httpOnly: false } // BAD, It is a session cookie, name doesn't matter
|
||||
}))
|
||||
@@ -1,91 +0,0 @@
|
||||
const http = require('http');
|
||||
|
||||
function test1() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// BAD
|
||||
res.setHeader("Set-Cookie", "auth=ninja");
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test2() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// GOOD
|
||||
res.setHeader("Set-Cookie", "auth=ninja; HttpOnly");
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test3() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// BAD
|
||||
res.setHeader("Set-Cookie", ["auth=ninja", "token=javascript"]);
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test4() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// GOOD
|
||||
res.setHeader("Set-Cookie", ["auth=ninja; HttpOnly"]);
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test5() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// GOOD, case insensitive
|
||||
res.setHeader("Set-Cookie", ["auth=ninja; httponly"]);
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test6() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// BAD
|
||||
res.setHeader("Set-Cookie", ["auth=ninja; httponly", "token=javascript"]);
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test7() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// Good, not auth related
|
||||
res.setHeader("Set-Cookie", ["foo=ninja", "bar=javascript"]);
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test8() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
let attr = "; httponly"
|
||||
res.setHeader("Set-Cookie", `session=ninja ${attr}`); // Good, httponly string expression
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test9() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
let attr = "; secure"
|
||||
res.setHeader("Set-Cookie", `session=ninja ${attr}`); // Bad, not httponly string expression
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
const express = require('express')
|
||||
const app = express()
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
res.cookie('session', 'value',
|
||||
{
|
||||
maxAge: 9000000000,
|
||||
httpOnly: true, // GOOD
|
||||
secure: false
|
||||
});
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
res.cookie('session', 'value',
|
||||
{
|
||||
maxAge: 9000000000,
|
||||
httpOnly: false, // BAD
|
||||
secure: false
|
||||
});
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
res.cookie('session', 'value',
|
||||
{
|
||||
maxAge: 9000000000
|
||||
});
|
||||
res.end('ok') // BAD
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000,
|
||||
httpOnly: true, // GOOD
|
||||
secure: false
|
||||
}
|
||||
res.cookie('session', 'value', options);
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000,
|
||||
httpOnly: false, // BAD
|
||||
secure: false
|
||||
}
|
||||
res.cookie('session', 'value', options);
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000
|
||||
}
|
||||
res.cookie('session', 'value', options); // BAD
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000
|
||||
}
|
||||
options.httpOnly = false;
|
||||
res.cookie('session', 'value', options); // BAD
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000
|
||||
}
|
||||
options.httpOnly = true;
|
||||
res.cookie('session', 'value', options); // GOOD
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000,
|
||||
httpOnly: false,
|
||||
}
|
||||
options.httpOnly = false;
|
||||
res.cookie('session', 'value', options); // BAD
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000,
|
||||
httpOnly: false,
|
||||
}
|
||||
options.httpOnly = false;
|
||||
let session = "blabla"
|
||||
res.cookie(session, 'value', options); // BAD, var name likely auth related
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000,
|
||||
httpOnly: false,
|
||||
}
|
||||
options.httpOnly = false;
|
||||
let o = { session: "blabla" }
|
||||
res.cookie(o.session, 'value', options); // BAD, var name likely auth related
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000,
|
||||
httpOnly: false,
|
||||
}
|
||||
options.httpOnly = false;
|
||||
let blabla = "session"
|
||||
res.cookie(blabla, 'value', options); // BAD, var name likely auth related
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000,
|
||||
httpOnly: true,
|
||||
}
|
||||
options.httpOnly = true;
|
||||
res.cookie('session', 'value', options); // GOOD
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000,
|
||||
httpOnly: false,
|
||||
}
|
||||
options.httpOnly = true;
|
||||
res.cookie('session', 'value', options); // GOOD
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000,
|
||||
httpOnly: false,
|
||||
}
|
||||
res.cookie('mycookie', 'value', options); // GOOD, name likely is not auth sensitive
|
||||
res.end('ok')
|
||||
})
|
||||
334
javascript/ql/test/query-tests/Security/CWE-1004/tst-httpOnly.js
Normal file
334
javascript/ql/test/query-tests/Security/CWE-1004/tst-httpOnly.js
Normal file
@@ -0,0 +1,334 @@
|
||||
const express = require('express')
|
||||
const app = express()
|
||||
const session = require('cookie-session')
|
||||
|
||||
app.use(session({
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2'],
|
||||
httpOnly: true, // GOOD
|
||||
}))
|
||||
|
||||
app.use(session({
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2'],
|
||||
httpOnly: false // BAD
|
||||
}))
|
||||
|
||||
app.use(session({
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2'],
|
||||
secure: true // GOOD, httpOnly is true by default
|
||||
}))
|
||||
|
||||
var sess = {
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2'],
|
||||
}
|
||||
|
||||
sess.httpOnly = false;
|
||||
app.use(session(sess)) // BAD
|
||||
|
||||
var sess2 = {
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2'],
|
||||
httpOnly: true,
|
||||
}
|
||||
|
||||
sess2.httpOnly = false;
|
||||
app.use(session(sess2)) // BAD
|
||||
|
||||
var sess3 = {
|
||||
name: 'mycookie',
|
||||
keys: ['key1', 'key2'],
|
||||
httpOnly: true,
|
||||
}
|
||||
|
||||
sess3.httpOnly = false;
|
||||
app.use(session(sess3)) // BAD, It is a session cookie, name doesn't matter
|
||||
|
||||
var flag = false
|
||||
var flag2 = flag
|
||||
app.use(session({
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2'],
|
||||
httpOnly: flag2 // BAD
|
||||
}))
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
res.cookie('authkey', 'value',
|
||||
{
|
||||
maxAge: 9000000000,
|
||||
httpOnly: true, // GOOD
|
||||
secure: false
|
||||
});
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
res.cookie('authkey', 'value',
|
||||
{
|
||||
maxAge: 9000000000,
|
||||
httpOnly: false, // BAD
|
||||
secure: false
|
||||
});
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
res.cookie('authkey', 'value',
|
||||
{
|
||||
maxAge: 9000000000
|
||||
});
|
||||
res.end('ok') // BAD
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000,
|
||||
httpOnly: true, // GOOD
|
||||
secure: false
|
||||
}
|
||||
res.cookie('authkey', 'value', options);
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000,
|
||||
httpOnly: false, // BAD
|
||||
secure: false
|
||||
}
|
||||
res.cookie('authkey', 'value', options);
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000
|
||||
}
|
||||
res.cookie('authkey', 'value', options); // BAD
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000
|
||||
}
|
||||
options.httpOnly = false;
|
||||
res.cookie('authkey', 'value', options); // BAD
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000
|
||||
}
|
||||
options.httpOnly = true;
|
||||
res.cookie('authkey', 'value', options); // GOOD
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000,
|
||||
httpOnly: false,
|
||||
}
|
||||
options.httpOnly = false;
|
||||
res.cookie('authkey', 'value', options); // BAD
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000,
|
||||
httpOnly: false,
|
||||
}
|
||||
options.httpOnly = false;
|
||||
let authKey = "blabla"
|
||||
res.cookie(authKey, 'value', options); // BAD, var name likely auth related
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000,
|
||||
httpOnly: false,
|
||||
}
|
||||
options.httpOnly = false;
|
||||
let o = { authKey: "blabla" }
|
||||
res.cookie(o.authKey, 'value', options); // BAD, var name likely auth related
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000,
|
||||
httpOnly: false,
|
||||
}
|
||||
options.httpOnly = false;
|
||||
let blabla = "authKey"
|
||||
res.cookie(blabla, 'value', options); // BAD, var name likely auth related
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000,
|
||||
httpOnly: true,
|
||||
}
|
||||
options.httpOnly = true;
|
||||
res.cookie('authkey', 'value', options); // GOOD
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000,
|
||||
httpOnly: false,
|
||||
}
|
||||
options.httpOnly = true;
|
||||
res.cookie('authkey', 'value', options); // GOOD
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000,
|
||||
httpOnly: false,
|
||||
}
|
||||
res.cookie('mycookie', 'value', options); // GOOD, name likely is not auth sensitive
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
const http = require('http');
|
||||
|
||||
function test1() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// BAD
|
||||
res.setHeader("Set-Cookie", "authKey=ninja");
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test2() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// GOOD
|
||||
res.setHeader("Set-Cookie", "auth=ninja; HttpOnly");
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test3() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// BAD
|
||||
res.setHeader("Set-Cookie", ["authKey=ninja", "token=javascript"]);
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test4() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// GOOD
|
||||
res.setHeader("Set-Cookie", ["auth=ninja; HttpOnly"]);
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test5() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// GOOD, case insensitive
|
||||
res.setHeader("Set-Cookie", ["auth=ninja; httponly"]);
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test6() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// OK - the sensitive cookie has httpOnly set
|
||||
res.setHeader("Set-Cookie", ["authKey=ninja; httponly", "token=javascript"]);
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test7() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// Good, not auth related
|
||||
res.setHeader("Set-Cookie", ["foo=ninja", "bar=javascript"]);
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test8() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
let attr = "; httponly"
|
||||
res.setHeader("Set-Cookie", `session=ninja ${attr}`); // Good, httponly string expression
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test9() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
let attr = "; secure"
|
||||
res.setHeader("Set-Cookie", `authKey=ninja ${attr}`); // Bad, not httponly string expression
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
const session = require('express-session')
|
||||
|
||||
app.use(session({
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2'],
|
||||
cookie: { httpOnly: true }, // GOOD
|
||||
}))
|
||||
|
||||
app.use(session({
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2'],
|
||||
cookie: { httpOnly: false } // BAD
|
||||
}))
|
||||
|
||||
app.use(session({
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2'],
|
||||
cookie: { secure: true } // GOOD, httpOnly is true by default
|
||||
}))
|
||||
|
||||
app.use(session({ // GOOD, httpOnly is true by default
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2']
|
||||
}))
|
||||
|
||||
app.use(session({
|
||||
name: 'mycookie',
|
||||
keys: ['key1', 'key2'],
|
||||
cookie: { httpOnly: false } // BAD, It is a session cookie, name doesn't matter
|
||||
}))
|
||||
|
||||
const http = require('http');
|
||||
function test10() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.setHeader("Set-Cookie", "sessionKey=" + makeSessionKey()); // BAD
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
| tst-cleartextCookie.js:5:5:10:10 | res.coo ... }) | Sensitive cookie sent without enforcing SSL encryption |
|
||||
| tst-cleartextCookie.js:20:5:20:43 | res.coo ... ptions) | Sensitive cookie sent without enforcing SSL encryption |
|
||||
| tst-cleartextCookie.js:35:1:35:52 | js_cook ... alse }) | Sensitive cookie sent without enforcing SSL encryption |
|
||||
| tst-cleartextCookie.js:44:37:44:51 | "authKey=ninja" | Sensitive cookie sent without enforcing SSL encryption |
|
||||
| tst-cleartextCookie.js:64:13:64:27 | "authKey=ninja" | Sensitive cookie sent without enforcing SSL encryption |
|
||||
| tst-cleartextCookie.js:97:13:97:25 | "authKey=foo" | Sensitive cookie sent without enforcing SSL encryption |
|
||||
| tst-cleartextCookie.js:108:9:111:2 | session ... T OK\\n}) | Sensitive cookie sent without enforcing SSL encryption |
|
||||
| tst-cleartextCookie.js:113:9:116:2 | session ... T OK\\n}) | Sensitive cookie sent without enforcing SSL encryption |
|
||||
| tst-cleartextCookie.js:118:9:121:2 | session ... T OK\\n}) | Sensitive cookie sent without enforcing SSL encryption |
|
||||
| tst-cleartextCookie.js:128:9:128:21 | session(sess) | Sensitive cookie sent without enforcing SSL encryption |
|
||||
| tst-cleartextCookie.js:152:9:160:2 | session ... Date\\n}) | Sensitive cookie sent without enforcing SSL encryption |
|
||||
| tst-cleartextCookie.js:164:33:164:58 | `authKe ... key()}` | Sensitive cookie sent without enforcing SSL encryption |
|
||||
| tst-cleartextCookie.js:177:5:177:19 | document.cookie | Sensitive cookie sent without enforcing SSL encryption |
|
||||
| tst-cleartextCookie.js:181:5:181:41 | cookies ... hkey()) | Sensitive cookie sent without enforcing SSL encryption |
|
||||
| tst-cleartextCookie.js:186:5:186:46 | cookie. ... hkey()) | Sensitive cookie sent without enforcing SSL encryption |
|
||||
| tst-cleartextCookie.js:195:33:195:74 | cookie. ... hkey()) | Sensitive cookie sent without enforcing SSL encryption |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-614/ClearTextCookie.ql
|
||||
@@ -1,11 +0,0 @@
|
||||
| test_cookie-session.js:16:9:24:2 | session ... Date\\n}) | Cookie is added to response without the 'secure' flag being set to true |
|
||||
| test_express-session.js:5:9:8:2 | session ... T OK\\n}) | Cookie is added to response without the 'secure' flag being set to true |
|
||||
| test_express-session.js:10:9:13:2 | session ... T OK\\n}) | Cookie is added to response without the 'secure' flag being set to true |
|
||||
| test_express-session.js:15:9:18:2 | session ... T OK\\n}) | Cookie is added to response without the 'secure' flag being set to true |
|
||||
| test_express-session.js:25:9:25:21 | session(sess) | Cookie is added to response without the 'secure' flag being set to true |
|
||||
| test_httpserver.js:7:37:7:48 | "type=ninja" | Cookie is added to response without the 'secure' flag being set to true |
|
||||
| test_httpserver.js:27:37:27:73 | ["type= ... cript"] | Cookie is added to response without the 'secure' flag being set to true |
|
||||
| test_httpserver.js:57:37:57:81 | ["type= ... cript"] | Cookie is added to response without the 'secure' flag being set to true |
|
||||
| test_jscookie.js:2:1:2:48 | js_cook ... alse }) | Cookie is added to response without the 'secure' flag being set to true |
|
||||
| test_responseCookie.js:5:5:10:10 | res.coo ... }) | Cookie is added to response without the 'secure' flag being set to true |
|
||||
| test_responseCookie.js:20:5:20:40 | res.coo ... ptions) | Cookie is added to response without the 'secure' flag being set to true |
|
||||
@@ -1 +0,0 @@
|
||||
experimental/Security/CWE-614/InsecureCookie.ql
|
||||
@@ -1,24 +0,0 @@
|
||||
const express = require('express')
|
||||
const app = express()
|
||||
const session = require('cookie-session')
|
||||
const expiryDate = new Date(Date.now() + 60 * 60 * 1000)
|
||||
|
||||
app.use(session({
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2'],
|
||||
secure: true, // OK
|
||||
httpOnly: true,
|
||||
domain: 'example.com',
|
||||
path: 'foo/bar',
|
||||
expires: expiryDate
|
||||
}))
|
||||
|
||||
app.use(session({
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2'],
|
||||
secure: false, // NOT OK
|
||||
httpOnly: true,
|
||||
domain: 'example.com',
|
||||
path: 'foo/bar',
|
||||
expires: expiryDate
|
||||
}))
|
||||
@@ -1,33 +0,0 @@
|
||||
const express = require('express')
|
||||
const app = express()
|
||||
const session = require('express-session')
|
||||
|
||||
app.use(session({
|
||||
secret: 'secret',
|
||||
cookie: { secure: false } // NOT OK
|
||||
}))
|
||||
|
||||
app.use(session({
|
||||
secret: 'secret'
|
||||
// NOT OK
|
||||
}))
|
||||
|
||||
app.use(session({
|
||||
secret: 'secret',
|
||||
cookie: {} // NOT OK
|
||||
}))
|
||||
|
||||
const sess = {
|
||||
secret: 'secret',
|
||||
cookie: { secure: false } // NOT OK
|
||||
}
|
||||
|
||||
app.use(session(sess))
|
||||
|
||||
|
||||
app.set('trust proxy', 1)
|
||||
app.use(session({
|
||||
secret: 'secret',
|
||||
cookie: { secure: true } // OK
|
||||
}))
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
const http = require('http');
|
||||
|
||||
function test1() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// BAD
|
||||
res.setHeader("Set-Cookie", "type=ninja");
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test2() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// GOOD
|
||||
res.setHeader("Set-Cookie", "type=ninja; Secure");
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test3() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// BAD
|
||||
res.setHeader("Set-Cookie", ["type=ninja", "language=javascript"]);
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test4() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// GOOD
|
||||
res.setHeader("Set-Cookie", ["type=ninja; Secure"]);
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test5() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// GOOD, case insensitive
|
||||
res.setHeader("Set-Cookie", ["type=ninja; secure"]);
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test6() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// BAD
|
||||
res.setHeader("Set-Cookie", ["type=ninja; secure", "language=javascript"]);
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
const js_cookie = require('js-cookie')
|
||||
js_cookie.set('key', 'value', { secure: false }); // NOT OK
|
||||
js_cookie.set('key', 'value', { secure: true }); // OK
|
||||
@@ -1,33 +0,0 @@
|
||||
const express = require('express')
|
||||
const app = express()
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
res.cookie('name', 'value',
|
||||
{
|
||||
maxAge: 9000000000,
|
||||
httpOnly: true,
|
||||
secure: false // NOT OK
|
||||
});
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/b', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000,
|
||||
httpOnly: true,
|
||||
secure: false // NOT OK
|
||||
}
|
||||
res.cookie('name', 'value', options);
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/c', function (req, res, next) {
|
||||
res.cookie('name', 'value',
|
||||
{
|
||||
maxAge: 9000000000,
|
||||
httpOnly: true,
|
||||
secure: true // OK
|
||||
});
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
const express = require('express')
|
||||
const app = express()
|
||||
|
||||
app.get('/a', function (req, res, next) {
|
||||
res.cookie('authkey', 'value',
|
||||
{
|
||||
maxAge: 9000000000,
|
||||
httpOnly: true,
|
||||
secure: false // NOT OK
|
||||
});
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/b', function (req, res, next) {
|
||||
let options = {
|
||||
maxAge: 9000000000,
|
||||
httpOnly: true,
|
||||
secure: false // NOT OK
|
||||
}
|
||||
res.cookie('authKey', 'value', options);
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
app.get('/c', function (req, res, next) {
|
||||
res.cookie('name', 'value',
|
||||
{
|
||||
maxAge: 9000000000,
|
||||
httpOnly: true,
|
||||
secure: true // OK
|
||||
});
|
||||
res.end('ok')
|
||||
})
|
||||
|
||||
const js_cookie = require('js-cookie')
|
||||
js_cookie.set('authKey', 'value', { secure: false }); // NOT OK
|
||||
js_cookie.set('authKey', 'value', { secure: true }); // OK
|
||||
|
||||
const http = require('http');
|
||||
|
||||
function test1() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// BAD
|
||||
res.setHeader("Set-Cookie", "authKey=ninja");
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test2() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// GOOD
|
||||
res.setHeader("Set-Cookie", "type=ninja; Secure");
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test3() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.setHeader("Set-Cookie", [
|
||||
"authKey=ninja", // NOT OK
|
||||
"language=javascript" // OK
|
||||
]);
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test4() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// GOOD
|
||||
res.setHeader("Set-Cookie", ["type=ninja; Secure"]);
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test5() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
// GOOD, case insensitive
|
||||
res.setHeader("Set-Cookie", ["type=ninja; secure"]);
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
function test6() {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.setHeader("Set-Cookie", [
|
||||
"type=ninja; secure", // OK
|
||||
"authKey=foo" // NOT OK
|
||||
]);
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
}
|
||||
|
||||
const express = require('express')
|
||||
const app = express()
|
||||
const session = require('express-session')
|
||||
|
||||
app.use(session({
|
||||
secret: 'secret',
|
||||
cookie: { secure: false } // NOT OK
|
||||
}))
|
||||
|
||||
app.use(session({
|
||||
secret: 'secret'
|
||||
// NOT OK
|
||||
}))
|
||||
|
||||
app.use(session({
|
||||
secret: 'secret',
|
||||
cookie: {} // NOT OK
|
||||
}))
|
||||
|
||||
const sess = {
|
||||
secret: 'secret',
|
||||
cookie: { secure: false } // NOT OK
|
||||
}
|
||||
|
||||
app.use(session(sess))
|
||||
|
||||
|
||||
app.set('trust proxy', 1)
|
||||
app.use(session({
|
||||
secret: 'secret',
|
||||
cookie: { secure: true } // OK
|
||||
}))
|
||||
|
||||
const express = require('express')
|
||||
const app = express()
|
||||
const session = require('cookie-session')
|
||||
const expiryDate = new Date(Date.now() + 60 * 60 * 1000)
|
||||
|
||||
app.use(session({
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2'],
|
||||
secure: true, // OK
|
||||
httpOnly: true,
|
||||
domain: 'example.com',
|
||||
path: 'foo/bar',
|
||||
expires: expiryDate
|
||||
}))
|
||||
|
||||
app.use(session({
|
||||
name: 'session',
|
||||
keys: ['key1', 'key2'],
|
||||
secure: false, // NOT OK
|
||||
httpOnly: true,
|
||||
domain: 'example.com',
|
||||
path: 'foo/bar',
|
||||
expires: expiryDate
|
||||
}))
|
||||
|
||||
http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.setHeader("Set-Cookie", `authKey=${makeAuthkey()}`); // NOT OK
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
|
||||
http.createServer((req, res) => {
|
||||
res.setHeader("Set-Cookie", `authKey=${makeAuthkey()}; secure; httpOnly`); // OK
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||||
res.end('<h2>Hello world</h2>');
|
||||
});
|
||||
|
||||
function clientCookies() {
|
||||
document.cookie = `authKey=${makeAuthkey()}; secure`; // OK
|
||||
document.cookie = `authKey=${makeAuthkey()}`; // NOT OK
|
||||
|
||||
var cookies = require('browser-cookies');
|
||||
|
||||
cookies.set('authKey', makeAuthkey()); // NOT OK
|
||||
cookies.set('authKey', makeAuthkey(), { secure: true, expires: 7 }); // OK
|
||||
|
||||
const cookie = require('cookie');
|
||||
|
||||
cookie.serialize('authKey', makeAuthkey()); // NOT OK
|
||||
cookie.serialize('authKey', makeAuthkey(), { secure: true, expires: 7 }); // OK
|
||||
}
|
||||
|
||||
const cookie = require('cookie');
|
||||
|
||||
http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.setHeader("Set-Cookie", cookie.serialize("authKey", makeAuthkey(), {secure: true,httpOnly: true})); // OK
|
||||
res.setHeader("Set-Cookie", cookie.serialize("authKey", makeAuthkey())); // NOT OK
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
});
|
||||
|
||||
(function mightBeSecures() {
|
||||
const express = require('express')
|
||||
const app = express()
|
||||
const session = require('express-session')
|
||||
|
||||
app.use(session({
|
||||
secret: config.sessionSecret,
|
||||
cookie: {
|
||||
httpOnly: config.sessionCookie.httpOnly,
|
||||
secure: config.sessionCookie.secure && config.secure.ssl
|
||||
},
|
||||
name: config.sessionKey
|
||||
}));
|
||||
})();
|
||||
Reference in New Issue
Block a user