Merge branch 'main' into moreReDoS

This commit is contained in:
Erik Krogh Kristensen
2020-11-17 17:34:49 +01:00
357 changed files with 9192 additions and 5175 deletions

View File

@@ -0,0 +1,22 @@
/**
* Provides shared predicates related to contextual queries in the code viewer.
*/
import semmle.files.FileSystem
/**
* Returns the `File` matching the given source file name as encoded by the VS
* Code extension.
*/
cached
File getFileBySourceArchiveName(string name) {
// The name provided for a file in the source archive by the VS Code extension
// has some differences from the absolute path in the database:
// 1. colons are replaced by underscores
// 2. there's a leading slash, even for Windows paths: "C:/foo/bar" ->
// "/C_/foo/bar"
// 3. double slashes in UNC prefixes are replaced with a single slash
// We can handle 2 and 3 together by unconditionally adding a leading slash
// before replacing double slashes.
name = ("/" + result.getAbsolutePath().replaceAll(":", "_")).replaceAll("//", "/")
}

View File

@@ -4,6 +4,7 @@
*/
import javascript
import IDEContextual
private import Declarations.Declarations
/**
@@ -178,11 +179,3 @@ ASTNode definitionOf(Locatable e, string kind) {
or
jsdocTypeLookup(e, result, kind)
}
/**
* Returns an appropriately encoded version of a filename `name`
* passed by the VS Code extension in order to coincide with the
* output of `.getFile()` on locatable entities.
*/
cached
File getEncodedFile(string name) { result.getAbsolutePath().replaceAll(":", "_") = name }

View File

