Add cookbook queries

This commit is contained in:
Arthur Baars
2019-07-25 13:30:50 +02:00
parent a1b4d09b42
commit 30860daac4
156 changed files with 2342 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
/**
* @name Parameters called 'arguments'
* @description Finds parameters called 'arguments'
* @tags parameter
* arguments
*/
import javascript
from SimpleParameter p
where p.getName() = "arguments"
select p

View File

@@ -0,0 +1,13 @@
/**
* @name Calls to function
* @description Finds function calls of the form `eval(...)`
* @tags call
* function
* eval
*/
import javascript
from CallExpr c
where c.getCalleeName() = "eval"
select c

View File

@@ -0,0 +1,13 @@
/**
* @name Callbacks
* @description Finds functions that are passed as arguments to other functions
* @tags function
* callback
* higher-order
*/
import javascript
from InvokeExpr invk, DataFlow::FunctionNode f
where f.flowsToExpr(invk.getAnArgument())
select invk, f

View File

@@ -0,0 +1,15 @@
/**
* @name Classes with a default constructor
* @description Finds classes that do not declare an explicit constructor
* @tags class
* constructor
* default constructor
* ECMAScript 6
* ECMAScript 2015
*/
import javascript
from ClassDefinition c
where c.getConstructor().isSynthetic()
select c

View File

@@ -0,0 +1,14 @@
/**
* @name Classes called 'File'
* @description Finds classes called 'File'
* @tags class
* name
* ECMAScript 6
* ECMAScript 2015
*/
import javascript
from ClassDefinition cd
where cd.getName() = "File"
select cd

View File

@@ -0,0 +1,16 @@
/**
* @name Constant property name in `[]` property access
* @description Finds property accesses using the square bracket notation
* where the property name is a constant string
* @tags property access
* computed
* brackets
* index
* constant
*/
import javascript
from IndexExpr idx
where idx.getIndex() instanceof StringLiteral
select idx

View File

@@ -0,0 +1,48 @@
/**
* @name IDOR through request to backend service
* @description Finds cases where the 'userId' field in a request to another service
* is an arbitrary user-controlled value, indicating lack of authentication.
* @kind path-problem
* @tags security
* @id js/cookbook/backend-idor
*/
import javascript::DataFlow
import DataFlow::PathGraph
/**
* Tracks user-controlled values into a 'userId' property sent to a backend service.
*/
class IdorTaint extends TaintTracking::Configuration {
IdorTaint() { this = "IdorTaint" }
override predicate isSource(Node node) { node instanceof RemoteFlowSource }
override predicate isSink(Node node) { exists(ClientRequest req | node = req.getADataNode()) }
override predicate isAdditionalTaintStep(Node pred, Node succ) {
// Step from x -> { userId: x }
succ.(SourceNode).getAPropertyWrite("userId").getRhs() = pred
}
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode node) {
// After a check like `if (userId === session.user.id)`, the userId is considered safe.
node instanceof EqualityGuard
}
}
/**
* Sanitize values that have succesfully been compared to another value.
*/
class EqualityGuard extends TaintTracking::SanitizerGuardNode, ValueNode {
override EqualityTest astNode;
override predicate sanitizes(boolean outcome, Expr e) {
e = astNode.getAnOperand() and
outcome = astNode.getPolarity()
}
}
from IdorTaint cfg, PathNode source, PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Unauthenticated user ID from $@.", source.getNode(), "here"

View File

@@ -0,0 +1,29 @@
/**
* @name Decoding after sanitization
* @description Tracks the return value of 'escapeHtml' into 'decodeURI', indicating
an ineffective sanitization attempt.
* @kind path-problem
* @tags security
* @id js/cookbook/decoding-after-sanitization
*/
import javascript::DataFlow
import DataFlow::PathGraph
class DecodingAfterSanitization extends TaintTracking::Configuration {
DecodingAfterSanitization() { this = "DecodingAfterSanitization" }
override predicate isSource(Node node) { node.(CallNode).getCalleeName() = "escapeHtml" }
override predicate isSink(Node node) {
exists(CallNode call |
call.getCalleeName().matches("decodeURI%") and
node = call.getArgument(0)
)
}
}
from DecodingAfterSanitization cfg, PathNode source, PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "URI decoding invalidates the HTML sanitization performed $@.",
source.getNode(), "here"

