Merge branch 'main' into extractBigReg

This commit is contained in:
Erik Krogh Kristensen
2021-11-03 13:08:51 +01:00
313 changed files with 7234 additions and 3217 deletions

View 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)

View 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.

View File

@@ -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()]
)
}

View File

@@ -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

View File

@@ -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() }
}
/**

View File

@@ -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()
}

View File

@@ -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)
)
}
}

View File

@@ -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`.

View File

@@ -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. */

View File

@@ -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()
)
}

View File

@@ -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())
)
}

View File

@@ -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()) }
}

View 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 }
}
}

View File

@@ -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()

View File

@@ -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();
}

View File

@@ -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)
|

View File

@@ -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
)
}
}
}

View File

@@ -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()
)
}
}

View File

@@ -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

View File

@@ -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]).*"
}

View File

@@ -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() }
}
/**

View File

@@ -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"
}

View File

@@ -9,6 +9,8 @@
* @id js/sql-injection
* @tags security
* external/cwe/cwe-089
* external/cwe/cwe-090
* external/cwe/cwe-943
*/
import javascript

View File

@@ -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>

View 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."

View File

@@ -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>');
});

View File

@@ -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>');
});

View File

@@ -10,6 +10,7 @@
* @id js/stack-trace-exposure
* @tags security
* external/cwe/cwe-209
* external/cwe/cwe-497
*/
import javascript

View 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>

View 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"

View File

@@ -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>');
});

View File

@@ -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>');
});

View File

@@ -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>

View File

@@ -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"

View File

@@ -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
)
}
}
}

View File

@@ -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 + ")")
)
}
}
}

View File

@@ -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") }
}
}

View File

@@ -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) {
});
});

View File

@@ -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) {
});
});

View File

@@ -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) {
});
});

View File

@@ -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) {
});
});

View File

@@ -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>

View File

@@ -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."

View File

@@ -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>

View File

@@ -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"

View File

@@ -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
}
}

View File

@@ -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"

View File

@@ -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()

View File

@@ -0,0 +1,8 @@
abstract class Q {
abstract test();
static test() {}
method() {
this.test(); // OK
}
}

View File

@@ -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 |

View File

@@ -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, () => {});

View File

@@ -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. |

View File

@@ -0,0 +1 @@
Security/CWE-1004/ClientExposedCookie.ql

View File

@@ -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. |

View File

@@ -1 +0,0 @@
experimental/Security/CWE-1004/CookieWithoutHttpOnly.ql

View File

@@ -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
}))

View File

@@ -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
}))

View File

@@ -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');
});
}

View File

@@ -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')
})

View 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');
});
}

View File

@@ -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 |

View File

@@ -0,0 +1 @@
Security/CWE-614/ClearTextCookie.ql

View File

@@ -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 |

View File

@@ -1 +0,0 @@
experimental/Security/CWE-614/InsecureCookie.ql

View File

@@ -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
}))

View File

@@ -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
}))

View File

@@ -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');
});
}

View File

@@ -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

View File

@@ -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')
})

View File

@@ -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
}));
})();