@@ -123,9 +123,11 @@ module Cookie {
class InsecureJsCookie extends Cookie {
InsecureJsCookie() {
this =
[DataFlow::globalVarRef("Cookie"),
DataFlow::globalVarRef("Cookie").getAMemberCall("noConflict"),
DataFlow::moduleImport("js-cookie")].getAMemberCall("set")
[
DataFlow::globalVarRef("Cookie"),
DataFlow::globalVarRef("Cookie").getAMemberCall("noConflict"),
DataFlow::moduleImport("js-cookie")
].getAMemberCall("set")
}
override string getKind() { result = "js-cookie" }

View File

@@ -79,12 +79,14 @@ import semmle.javascript.frameworks.ClosureLibrary
import semmle.javascript.frameworks.CookieLibraries
import semmle.javascript.frameworks.Credentials
import semmle.javascript.frameworks.CryptoLibraries
import semmle.javascript.frameworks.DateFunctions
import semmle.javascript.frameworks.DigitalOcean
import semmle.javascript.frameworks.Electron
import semmle.javascript.frameworks.EventEmitter
import semmle.javascript.frameworks.Files
import semmle.javascript.frameworks.Firebase
import semmle.javascript.frameworks.jQuery
import semmle.javascript.frameworks.JWT
import semmle.javascript.frameworks.Handlebars
import semmle.javascript.frameworks.LazyCache
import semmle.javascript.frameworks.LodashUnderscore

View File

@@ -12,5 +12,5 @@ import definitions
external string selectedSourceFile();
from Locatable e, ASTNode def, string kind
where def = definitionOf(e, kind) and e.getFile() = getEncodedFile(selectedSourceFile())
where def = definitionOf(e, kind) and e.getFile() = getFileBySourceArchiveName(selectedSourceFile())
select e, def, kind

View File

@@ -12,5 +12,6 @@ import definitions
external string selectedSourceFile();
from Locatable e, ASTNode def, string kind
where def = definitionOf(e, kind) and def.getFile() = getEncodedFile(selectedSourceFile())
where
def = definitionOf(e, kind) and def.getFile() = getFileBySourceArchiveName(selectedSourceFile())
select e, def, kind

View File

@@ -23,6 +23,6 @@ class PrintAstConfigurationOverride extends PrintAstConfiguration {
*/
override predicate shouldPrint(Locatable e, Location l) {
super.shouldPrint(e, l) and
l.getFile() = getEncodedFile(selectedSourceFile())
l.getFile() = getFileBySourceArchiveName(selectedSourceFile())
}
}

View File

@@ -40,6 +40,40 @@ class ES2015Module extends Module {
}
}
/**
* Holds if `mod` contains one or more named export declarations other than `default`.
*/
private predicate hasNamedExports(ES2015Module mod) {
mod.getAnExport().(ExportNamedDeclaration).getASpecifier().getExportedName() != "default"
or
exists(mod.getAnExport().(ExportNamedDeclaration).getAnExportedDecl())
or
// Bulk re-exports only export named bindings (not "default")
mod.getAnExport() instanceof BulkReExportDeclaration
}
/**
* Holds if this module contains a `default` export.
*/
private predicate hasDefaultExport(ES2015Module mod) {
// export default foo;
mod.getAnExport() instanceof ExportDefaultDeclaration
or
// export { foo as default };
mod.getAnExport().(ExportNamedDeclaration).getASpecifier().getExportedName() = "default"
}
/**
* Holds if `mod` contains both named and `default` exports.
*
* This is used to determine whether a default-import of the module should be reinterpreted
* as a namespace-import, to accomodate the non-standard behavior implemented by some compilers.
*/
private predicate hasBothNamedAndDefaultExports(ES2015Module mod) {
hasNamedExports(mod) and
hasDefaultExport(mod)
}
/**
* An import declaration.
*
@@ -70,6 +104,10 @@ class ImportDeclaration extends Stmt, Import, @import_declaration {
is instanceof ImportNamespaceSpecifier and
count(getASpecifier()) = 1
or
// For compatibility with the non-standard implementation of default imports,
// treat default imports as namespace imports in cases where it can't cause ambiguity
// between named exports and the properties of a default-exported object.
not hasBothNamedAndDefaultExports(getImportedModule()) and
is.getImportedName() = "default"
)
or

View File

@@ -13,9 +13,11 @@ class JsonStringifyCall extends DataFlow::CallNode {
callee = DataFlow::globalVarRef("JSON").getAPropertyRead("stringify") or
callee = DataFlow::moduleMember("json3", "stringify") or
callee =
DataFlow::moduleImport(["json-stringify-safe", "json-stable-stringify", "stringify-object",
"fast-json-stable-stringify", "fast-safe-stringify", "javascript-stringify",
"js-stringify"]) or
DataFlow::moduleImport([
"json-stringify-safe", "json-stable-stringify", "stringify-object",
"fast-json-stable-stringify", "fast-safe-stringify", "javascript-stringify",
"js-stringify"
]) or
// require("util").inspect() and similar
callee = DataFlow::moduleMember("util", "inspect") or
callee = DataFlow::moduleImport(["pretty-format", "object-inspect"])

View File

@@ -205,15 +205,18 @@ private predicate isRequire(DataFlow::Node nd) {
or
isRequire(nd.getAPredecessor())
or
// `import { createRequire } from 'module';` support.
// specialized to ES2015 modules to avoid recursion in the `DataFlow::moduleImport()` predicate.
exists(ImportDeclaration imp | imp.getImportedPath().getValue() = "module" |
nd =
imp
.getImportedModuleNode()
.(DataFlow::SourceNode)
.getAPropertyRead("createRequire")
.getACall()
// `import { createRequire } from 'module';`.
// specialized to ES2015 modules to avoid recursion in the `DataFlow::moduleImport()` predicate and to avoid
// negative recursion between `Import.getImportedModuleNode()` and `Import.getImportedModule()`.
exists(ImportDeclaration imp, DataFlow::SourceNode baseObj |
imp.getImportedPath().getValue() = "module"
|
baseObj =
[
DataFlow::destructuredModuleImportNode(imp),
DataFlow::valueNode(imp.getASpecifier().(ImportNamespaceSpecifier))
] and
nd = baseObj.getAPropertyRead("createRequire").getACall()
)
}

View File

@@ -299,8 +299,10 @@ module PromiseFlow {
or
prop = errorProp() and
value =
[promise.getRejectParameter().getACall().getArgument(0),
promise.getExecutor().getExceptionalReturn()]
[
promise.getRejectParameter().getACall().getArgument(0),
promise.getExecutor().getExceptionalReturn()
]
)
or
// promise creation call, e.g. `Promise.resolve`.

View File

@@ -699,8 +699,10 @@ module TaintTracking {
private DataFlow::PropRead getAStaticCaptureRef() {
result =
DataFlow::globalVarRef("RegExp")
.getAPropertyRead(["$" + [1 .. 9], "input", "lastMatch", "leftContext", "rightContext",
"$&", "$^", "$`"])
.getAPropertyRead([
"$" + [1 .. 9], "input", "lastMatch", "leftContext", "rightContext", "$&", "$^",
"$`"
])
}
/**

View File

@@ -318,7 +318,12 @@ private class AnalyzedVariableExport extends AnalyzedPropertyWrite, DataFlow::Va
override predicate writes(AbstractValue baseVal, string propName, DataFlow::AnalyzedNode source) {
baseVal = TAbstractExportsObject(export.getEnclosingModule()) and
propName = name and
source = varDef.getSource().analyze()
(
source = varDef.getSource().analyze()
or
varDef.getTarget() instanceof DestructuringPattern and
source = export.getSourceNode(propName)
)
}
override predicate writesValue(AbstractValue baseVal, string propName, AbstractValue val) {

View File

@@ -0,0 +1,93 @@
/** Provides taint steps modeling flow through date-manipulation libraries. */
private import javascript
private module DateFns {
private API::Node formatFunction() {
result = API::moduleImport(["date-fns", "date-fns/esm"]).getMember(["format", "lightFormat"])
or
result =
API::moduleImport([
"date-fns/format", "date-fns/lightFormat", "date-fns/esm/format",
"date-fns/esm/lightFormat"
])
}
private API::Node curriedFormatFunction() {
result =
API::moduleImport(["date-fns/fp", "date-fns/esm/fp"]).getMember(["format", "lightFormat"])
or
result =
API::moduleImport([
"date-fns/fp/format", "date-fns/fp/lightFormat", "date-fns/esm/fp/format",
"date-fns/esm/fp/lightFormat"
])
}
/**
* Taint step of form: `f -> format(date, f)`
*
* A format string can use single-quotes to include mostly arbitrary text.
*/
private class FormatStep extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
FormatStep() { this = formatFunction().getACall() }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = getArgument(1) and
succ = this
}
}
/**
* Taint step of form: `f -> format(f)(date)`
*/
private class CurriedFormatStep extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
CurriedFormatStep() { this = curriedFormatFunction().getACall() }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = getArgument(0) and
succ = getACall()
}
}
}
private module Moment {
/** Gets a reference to a `moment` object. */
private API::Node moment() {
result = API::moduleImport(["moment", "moment-timezone"])
or
result = moment().getReturn()
or
result = moment().getAMember()
}
/**
* Taint step of form: `f -> momentObj.format(f)`
*
* The format string can use backslash-escaping to include mostly arbitrary text.
*/
private class MomentFormatStep extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
MomentFormatStep() { this = moment().getMember("format").getACall() }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = getArgument(0) and
succ = this
}
}
}
private module DateFormat {
/**
* Taint step of form: `x -> dateformat(..., x)`
*
* The format string can use single-quotes to include mostly arbitrary text.
*/
private class DateFormatStep extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
DateFormatStep() { this = DataFlow::moduleImport("dateformat").getACall() }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = getArgument(1) and
succ = this
}
}
}