View File

@@ -0,0 +1,51 @@
/**
* @name Decoding after sanitization (generalized)
* @description Tracks the return value of an HTML sanitizer into an escape-sequence decoder,
indicating an ineffective sanitization attempt.
* @kind path-problem
* @tags security
* @id js/cookbook/decoding-after-sanitization-generalized
*/
import javascript::DataFlow
import DataFlow::PathGraph
/**
* A call to a function that may introduce HTML meta-characters by
* replacing `%3C` or `\u003C` with `<`.
*/
class DecodingCall extends CallNode {
string kind;
Node input;
DecodingCall() {
getCalleeName().matches("decodeURI%") and
input = getArgument(0) and
kind = "URI decoding"
or
input = this.(JsonParserCall).getInput() and
kind = "JSON parsing"
}
/** Gets the decoder kind, to be used in the alert message. */
string getKind() { result = kind }
/** Gets the input being decoded. */
Node getInput() { result = input }
}
class DecodingAfterSanitization extends TaintTracking::Configuration {
DecodingAfterSanitization() { this = "DecodingAfterSanitization" }
override predicate isSource(Node node) { node instanceof HtmlSanitizerCall }
override predicate isSink(Node node) { node = any(DecodingCall c).getInput() }
}
from DecodingAfterSanitization cfg, PathNode source, PathNode sink, DecodingCall decoder
where
cfg.hasFlowPath(source, sink) and
decoder.getInput() = sink.getNode()
select sink.getNode(), source, sink,
decoder.getKind() + " invalidates the HTML sanitization performed $@.", source.getNode(), "here"

View File

@@ -0,0 +1,21 @@
/**
* @name Taint-tracking to 'eval' calls
* @description Tracks user-controlled values into 'eval' calls (special case of js/code-injection).
* @kind problem
* @tags security
* @id js/cookbook/eval-taint
*/
import javascript::DataFlow
class EvalTaint extends TaintTracking::Configuration {
EvalTaint() { this = "EvalTaint" }
override predicate isSource(Node node) { node instanceof RemoteFlowSource }
override predicate isSink(Node node) { node = globalVarRef("eval").getACall().getArgument(0) }
}
from EvalTaint cfg, Node source, Node sink
where cfg.hasFlow(source, sink)
select sink, "Eval with user-controlled input from $@.", source, "here"

View File

@@ -0,0 +1,24 @@
/**
* @name Taint-tracking to 'eval' calls (with path visualization)
* @description Tracks user-controlled values into 'eval' calls (special case of js/code-injection),
* and generates a visualizable path from the source to the sink.
* @kind path-problem
* @tags security
* @id js/cookbook/eval-taint-path
*/
import javascript::DataFlow
import DataFlow::PathGraph
class EvalTaint extends TaintTracking::Configuration {
EvalTaint() { this = "EvalTaint" }
override predicate isSource(Node node) { node instanceof RemoteFlowSource }
override predicate isSink(Node node) { node = globalVarRef("eval").getACall().getArgument(0) }
}
from EvalTaint cfg, PathNode source, PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Eval with user-controlled input from $@.", source.getNode(),
"here"

View File

