mirror of
https://github.com/github/codeql.git
synced 2026-05-05 13:45:19 +02:00
Merge branch 'main' into extractBigReg
This commit is contained in:
@@ -63,29 +63,25 @@ module DOM {
|
||||
/**
|
||||
* An HTML element, viewed as an `ElementDefinition`.
|
||||
*/
|
||||
private class HtmlElementDefinition extends ElementDefinition, @xmlelement {
|
||||
HtmlElementDefinition() { this instanceof HTML::Element }
|
||||
|
||||
override string getName() { result = this.(HTML::Element).getName() }
|
||||
private class HtmlElementDefinition extends ElementDefinition, @xmlelement instanceof HTML::Element {
|
||||
override string getName() { result = HTML::Element.super.getName() }
|
||||
|
||||
override AttributeDefinition getAttribute(int i) {
|
||||
result = this.(HTML::Element).getAttribute(i)
|
||||
result = HTML::Element.super.getAttribute(i)
|
||||
}
|
||||
|
||||
override ElementDefinition getParent() { result = this.(HTML::Element).getParent() }
|
||||
override ElementDefinition getParent() { result = HTML::Element.super.getParent() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A JSX element, viewed as an `ElementDefinition`.
|
||||
*/
|
||||
private class JsxElementDefinition extends ElementDefinition, @jsx_element {
|
||||
JsxElementDefinition() { this instanceof JSXElement }
|
||||
private class JsxElementDefinition extends ElementDefinition, @jsx_element instanceof JSXElement {
|
||||
override string getName() { result = JSXElement.super.getName() }
|
||||
|
||||
override string getName() { result = this.(JSXElement).getName() }
|
||||
override AttributeDefinition getAttribute(int i) { result = JSXElement.super.getAttribute(i) }
|
||||
|
||||
override AttributeDefinition getAttribute(int i) { result = this.(JSXElement).getAttribute(i) }
|
||||
|
||||
override ElementDefinition getParent() { result = this.(JSXElement).getJsxParent() }
|
||||
override ElementDefinition getParent() { result = super.getJsxParent() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,14 +127,12 @@ module DOM {
|
||||
/**
|
||||
* An HTML attribute, viewed as an `AttributeDefinition`.
|
||||
*/
|
||||
private class HtmlAttributeDefinition extends AttributeDefinition, @xmlattribute {
|
||||
HtmlAttributeDefinition() { this instanceof HTML::Attribute }
|
||||
private class HtmlAttributeDefinition extends AttributeDefinition, @xmlattribute instanceof HTML::Attribute {
|
||||
override string getName() { result = HTML::Attribute.super.getName() }
|
||||
|
||||
override string getName() { result = this.(HTML::Attribute).getName() }
|
||||
override string getStringValue() { result = super.getValue() }
|
||||
|
||||
override string getStringValue() { result = this.(HTML::Attribute).getValue() }
|
||||
|
||||
override ElementDefinition getElement() { result = this.(HTML::Attribute).getElement() }
|
||||
override ElementDefinition getElement() { result = HTML::Attribute.super.getElement() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -658,7 +658,7 @@ abstract class ReExportDeclaration extends ExportDeclaration {
|
||||
cached
|
||||
Module getReExportedModule() {
|
||||
Stages::Imports::ref() and
|
||||
result.getFile() = getEnclosingModule().resolve(getImportedPath().(PathExpr))
|
||||
result.getFile() = getEnclosingModule().resolve(getImportedPath())
|
||||
or
|
||||
result = resolveFromTypeRoot()
|
||||
}
|
||||
|
||||
@@ -699,7 +699,7 @@ module PrintHTML {
|
||||
childIndex = -1 and result.(HTMLAttributesNodes).getElement() = element
|
||||
or
|
||||
exists(HTML::Element child | result.(HTMLElementNode).getElement() = child |
|
||||
child = element.(HTML::Element).getChild(childIndex)
|
||||
child = element.getChild(childIndex)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,17 +61,15 @@ class ParameterNode extends DataFlow::SourceNode {
|
||||
* new Array(16)
|
||||
* ```
|
||||
*/
|
||||
class InvokeNode extends DataFlow::SourceNode {
|
||||
InvokeNode() { this instanceof DataFlow::Impl::InvokeNodeDef }
|
||||
|
||||
class InvokeNode extends DataFlow::SourceNode instanceof DataFlow::Impl::InvokeNodeDef {
|
||||
/** Gets the syntactic invoke expression underlying this function invocation. */
|
||||
InvokeExpr getInvokeExpr() { result = this.(DataFlow::Impl::InvokeNodeDef).getInvokeExpr() }
|
||||
InvokeExpr getInvokeExpr() { result = super.getInvokeExpr() }
|
||||
|
||||
/** Gets the name of the function or method being invoked, if it can be determined. */
|
||||
string getCalleeName() { result = this.(DataFlow::Impl::InvokeNodeDef).getCalleeName() }
|
||||
string getCalleeName() { result = super.getCalleeName() }
|
||||
|
||||
/** Gets the data flow node specifying the function to be called. */
|
||||
DataFlow::Node getCalleeNode() { result = this.(DataFlow::Impl::InvokeNodeDef).getCalleeNode() }
|
||||
DataFlow::Node getCalleeNode() { result = super.getCalleeNode() }
|
||||
|
||||
/**
|
||||
* Gets the data flow node corresponding to the `i`th argument of this invocation.
|
||||
@@ -92,10 +90,10 @@ class InvokeNode extends DataFlow::SourceNode {
|
||||
* but the position of `z` cannot be determined, hence there are no first and second
|
||||
* argument nodes.
|
||||
*/
|
||||
DataFlow::Node getArgument(int i) { result = this.(DataFlow::Impl::InvokeNodeDef).getArgument(i) }
|
||||
DataFlow::Node getArgument(int i) { result = super.getArgument(i) }
|
||||
|
||||
/** Gets the data flow node corresponding to an argument of this invocation. */
|
||||
DataFlow::Node getAnArgument() { result = this.(DataFlow::Impl::InvokeNodeDef).getAnArgument() }
|
||||
DataFlow::Node getAnArgument() { result = super.getAnArgument() }
|
||||
|
||||
/** Gets the data flow node corresponding to the last argument of this invocation. */
|
||||
DataFlow::Node getLastArgument() { result = getArgument(getNumArgument() - 1) }
|
||||
@@ -112,12 +110,10 @@ class InvokeNode extends DataFlow::SourceNode {
|
||||
* ```
|
||||
* .
|
||||
*/
|
||||
DataFlow::Node getASpreadArgument() {
|
||||
result = this.(DataFlow::Impl::InvokeNodeDef).getASpreadArgument()
|
||||
}
|
||||
DataFlow::Node getASpreadArgument() { result = super.getASpreadArgument() }
|
||||
|
||||
/** Gets the number of arguments of this invocation, if it can be determined. */
|
||||
int getNumArgument() { result = this.(DataFlow::Impl::InvokeNodeDef).getNumArgument() }
|
||||
int getNumArgument() { result = super.getNumArgument() }
|
||||
|
||||
Function getEnclosingFunction() { result = getBasicBlock().getContainer() }
|
||||
|
||||
@@ -258,15 +254,13 @@ class InvokeNode extends DataFlow::SourceNode {
|
||||
* Math.abs(x)
|
||||
* ```
|
||||
*/
|
||||
class CallNode extends InvokeNode {
|
||||
CallNode() { this instanceof DataFlow::Impl::CallNodeDef }
|
||||
|
||||
class CallNode extends InvokeNode instanceof DataFlow::Impl::CallNodeDef {
|
||||
/**
|
||||
* Gets the data flow node corresponding to the receiver expression of this method call.
|
||||
*
|
||||
* For example, the receiver of `x.m()` is `x`.
|
||||
*/
|
||||
DataFlow::Node getReceiver() { result = this.(DataFlow::Impl::CallNodeDef).getReceiver() }
|
||||
DataFlow::Node getReceiver() { result = super.getReceiver() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -279,11 +273,9 @@ class CallNode extends InvokeNode {
|
||||
* Math.abs(x)
|
||||
* ```
|
||||
*/
|
||||
class MethodCallNode extends CallNode {
|
||||
MethodCallNode() { this instanceof DataFlow::Impl::MethodCallNodeDef }
|
||||
|
||||
class MethodCallNode extends CallNode instanceof DataFlow::Impl::MethodCallNodeDef {
|
||||
/** Gets the name of the invoked method, if it can be determined. */
|
||||
string getMethodName() { result = this.(DataFlow::Impl::MethodCallNodeDef).getMethodName() }
|
||||
string getMethodName() { result = super.getMethodName() }
|
||||
|
||||
/**
|
||||
* Holds if this data flow node calls method `methodName` on receiver node `receiver`.
|
||||
|
||||
@@ -53,21 +53,18 @@ abstract class RefinementCandidate extends Expr {
|
||||
* A refinement candidate that references at most one variable, and hence
|
||||
* can be used to refine the abstract values inferred for that variable.
|
||||
*/
|
||||
class Refinement extends Expr {
|
||||
Refinement() {
|
||||
this instanceof RefinementCandidate and
|
||||
count(this.(RefinementCandidate).getARefinedVar()) <= 1
|
||||
}
|
||||
class Refinement extends Expr instanceof RefinementCandidate {
|
||||
Refinement() { count(this.(RefinementCandidate).getARefinedVar()) <= 1 }
|
||||
|
||||
/**
|
||||
* Gets the variable refined by this expression, if any.
|
||||
*/
|
||||
SsaSourceVariable getRefinedVar() { result = this.(RefinementCandidate).getARefinedVar() }
|
||||
SsaSourceVariable getRefinedVar() { result = super.getARefinedVar() }
|
||||
|
||||
/**
|
||||
* Gets a refinement value inferred for this expression in context `ctxt`.
|
||||
*/
|
||||
RefinementValue eval(RefinementContext ctxt) { result = this.(RefinementCandidate).eval(ctxt) }
|
||||
RefinementValue eval(RefinementContext ctxt) { result = super.eval(ctxt) }
|
||||
}
|
||||
|
||||
/** A literal, viewed as a refinement expression. */
|
||||
|
||||
@@ -8,7 +8,7 @@ pragma[nomagic]
|
||||
predicate isAnalyzedParameter(Parameter p) {
|
||||
exists(FunctionWithAnalyzedParameters f, int parmIdx | p = f.getParameter(parmIdx) |
|
||||
// we cannot track flow into rest parameters
|
||||
not p.(Parameter).isRestParameter()
|
||||
not p.isRestParameter()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -199,7 +199,7 @@ private class AnalyzedNewExpr extends DataFlow::AnalyzedValueNode {
|
||||
*/
|
||||
private predicate isIndefinite() {
|
||||
exists(DataFlow::AnalyzedNode callee, AbstractValue calleeVal |
|
||||
callee = astNode.(NewExpr).getCallee().analyze() and
|
||||
callee = astNode.getCallee().analyze() and
|
||||
calleeVal = callee.getALocalValue()
|
||||
|
|
||||
calleeVal.isIndefinite(_) or
|
||||
@@ -217,7 +217,7 @@ private class NewInstance extends DataFlow::AnalyzedValueNode {
|
||||
|
||||
override AbstractValue getALocalValue() {
|
||||
exists(DataFlow::AnalyzedNode callee |
|
||||
callee = astNode.(NewExpr).getCallee().analyze() and
|
||||
callee = astNode.getCallee().analyze() and
|
||||
result = TAbstractInstance(callee.getALocalValue())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,97 @@
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Classes and predicates for reasoning about writes to cookies.
|
||||
*/
|
||||
module CookieWrites {
|
||||
/**
|
||||
* A write to a cookie.
|
||||
*/
|
||||
abstract class CookieWrite extends DataFlow::Node {
|
||||
/**
|
||||
* Holds if this cookie is secure, i.e. only transmitted over SSL.
|
||||
*/
|
||||
abstract predicate isSecure();
|
||||
|
||||
/**
|
||||
* Holds if this cookie is HttpOnly, i.e. not accessible by JavaScript.
|
||||
*/
|
||||
abstract predicate isHttpOnly();
|
||||
|
||||
/**
|
||||
* Holds if the cookie likely is an authentication cookie or otherwise sensitive.
|
||||
*/
|
||||
abstract predicate isSensitive();
|
||||
|
||||
/**
|
||||
* Holds if the cookie write happens on a server, i.e. the `httpOnly` flag is relevant.
|
||||
*/
|
||||
predicate isServerSide() {
|
||||
any() // holds by default. Client-side cookie writes should extend ClientSideCookieWrite.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A client-side write to a cookie.
|
||||
*/
|
||||
abstract class ClientSideCookieWrite extends CookieWrite {
|
||||
final override predicate isHttpOnly() { none() }
|
||||
|
||||
final override predicate isServerSide() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The flag that indicates that a cookie is secure.
|
||||
*/
|
||||
string secure() { result = "secure" }
|
||||
|
||||
/**
|
||||
* The flag that indicates that a cookie is HttpOnly.
|
||||
*/
|
||||
string httpOnly() { result = "httpOnly" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` looks like it can contain a sensitive cookie.
|
||||
*
|
||||
* Heuristics:
|
||||
* - `node` contains a string value that looks like a sensitive cookie name
|
||||
* - `node` is a sensitive expression
|
||||
*/
|
||||
private predicate canHaveSensitiveCookie(DataFlow::Node node) {
|
||||
exists(string s |
|
||||
node.mayHaveStringValue(s) or
|
||||
s = node.(StringOps::ConcatenationRoot).getConstantStringParts()
|
||||
|
|
||||
HeuristicNames::nameIndicatesSensitiveData([s, getCookieName(s)], _)
|
||||
)
|
||||
or
|
||||
node.asExpr() instanceof SensitiveExpr
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cookie name of a `Set-Cookie` header value.
|
||||
* The header value always starts with `<cookie-name>=<cookie-value>` optionally followed by attributes:
|
||||
* `<cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly`
|
||||
*/
|
||||
bindingset[s]
|
||||
private string getCookieName(string s) { result = s.regexpCapture("([^=]*)=.*", 1).trim() }
|
||||
|
||||
/**
|
||||
* Holds if the `Set-Cookie` header value contains the specified attribute
|
||||
* 1. The attribute is case insensitive
|
||||
* 2. It always starts with a pair `<cookie-name>=<cookie-value>`.
|
||||
* If the attribute is present there must be `;` after the pair.
|
||||
* Other attributes like `Domain=`, `Path=`, etc. may come after the pair:
|
||||
* `<cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly`
|
||||
* See `https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie`
|
||||
*/
|
||||
bindingset[s, attribute]
|
||||
private predicate hasCookieAttribute(string s, string attribute) {
|
||||
s.regexpMatch("(?i).*;\\s*" + attribute + "\\b\\s*;?.*$")
|
||||
}
|
||||
|
||||
/**
|
||||
* A model of the `js-cookie` library (https://github.com/js-cookie/js-cookie).
|
||||
*/
|
||||
@@ -25,12 +116,22 @@ private module JsCookie {
|
||||
}
|
||||
}
|
||||
|
||||
class WriteAccess extends PersistentWriteAccess, DataFlow::CallNode {
|
||||
class WriteAccess extends PersistentWriteAccess, DataFlow::CallNode,
|
||||
CookieWrites::ClientSideCookieWrite {
|
||||
WriteAccess() { this = libMemberCall("set") }
|
||||
|
||||
string getKey() { getArgument(0).mayHaveStringValue(result) }
|
||||
|
||||
override DataFlow::Node getValue() { result = getArgument(1) }
|
||||
|
||||
override predicate isSecure() {
|
||||
// A cookie is secure if there are cookie options with the `secure` flag set to `true`.
|
||||
exists(DataFlow::Node value | value = this.getOptionArgument(2, CookieWrites::secure()) |
|
||||
not value.mayHaveBooleanValue(false) // anything but `false` is accepted as being maybe true
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSensitive() { canHaveSensitiveCookie(this.getArgument(0)) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,12 +154,25 @@ private module BrowserCookies {
|
||||
}
|
||||
}
|
||||
|
||||
class WriteAccess extends PersistentWriteAccess, DataFlow::CallNode {
|
||||
class WriteAccess extends PersistentWriteAccess, DataFlow::CallNode,
|
||||
CookieWrites::ClientSideCookieWrite {
|
||||
WriteAccess() { this = libMemberCall("set") }
|
||||
|
||||
string getKey() { getArgument(0).mayHaveStringValue(result) }
|
||||
|
||||
override DataFlow::Node getValue() { result = getArgument(1) }
|
||||
|
||||
override predicate isSecure() {
|
||||
// A cookie is secure if there are cookie options with the `secure` flag set to `true`.
|
||||
exists(DataFlow::Node value | value = this.getOptionArgument(2, CookieWrites::secure()) |
|
||||
not value.mayHaveBooleanValue(false) // anything but `false` is accepted as being maybe true
|
||||
)
|
||||
or
|
||||
// or, an explicit default has been set
|
||||
exists(DataFlow::moduleMember("browser-cookies", "defaults").getAPropertyWrite("secure"))
|
||||
}
|
||||
|
||||
override predicate isSensitive() { canHaveSensitiveCookie(this.getArgument(0)) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,11 +195,174 @@ private module LibCookie {
|
||||
override PersistentWriteAccess getAWrite() { key = result.(WriteAccess).getKey() }
|
||||
}
|
||||
|
||||
class WriteAccess extends PersistentWriteAccess, DataFlow::CallNode {
|
||||
class WriteAccess extends PersistentWriteAccess, DataFlow::CallNode,
|
||||
CookieWrites::ClientSideCookieWrite {
|
||||
WriteAccess() { this = libMemberCall("serialize") }
|
||||
|
||||
string getKey() { getArgument(0).mayHaveStringValue(result) }
|
||||
|
||||
override DataFlow::Node getValue() { result = getArgument(1) }
|
||||
|
||||
override predicate isSecure() {
|
||||
// A cookie is secure if there are cookie options with the `secure` flag set to `true`.
|
||||
exists(DataFlow::Node value | value = this.getOptionArgument(2, CookieWrites::secure()) |
|
||||
not value.mayHaveBooleanValue(false) // anything but `false` is accepted as being maybe true
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSensitive() { canHaveSensitiveCookie(this.getArgument(0)) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A model of cookies in an express application.
|
||||
*/
|
||||
private module ExpressCookies {
|
||||
/**
|
||||
* A cookie set using `response.cookie` from `express` module (https://expressjs.com/en/api.html#res.cookie).
|
||||
*/
|
||||
private class InsecureExpressCookieResponse extends CookieWrites::CookieWrite,
|
||||
DataFlow::MethodCallNode {
|
||||
InsecureExpressCookieResponse() { this.asExpr() instanceof Express::SetCookie }
|
||||
|
||||
override predicate isSecure() {
|
||||
// A cookie is secure if there are cookie options with the `secure` flag set to `true`.
|
||||
// The default is `false`.
|
||||
exists(DataFlow::Node value | value = this.getOptionArgument(2, CookieWrites::secure()) |
|
||||
not value.mayHaveBooleanValue(false) // anything but `false` is accepted as being maybe true
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSensitive() { canHaveSensitiveCookie(this.getArgument(0)) }
|
||||
|
||||
override predicate isHttpOnly() {
|
||||
// A cookie is httpOnly if there are cookie options with the `httpOnly` flag set to `true`.
|
||||
// The default is `false`.
|
||||
exists(DataFlow::Node value | value = this.getOptionArgument(2, CookieWrites::httpOnly()) |
|
||||
not value.mayHaveBooleanValue(false) // anything but `false` is accepted as being maybe true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cookie set using the `express` module `cookie-session` (https://github.com/expressjs/cookie-session).
|
||||
*/
|
||||
class InsecureCookieSession extends ExpressLibraries::CookieSession::MiddlewareInstance,
|
||||
CookieWrites::CookieWrite {
|
||||
private DataFlow::Node getCookieFlagValue(string flag) {
|
||||
result = this.getOptionArgument(0, flag)
|
||||
}
|
||||
|
||||
override predicate isSecure() {
|
||||
// The flag `secure` is set to `false` by default for HTTP, `true` by default for HTTPS (https://github.com/expressjs/cookie-session#cookie-options).
|
||||
// A cookie is secure if the `secure` flag is not explicitly set to `false`.
|
||||
not getCookieFlagValue(CookieWrites::secure()).mayHaveBooleanValue(false)
|
||||
}
|
||||
|
||||
override predicate isSensitive() {
|
||||
any() // It is a session cookie, likely auth sensitive
|
||||
}
|
||||
|
||||
override predicate isHttpOnly() {
|
||||
// The flag `httpOnly` is set to `true` by default (https://github.com/expressjs/cookie-session#cookie-options).
|
||||
// A cookie is httpOnly if the `httpOnly` flag is not explicitly set to `false`.
|
||||
not getCookieFlagValue(CookieWrites::httpOnly()).mayHaveBooleanValue(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cookie set using the `express` module `express-session` (https://github.com/expressjs/session).
|
||||
*/
|
||||
class InsecureExpressSessionCookie extends ExpressLibraries::ExpressSession::MiddlewareInstance,
|
||||
CookieWrites::CookieWrite {
|
||||
private DataFlow::Node getCookieFlagValue(string flag) {
|
||||
result = this.getOption("cookie").getALocalSource().getAPropertyWrite(flag).getRhs()
|
||||
}
|
||||
|
||||
override predicate isSecure() {
|
||||
// The flag `secure` is not set by default (https://github.com/expressjs/session#Cookiesecure).
|
||||
// The default value for cookie options is { path: '/', httpOnly: true, secure: false, maxAge: null }.
|
||||
exists(DataFlow::Node value | value = getCookieFlagValue(CookieWrites::secure()) |
|
||||
not value.mayHaveBooleanValue(false) // anything but `false` is accepted as being maybe true
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSensitive() {
|
||||
any() // It is a session cookie, likely auth sensitive
|
||||
}
|
||||
|
||||
override predicate isHttpOnly() {
|
||||
// The flag `httpOnly` is set by default (https://github.com/expressjs/session#Cookiesecure).
|
||||
// The default value for cookie options is { path: '/', httpOnly: true, secure: false, maxAge: null }.
|
||||
// A cookie is httpOnly if the `httpOnly` flag is not explicitly set to `false`.
|
||||
not getCookieFlagValue(CookieWrites::httpOnly()).mayHaveBooleanValue(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cookie set using `Set-Cookie` header of an `HTTP` response, where a raw header is used.
|
||||
* (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie).
|
||||
* This class does not model the Express implementation of `HTTP::CookieDefintion`
|
||||
* as the express implementation does not use raw headers.
|
||||
*
|
||||
* In case an array is passed `setHeader("Set-Cookie", [...]` it sets multiple cookies.
|
||||
* We model a `CookieWrite` for each array element.
|
||||
*/
|
||||
private class HTTPCookieWrite extends CookieWrites::CookieWrite {
|
||||
string header;
|
||||
|
||||
HTTPCookieWrite() {
|
||||
exists(HTTP::CookieDefinition setCookie |
|
||||
this.asExpr() = setCookie.getHeaderArgument() and
|
||||
not this instanceof DataFlow::ArrayCreationNode
|
||||
or
|
||||
this = setCookie.getHeaderArgument().flow().(DataFlow::ArrayCreationNode).getAnElement()
|
||||
) and
|
||||
header =
|
||||
[
|
||||
any(string s | this.mayHaveStringValue(s)),
|
||||
this.(StringOps::ConcatenationRoot).getConstantStringParts()
|
||||
]
|
||||
}
|
||||
|
||||
override predicate isSecure() {
|
||||
// A cookie is secure if the `secure` flag is specified in the cookie definition.
|
||||
// The default is `false`.
|
||||
hasCookieAttribute(header, CookieWrites::secure())
|
||||
}
|
||||
|
||||
override predicate isHttpOnly() {
|
||||
// A cookie is httpOnly if the `httpOnly` flag is specified in the cookie definition.
|
||||
// The default is `false`.
|
||||
hasCookieAttribute(header, CookieWrites::httpOnly())
|
||||
}
|
||||
|
||||
override predicate isSensitive() { canHaveSensitiveCookie(this) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A write to `document.cookie`.
|
||||
*/
|
||||
private class DocumentCookieWrite extends CookieWrites::ClientSideCookieWrite {
|
||||
string cookie;
|
||||
DataFlow::PropWrite write;
|
||||
|
||||
DocumentCookieWrite() {
|
||||
this = write and
|
||||
write = DOM::documentRef().getAPropertyWrite("cookie") and
|
||||
cookie =
|
||||
[
|
||||
any(string s | write.getRhs().mayHaveStringValue(s)),
|
||||
write.getRhs().(StringOps::ConcatenationRoot).getConstantStringParts()
|
||||
]
|
||||
}
|
||||
|
||||
override predicate isSecure() {
|
||||
// A cookie is secure if the `secure` flag is specified in the cookie definition.
|
||||
// The default is `false`.
|
||||
hasCookieAttribute(cookie, CookieWrites::secure())
|
||||
}
|
||||
|
||||
override predicate isSensitive() { canHaveSensitiveCookie(write.getRhs()) }
|
||||
}
|
||||
|
||||
71
javascript/ql/lib/semmle/javascript/frameworks/LdapJS.qll
Normal file
71
javascript/ql/lib/semmle/javascript/frameworks/LdapJS.qll
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Provides classes for working with [LDAPjs](https://www.npmjs.com/package/ldapjs)
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* A module providing sinks and sanitizers for LDAP injection.
|
||||
*/
|
||||
module LdapJS {
|
||||
/** Gets a reference to the ldapjs library. */
|
||||
API::Node ldapjs() { result = API::moduleImport("ldapjs") }
|
||||
|
||||
/** Gets an LDAPjs client. */
|
||||
private API::Node ldapClient() { result = ldapjs().getMember("createClient").getReturn() }
|
||||
|
||||
/** A call to a LDAPjs Client API method. */
|
||||
class ClientCall extends API::CallNode {
|
||||
string methodName;
|
||||
|
||||
ClientCall() {
|
||||
methodName = ["add", "bind", "compare", "del", "modify", "modifyDN", "search"] and
|
||||
this = ldapClient().getMember(methodName).getACall()
|
||||
}
|
||||
|
||||
/** Gets the name of the LDAPjs Client API method. */
|
||||
string getMethodName() { result = methodName }
|
||||
}
|
||||
|
||||
/** A reference to a LDAPjs client `search` options. */
|
||||
class SearchOptions extends API::Node {
|
||||
ClientCall call;
|
||||
|
||||
SearchOptions() { call.getMethodName() = "search" and this = call.getParameter(1) }
|
||||
}
|
||||
|
||||
/** A creation of an LDAPjs filter, or object containing a filter, that doesn't sanitizes the input. */
|
||||
abstract class TaintPreservingLdapFilterStep extends DataFlow::Node {
|
||||
/** The input that creates (part of) an LDAPjs filter. */
|
||||
abstract DataFlow::Node getInput();
|
||||
|
||||
/** The resulting LDAPjs filter. */
|
||||
abstract DataFlow::Node getOutput();
|
||||
}
|
||||
|
||||
/** A call to the LDAPjs utility method "parseFilter". */
|
||||
private class ParseFilter extends TaintPreservingLdapFilterStep, API::CallNode {
|
||||
ParseFilter() { this = ldapjs().getMember("parseFilter").getACall() }
|
||||
|
||||
override DataFlow::Node getInput() { result = this.getArgument(0) }
|
||||
|
||||
override DataFlow::Node getOutput() { result = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter used in call to "search" on an LDAPjs client.
|
||||
* We model that as a step from the ".filter" write to the options object itself.
|
||||
*/
|
||||
private class SearchFilter extends TaintPreservingLdapFilterStep {
|
||||
SearchOptions options;
|
||||
|
||||
SearchFilter() {
|
||||
options = ldapClient().getMember("search").getACall().getParameter(1) and
|
||||
this = options.getARhs()
|
||||
}
|
||||
|
||||
override DataFlow::Node getInput() { result = options.getMember("filter").getARhs() }
|
||||
|
||||
override DataFlow::Node getOutput() { result = this }
|
||||
}
|
||||
}
|
||||
@@ -139,7 +139,7 @@ private class JQueryDomElementDefinition extends DOM::ElementDefinition, @call_e
|
||||
JQueryDomElementDefinition() {
|
||||
this = call and
|
||||
call = jquery().getACall().asExpr() and
|
||||
exists(string s | s = call.getArgument(0).(Expr).getStringValue() |
|
||||
exists(string s | s = call.getArgument(0).getStringValue() |
|
||||
// match an opening angle bracket followed by a tag name, followed by arbitrary
|
||||
// text and a closing angle bracket, potentially with whitespace in between
|
||||
tagName = s.regexpCapture("\\s*<\\s*(\\w+)\\b[^>]*>\\s*", 1).toLowerCase()
|
||||
|
||||
@@ -14,11 +14,14 @@ import semmle.javascript.security.internal.SensitiveDataHeuristics
|
||||
private import HeuristicNames
|
||||
|
||||
/** An expression that might contain sensitive data. */
|
||||
cached
|
||||
abstract class SensitiveExpr extends Expr {
|
||||
/** Gets a human-readable description of this expression for use in alert messages. */
|
||||
cached
|
||||
abstract string describe();
|
||||
|
||||
/** Gets a classification of the kind of sensitive data this expression might contain. */
|
||||
cached
|
||||
abstract SensitiveDataClassification getClassification();
|
||||
}
|
||||
|
||||
|
||||
@@ -317,7 +317,7 @@ module PrettyPrintCatCall {
|
||||
*/
|
||||
string createFileThatIsReadFromCommandList(CommandCall call) {
|
||||
exists(DataFlow::ArrayCreationNode array, DataFlow::Node element |
|
||||
array = call.getArgumentList().(DataFlow::ArrayCreationNode) and
|
||||
array = call.getArgumentList() and
|
||||
array.getSize() = 1 and
|
||||
element = array.getElement(0)
|
||||
|
|
||||
|
||||
@@ -41,4 +41,35 @@ module SqlInjection {
|
||||
class GraphqlInjectionSink extends Sink {
|
||||
GraphqlInjectionSink() { this instanceof GraphQL::GraphQLString }
|
||||
}
|
||||
|
||||
/**
|
||||
* An LDAPjs sink.
|
||||
*/
|
||||
class LdapJSSink extends Sink {
|
||||
LdapJSSink() {
|
||||
// A distinguished name (DN) used in a call to the client API.
|
||||
this = any(LdapJS::ClientCall call).getArgument(0)
|
||||
or
|
||||
// A search options object, which contains a filter and a baseDN.
|
||||
this = any(LdapJS::SearchOptions opt).getARhs()
|
||||
or
|
||||
// A call to "parseDN", which parses a DN from a string.
|
||||
this = LdapJS::ldapjs().getMember("parseDN").getACall().getArgument(0)
|
||||
}
|
||||
}
|
||||
|
||||
import semmle.javascript.security.IncompleteBlacklistSanitizer as IncompleteBlacklistSanitizer
|
||||
|
||||
/**
|
||||
* A chain of replace calls that replaces all unsafe chars for ldap injection.
|
||||
* For simplicity it's used as a sanitizer for all of `js/sql-injection`.
|
||||
*/
|
||||
class LdapStringSanitizer extends Sanitizer,
|
||||
IncompleteBlacklistSanitizer::StringReplaceCallSequence {
|
||||
LdapStringSanitizer() {
|
||||
forall(string char | char = ["*", "(", ")", "\\", "/"] |
|
||||
this.getAMember().getAReplacedString() = char
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,4 +24,11 @@ class Configuration extends TaintTracking::Configuration {
|
||||
super.isSanitizer(node) or
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(LdapJS::TaintPreservingLdapFilterStep filter |
|
||||
pred = filter.getInput() and
|
||||
succ = filter.getOutput()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,9 +526,7 @@ module ReflectedXss {
|
||||
* ```
|
||||
*/
|
||||
predicate isLocalHeaderDefinition(HTTP::HeaderDefinition header) {
|
||||
exists(ReachableBasicBlock headerBlock |
|
||||
headerBlock = header.getBasicBlock().(ReachableBasicBlock)
|
||||
|
|
||||
exists(ReachableBasicBlock headerBlock | headerBlock = header.getBasicBlock() |
|
||||
1 =
|
||||
strictcount(HTTP::ResponseSendArgument sender |
|
||||
sender.getRouteHandler() = header.getRouteHandler() and
|
||||
|
||||
@@ -58,7 +58,7 @@ module HeuristicNames {
|
||||
*/
|
||||
string maybeAccountInfo() {
|
||||
result = "(?is).*acc(ou)?nt.*" or
|
||||
result = "(?is).*(puid|username|userid).*" or
|
||||
result = "(?is).*(puid|username|userid|session(id|key)).*" or
|
||||
result = "(?s).*([uU]|^|_|[a-z](?=U))([uU][iI][dD]).*"
|
||||
}
|
||||
|
||||
|
||||
@@ -47,10 +47,8 @@ module PolynomialReDoS {
|
||||
* A remote input to a server, seen as a source for polynomial
|
||||
* regular expression denial-of-service vulnerabilities.
|
||||
*/
|
||||
class RequestInputAccessAsSource extends Source {
|
||||
RequestInputAccessAsSource() { this instanceof HTTP::RequestInputAccess }
|
||||
|
||||
override string getKind() { result = this.(HTTP::RequestInputAccess).getKind() }
|
||||
class RequestInputAccessAsSource extends Source instanceof HTTP::RequestInputAccess {
|
||||
override string getKind() { result = HTTP::RequestInputAccess.super.getKind() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user