View File

@@ -491,8 +491,10 @@ module Express {
RequestInputAccess() {
kind = "parameter" and
this =
[getAQueryObjectReference(DataFlow::TypeTracker::end(), rh),
getAParamsObjectReference(DataFlow::TypeTracker::end(), rh)].getAPropertyRead()
[
getAQueryObjectReference(DataFlow::TypeTracker::end(), rh),
getAParamsObjectReference(DataFlow::TypeTracker::end(), rh)
].getAPropertyRead()
or
exists(DataFlow::SourceNode request | request = rh.getARequestSource().ref() |
kind = "parameter" and

View File

@@ -118,8 +118,10 @@ module Fastify {
.flow()
.(DataFlow::MethodCallNode)
.getOptionArgument(0,
["onRequest", "preParsing", "preValidation", "preHandler", "preSerialization",
"onSend", "onResponse", "handler"])
[
"onRequest", "preParsing", "preValidation", "preHandler", "preSerialization",
"onSend", "onResponse", "handler"
])
else result = getLastArgument().flow()
}
}

View File

@@ -0,0 +1,50 @@
/**
* Provides classes for working with JWT libraries.
*/
import javascript
/**
* Provides classes and predicates modeling the `jwt-decode` libary.
*/
private module JwtDecode {
/**
* A taint-step for `succ = require("jwt-decode")(pred)`.
*/
private class JwtDecodeStep extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
JwtDecodeStep() { this = DataFlow::moduleImport("jwt-decode").getACall() }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = this.getArgument(0) and
succ = this
}
}
}
/**
* Provides classes and predicates modeling the `jsonwebtoken` libary.
*/
private module JsonWebToken {
/**
* A taint-step for `require("jsonwebtoken").verify(pred, "key", (err succ) => {...})`.
*/
private class VerifyStep extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
VerifyStep() { this = DataFlow::moduleMember("jsonwebtoken", "verify").getACall() }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = this.getArgument(0) and
succ = this.getABoundCallbackParameter(2, 1)
}
}
/**
* The private key for a JWT as a `CredentialsExpr`.
*/
private class JWTKey extends CredentialsExpr {
JWTKey() {
this = DataFlow::moduleMember("jsonwebtoken", "sign").getACall().getArgument(1).asExpr()
}
override string getCredentialsKind() { result = "key" }
}
}