@@ -0,0 +1,55 @@
/**
* @name Information disclosure through postMessage
* @description Tracks values from an 'authKey' property into a postMessage call with unrestricted origin,
indicating a leak of sensitive information.
* @kind path-problem
* @tags security
* @id js/cookbook/information-disclosure
*/
import javascript::DataFlow
import DataFlow::PathGraph
/**
* Tracks authentication tokens ("authKey") to a postMessage call with unrestricted target origin.
*
* For example:
* ```
* win.postMessage(JSON.stringify({
* action: 'pause',
* auth: {
* key: window.state.authKey
* }
* }), '*');
* ```
*/
class AuthKeyTracking extends DataFlow::Configuration {
AuthKeyTracking() { this = "AuthKeyTracking" }
override predicate isSource(Node node) { node.(PropRead).getPropertyName() = "authKey" }
override predicate isSink(Node node) {
exists(MethodCallNode call |
call.getMethodName() = "postMessage" and
call.getArgument(1).getStringValue() = "*" and // no restriction on target origin
call.getArgument(0) = node
)
}
override predicate isAdditionalFlowStep(Node pred, Node succ) {
// Step into objects: x -> { f: x }
succ.(SourceNode).getAPropertyWrite().getRhs() = pred
or
// Step through JSON serialization: x -> JSON.stringify(x)
// Note: TaintTracking::Configuration includes this step by default, but not DataFlow::Configuration
exists(CallNode call |
call = globalVarRef("JSON").getAMethodCall("stringify") and
pred = call.getArgument(0) and
succ = call
)
}
}
from AuthKeyTracking cfg, PathNode source, PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Message leaks the authKey from $@.", source.getNode(), "here"

View File

@@ -0,0 +1,34 @@
/**
* @name Extension of standard query: Stored XSS
* @description Extends the standard Stored XSS query with an additional source.
* @kind path-problem
* @tags security
* @id js/cookbook/stored-xss
*/
import javascript::DataFlow
import semmle.javascript.security.dataflow.StoredXss
import DataFlow::PathGraph
/**
* Data returned from a MySQL query, such as the `data` parameter in this example:
* ```
* let mysql = require('mysql');
* let connection = mysql.createConnection();
*
* connection.query(..., (e, data) => { ... });
* ```
*/
class MysqlSource extends StoredXss::Source {
MysqlSource() {
this = moduleImport("mysql")
.getAMemberCall("createConnection")
.getAMethodCall("query")
.getCallback(1)
.getParameter(1)
}
}
from StoredXss::Configuration cfg, PathNode source, PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Stored XSS from $@.", source.getNode(), "database value."

View File

@@ -0,0 +1,49 @@
/**
* @name Extension of standard query: Stored XSS (with TrackedNode)
* @description Extends the standard Stored XSS query with an additional source,
* using TrackedNode to track MySQL connections globally.
* @kind path-problem
* @tags security
* @id js/cookbook/stored-xss-trackednode
*/
import javascript::DataFlow
import semmle.javascript.security.dataflow.StoredXss
import DataFlow::PathGraph
/**
* An instance of `mysql.createConnection()`, tracked globally.
*/
class MysqlConnection extends TrackedNode {
MysqlConnection() { this = moduleImport("mysql").getAMemberCall("createConnection") }
/**
* Gets a call to the `query` method on this connection object.
*/
MethodCallNode getAQueryCall() {
this.flowsTo(result.getReceiver()) and
result.getMethodName() = "query"
}
}
/**
* Data returned from a MySQL query.
*
* For example:
* ```
* let mysql = require('mysql');
*
* getData(mysql.createConnection());
*
* function getData(c) {
* c.query(..., (e, data) => { ... });
* }
* ```
*/
class MysqlSource extends StoredXss::Source {
MysqlSource() { this = any(MysqlConnection con).getAQueryCall().getCallback(1).getParameter(1) }
}
from StoredXss::Configuration cfg, PathNode source, PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Stored XSS from $@.", source.getNode(), "database value."

View File

@@ -0,0 +1,39 @@
/**
* @name Template injection
* @description Tracks user-controlled values to an unescaped lodash template placeholder.
* @kind path-problem
* @tags security
* @id js/cookbook/template-injection
*/
import javascript::DataFlow
import DataFlow::PathGraph
/**
* Gets the name of an unescaped placeholder in a lodash template.
*
* For example, the string `<h1><%= title %></h1>` contains the placeholder `title`.
*/
bindingset[s]
string getAPlaceholderInString(string s) {
result = s.regexpCapture(".*<%=\\s*([a-zA-Z0-9_]+)\\s*%>.*", 1)
}
class TemplateInjection extends TaintTracking::Configuration {
TemplateInjection() { this = "TemplateInjection" }
override predicate isSource(Node node) { node instanceof RemoteFlowSource }
override predicate isSink(Node node) {
exists(CallNode call, string placeholder |
call = LodashUnderscore::member("template").getACall() and
placeholder = getAPlaceholderInString(call.getArgument(0).getStringValue()) and
node = call.getOptionArgument(1, placeholder)
)
}
}
from TemplateInjection cfg, PathNode source, PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"User-controlled value from $@ occurs unescaped in a lodash template.", source.getNode(), "here."

View File

@@ -0,0 +1,13 @@
/**
* @name Empty blocks
* @description Finds empty block statements
* @tags empty
* block
* statement
*/
import javascript
from BlockStmt blk
where not exists(blk.getAStmt())
select blk

View File

@@ -0,0 +1,16 @@
/**
* @name If statements with empty then branch
* @description Finds 'if' statements where the 'then' branch is
* an empty block statement
* @tags if
* then
* empty
* conditional
* branch
*/
import javascript
from IfStmt i
where i.getThen().(BlockStmt).getNumStmt() = 0
select i

View File

@@ -0,0 +1,14 @@
/**
* @name Equalities as expression statements
* @description Finds `==` equality expressions that form an expression statement
* @tags comparison
* equality
* non-strict
* expression statement
*/
import javascript
from ExprStmt e
where e.getExpr() instanceof EqExpr
select e

View File

@@ -0,0 +1,15 @@
/**
* @name Tests for even numbers
* @description Finds expressions of the form `e % 2 === 0`
* @tags arithmetic
* modulo
* comparison
* even
*/
import javascript
from StrictEqExpr eq, ModExpr mod, NumberLiteral zero, NumberLiteral two
where two.getValue() = "2" and mod.getRightOperand() = two and
zero.getValue() = "0" and eq.hasOperands(mod, two)
select eq

View File

@@ -0,0 +1,15 @@
/**
* @name Default exports exporting a function
* @description Finds 'default' exports that export a function
* @tags module
* export
* default export
* ECMAScript 6
* ECMAScript 2015
*/
import javascript
from ExportDefaultDeclaration e
where e.getOperand() instanceof Function
select e

View File

@@ -0,0 +1,11 @@
/**
* @name File with given name
* @description Finds files called `index.js`
* @tags file
*/
import javascript
from File f
where f.getBaseName() = "index.js"
select f

View File

@@ -0,0 +1,13 @@
/**
* @name Functions without return statements
* @description Finds functions that do not contain a return statement
* @tags function
* return
*/
import javascript
from Function f
where exists(f.getABodyStmt()) and
not exists (ReturnStmt r | r.getContainer() = f)
select f

View File

@@ -0,0 +1,14 @@
/**
* @name Generator functions
* @description Finds generator functions
* @tags generator
* function
* ECMAScript 6
* ECMAScript 2015
*/
import javascript
from Function f
where f.isGenerator()
select f

View File

@@ -0,0 +1,13 @@
/**
* @name Immediately invoked function expressions
* @description Finds calls of the form `(function(...) { ... })(...)`
* @tags call
* function
* immediately invoked
*/
import javascript
from CallExpr c
where c.getCallee().stripParens() instanceof FunctionExpr
select c

View File

@@ -0,0 +1,14 @@
/**
* @name Imports from 'react'
* @description Finds import statements that import from module 'react'
* @tags module
* import
* ECMAScript 6
* ECMAScript 2015
*/
import javascript
from ImportDeclaration id
where id.getImportedPath().getValue() = "react"
select id