View File

@@ -403,16 +403,18 @@ module LodashUnderscore {
call = any(Member member | member.getName() = name).getACall()
|
name =
["find", "filter", "findWhere", "where", "reject", "pluck", "max", "min", "sortBy",
"shuffle", "sample", "toArray", "partition", "compact", "first", "initial", "last",
"rest", "flatten", "without", "difference", "uniq", "unique", "unzip", "transpose",
"object", "chunk", "values", "mapObject", "pick", "omit", "defaults", "clone", "tap",
"identity",
// String category
"camelCase", "capitalize", "deburr", "kebabCase", "lowerCase", "lowerFirst", "pad",
"padEnd", "padStart", "repeat", "replace", "snakeCase", "split", "startCase", "toLower",
"toUpper", "trim", "trimEnd", "trimStart", "truncate", "unescape", "upperCase",
"upperFirst", "words"] and
[
"find", "filter", "findWhere", "where", "reject", "pluck", "max", "min", "sortBy",
"shuffle", "sample", "toArray", "partition", "compact", "first", "initial", "last",
"rest", "flatten", "without", "difference", "uniq", "unique", "unzip", "transpose",
"object", "chunk", "values", "mapObject", "pick", "omit", "defaults", "clone", "tap",
"identity",
// String category
"camelCase", "capitalize", "deburr", "kebabCase", "lowerCase", "lowerFirst", "pad",
"padEnd", "padStart", "repeat", "replace", "snakeCase", "split", "startCase", "toLower",
"toUpper", "trim", "trimEnd", "trimStart", "truncate", "unescape", "upperCase",
"upperFirst", "words"
] and
pred = call.getArgument(0) and
succ = call
or

View File

@@ -798,10 +798,12 @@ private module Redis {
bindingset[argIndex]
predicate argumentIsAmbiguousKey(string method, int argIndex) {
method =
["set", "publish", "append", "bitfield", "decrby", "getset", "hincrby", "hincrbyfloat",
"hset", "hsetnx", "incrby", "incrbyfloat", "linsert", "lpush", "lpushx", "lset",
"ltrim", "rename", "renamenx", "rpushx", "setbit", "setex", "smove", "zincrby",
"zinterstore", "hdel", "lpush", "pfadd", "rpush", "sadd", "sdiffstore", "srem"] and
[
"set", "publish", "append", "bitfield", "decrby", "getset", "hincrby", "hincrbyfloat",
"hset", "hsetnx", "incrby", "incrbyfloat", "linsert", "lpush", "lpushx", "lset", "ltrim",
"rename", "renamenx", "rpushx", "setbit", "setex", "smove", "zincrby", "zinterstore",
"hdel", "lpush", "pfadd", "rpush", "sadd", "sdiffstore", "srem"
] and
argIndex = 0
or
method = ["bitop", "hmset", "mset", "msetnx", "geoadd"] and argIndex >= 0

View File

@@ -472,8 +472,10 @@ module NodeJSLib {
result = promisifyAllCall and
pred.flowsTo(promisifyAllCall.getArgument(0)) and
promisifyAllCall =
[DataFlow::moduleMember("bluebird", "promisifyAll"),
DataFlow::moduleImport("util-promisifyall")].getACall()
[
DataFlow::moduleMember("bluebird", "promisifyAll"),
DataFlow::moduleImport("util-promisifyall")
].getACall()
)
)
}
@@ -771,8 +773,10 @@ module NodeJSLib {
* Gets the code to be executed as part of this invocation.
*/
DataFlow::Node getACodeArgument() {
memberName in ["Script", "SourceTextModule", "compileFunction", "runInContext",
"runInNewContext", "runInThisContext"] and
memberName in [
"Script", "SourceTextModule", "compileFunction", "runInContext", "runInNewContext",
"runInThisContext"
] and
// all of the above methods/constructors take the command as their first argument
result = getArgument(0)
}

View File

@@ -569,10 +569,12 @@ private class UseStateStep extends PreCallGraphStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::CallNode call | call = react().getAMemberCall("useState") |
pred =
[call.getArgument(0), // initial state
call.getCallback(0).getReturnNode(), // lazy initial state
call.getAPropertyRead("1").getACall().getArgument(0), // setState invocation
call.getAPropertyRead("1").getACall().getCallback(0).getReturnNode()] and // setState with callback
[
call.getArgument(0), // initial state
call.getCallback(0).getReturnNode(), // lazy initial state
call.getAPropertyRead("1").getACall().getArgument(0), // setState invocation
call.getAPropertyRead("1").getACall().getCallback(0).getReturnNode() // setState with callback
] and
succ = call.getAPropertyRead("0")
or
// Propagate current state into the callback argument of `setState(prevState => { ... })`

View File

@@ -33,8 +33,10 @@ module Vue {
/** Gets the name of a lifecycle hook method. */
private string lifecycleHookName() {
result =
["beforeCreate", "created", "beforeMount", "mounted", "beforeUpdate", "updated", "activated",
"deactivated", "beforeDestroy", "destroyed", "errorCaptured"]
[
"beforeCreate", "created", "beforeMount", "mounted", "beforeUpdate", "updated", "activated",
"deactivated", "beforeDestroy", "destroyed", "errorCaptured"
]
}
/** Gets a value that can be used as a `@Component` decorator. */

View File

@@ -53,8 +53,10 @@ module ImproperCodeSanitization {
|
functionLeaf
.getStringValue()
.regexpMatch([".*function( )?([a-zA-Z0-9]+)?( )?\\(.*", ".*eval\\(.*",
".*new Function\\(.*", "(^|.*[^a-zA-Z0-9])\\(.*\\)( )?=>.*"])
.regexpMatch([
".*function( )?([a-zA-Z0-9]+)?( )?\\(.*", ".*eval\\(.*", ".*new Function\\(.*",
"(^|.*[^a-zA-Z0-9])\\(.*\\)( )?=>.*"
])
)
)
}

View File

@@ -66,9 +66,11 @@ module IndirectCommandInjection {
exists(string method |
not method =
// the methods that does not return a chained `yargs` object.
["getContext", "getDemandedOptions", "getDemandedCommands", "getDeprecatedOptions",
"_getParseContext", "getOptions", "getGroups", "getStrict", "getStrictCommands",
"getExitProcess", "locale", "getUsageInstance", "getCommandInstance"]
[
"getContext", "getDemandedOptions", "getDemandedCommands", "getDeprecatedOptions",
"_getParseContext", "getOptions", "getGroups", "getStrict", "getStrictCommands",
"getExitProcess", "locale", "getUsageInstance", "getCommandInstance"
]
|
result = yargs().getAMethodCall(method)
)

View File

@@ -96,8 +96,10 @@ module InsecureDownload {
*/
string unsafeExtension() {
result =
["exe", "dmg", "pkg", "tar.gz", "zip", "sh", "bat", "cmd", "app", "apk", "msi", "dmg",
"tar.gz", "zip", "js", "py", "jar", "war"]
[
"exe", "dmg", "pkg", "tar.gz", "zip", "sh", "bat", "cmd", "app", "apk", "msi", "dmg",
"tar.gz", "zip", "js", "py", "jar", "war"
]
}
/**

View File

@@ -105,8 +105,10 @@ module UnsafeShellCommandConstruction {
ArrayAppendEndingInCommandExecutinSink() {
this =
[array.(DataFlow::ArrayCreationNode).getAnElement(),
array.getAMethodCall(["push", "unshift"]).getAnArgument()] and
[
array.(DataFlow::ArrayCreationNode).getAnElement(),
array.getAMethodCall(["push", "unshift"]).getAnArgument()
] and
exists(DataFlow::MethodCallNode joinCall | array.getAMethodCall("join") = joinCall |
joinCall = isExecutedAsShellCommand(DataFlow::TypeBackTracker::end(), sys) and
joinCall.getNumArgument() = 1 and