View File

@@ -0,0 +1,12 @@
/**
* @name JSX attributes
* @description Finds JSX attributes named `dangerouslySetInnerHTML`
* @tags JSX
* attribute
*/
import javascript
from JSXAttribute a
where a.getName() = "dangerouslySetInnerHTML"
select a

View File

@@ -0,0 +1,13 @@
/**
* @name Method calls
* @description Finds calls of the form `this.isMounted(...)`
* @tags call
* method
*/
import javascript
from MethodCallExpr c
where c.getReceiver() instanceof ThisExpr and
c.getMethodName() = "isMounted"
select c

View File

@@ -0,0 +1,11 @@
/**
* @name Named function expression
* @description Finds function expressions that have a name
* @tags function expression
*/
import javascript
from FunctionExpr fn
where exists(fn.getName())
select fn

View File

@@ -0,0 +1,13 @@
/**
* @name New expressions
* @description Finds new expressions of the form `new RegExp(...)`
* @tags new
* constructor
* instantiation
*/
import javascript
from NewExpr new
where new.getCalleeName() = "RegExp"
select new

View File

@@ -0,0 +1,16 @@
/**
* @name Property accesses
* @description Finds property accesses of the form `x.innerHTML`
* @tags property
* field
* access
* read
* write
* reference
*/
import javascript
from PropAccess p
where p.getPropertyName() = "innerHTML"
select p

View File

@@ -0,0 +1 @@
<queries language="javascript"/>

View File

@@ -0,0 +1,14 @@
/**
* @name Methods named 'render'
* @description Finds methods named 'render'
* @tags class
* method
* ECMAScript 6
* ECMAScript 2015
*/
import javascript
from MethodDefinition m
where m.getName() = "render"
select m

View File

@@ -0,0 +1,13 @@
/**
* @name Single-quoted string literals
* @description Finds string literals using single quotes
* @tags string
* single quote
* quote
*/
import javascript
from StringLiteral s
where s.getRawValue().charAt(0) = "'"
select s

View File

@@ -0,0 +1,12 @@
/**
* @name Singleton blocks
* @description Finds block statements containing a single statement
* @tags block
* statement
*/
import javascript
from BlockStmt b
where b.getNumStmt() = 1
select b

View File

@@ -0,0 +1,12 @@
/**
* @name Tagged templates
* @description Finds tagged template expressions
* @tags template
* ECMAScript 6
* ECMAScript 2015
*/
import javascript
from TaggedTemplateExpr e
select e.getTag(), e.getTemplate()

View File

@@ -0,0 +1,12 @@
/**
* @name TODO comments
* @description Finds comments containing the word TODO
* @tags comment
* TODO
*/
import javascript
from Comment c
where c.getText().regexpMatch("(?si).*\\bTODO\\b.*")
select c

View File

@@ -0,0 +1,13 @@
/**
* @name Functions with many parameters
* @description Finds functions with more than ten parameters
* @tags function
* parameter
* argument
*/
import javascript
from Function f
where f.getNumParameter() > 10
select f

View File

@@ -0,0 +1,12 @@
/**
* @name Declaration of variable
* @description Finds places where we declare a variable called `v`
* @tags variable
* declaration
*/
import javascript
from VarDecl d
where d.getVariable().getName() = "v"
select d

View File

@@ -0,0 +1,12 @@
/**
* @name Reference to variable
* @description Finds places where we reference a variable called `undefined`
* @tags variable
* reference
*/
import javascript
from VarRef ref
where ref.getVariable().getName() = "undefined"
select ref

View File

@@ -0,0 +1,14 @@
/**
* @name Empty yield
* @description Finds yield expressions without an operand
* @tags generator
* yield
* ECMAScript 6
* ECMAScript 2015
*/
import javascript
from YieldExpr yield
where not exists(yield.getOperand())
select yield