mirror of
https://github.com/github/codeql.git
synced 2026-04-24 08:15:14 +02:00
Merge branch 'main' into js/shared-dataflow-merge-main
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
## 2.1.0
|
||||
|
||||
### New Features
|
||||
|
||||
* Added support for custom threat-models, which can be used in most of our taint-tracking queries, see our [documentation](https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning#extending-codeql-coverage-with-threat-models) for more details.
|
||||
|
||||
## 2.0.2
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
Added support for `String.prototype.matchAll`.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added taint-steps for `Array.prototype.reverse`
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added taint-steps for `Array.prototype.toReversed`.
|
||||
* Added taint-steps for `Array.prototype.toSorted`.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
Added taint-steps for `Array.prototype.toSpliced`
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
Added taint-steps for `Array.prototype.with`.
|
||||
5
javascript/ql/lib/change-notes/released/2.1.0.md
Normal file
5
javascript/ql/lib/change-notes/released/2.1.0.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## 2.1.0
|
||||
|
||||
### New Features
|
||||
|
||||
* Added support for custom threat-models, which can be used in most of our taint-tracking queries, see our [documentation](https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning#extending-codeql-coverage-with-threat-models) for more details.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 2.0.2
|
||||
lastReleaseVersion: 2.1.0
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
extensions:
|
||||
- addsTo:
|
||||
pack: codeql/threat-models
|
||||
extensible: threatModelConfiguration
|
||||
data:
|
||||
# Since responses are enabled by default in the shared threat-models configuration,
|
||||
# we need to disable it here to keep existing behavior for the javascript analysis.
|
||||
- ["response", false, -2147483647]
|
||||
@@ -81,6 +81,7 @@ import semmle.javascript.frameworks.Classnames
|
||||
import semmle.javascript.frameworks.ClassValidator
|
||||
import semmle.javascript.frameworks.ClientRequests
|
||||
import semmle.javascript.frameworks.ClosureLibrary
|
||||
import semmle.javascript.frameworks.CommandLineArguments
|
||||
import semmle.javascript.frameworks.CookieLibraries
|
||||
import semmle.javascript.frameworks.Credentials
|
||||
import semmle.javascript.frameworks.CryptoLibraries
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/javascript-all
|
||||
version: 2.0.3-dev
|
||||
version: 2.1.1-dev
|
||||
groups: javascript
|
||||
dbscheme: semmlecode.javascript.dbscheme
|
||||
extractor: javascript
|
||||
@@ -10,6 +10,7 @@ dependencies:
|
||||
codeql/mad: ${workspace}
|
||||
codeql/regex: ${workspace}
|
||||
codeql/ssa: ${workspace}
|
||||
codeql/threat-models: ${workspace}
|
||||
codeql/tutorial: ${workspace}
|
||||
codeql/util: ${workspace}
|
||||
codeql/xml: ${workspace}
|
||||
@@ -18,4 +19,5 @@ dataExtensions:
|
||||
- semmle/javascript/frameworks/**/model.yml
|
||||
- semmle/javascript/frameworks/**/*.model.yml
|
||||
- semmle/javascript/security/domains/**/*.model.yml
|
||||
- ext/*.model.yml
|
||||
warnOnImplicitThis: true
|
||||
|
||||
@@ -81,12 +81,23 @@ module ArrayTaintTracking {
|
||||
pred = call.getArgument(any(int i | i >= 2)) and
|
||||
succ.(DataFlow::SourceNode).getAMethodCall("splice") = call
|
||||
or
|
||||
// `array.toSpliced(x, y, source())`: if `source()` is tainted, then so is the result of `toSpliced`, but not the original array.
|
||||
call.(DataFlow::MethodCallNode).getMethodName() = "toSpliced" and
|
||||
pred = call.getArgument(any(int i | i >= 2)) and
|
||||
succ = call
|
||||
or
|
||||
// `array.splice(i, del, ...e)`: if `e` is tainted, then so is `array`.
|
||||
pred = call.getASpreadArgument() and
|
||||
succ.(DataFlow::SourceNode).getAMethodCall("splice") = call
|
||||
or
|
||||
// `array.toSpliced(i, del, ...e)`: if `e` is tainted, then so is the result of `toSpliced`, but not the original array.
|
||||
pred = call.getASpreadArgument() and
|
||||
call.(DataFlow::MethodCallNode).getMethodName() = "toSpliced" and
|
||||
succ = call
|
||||
or
|
||||
// `e = array.pop()`, `e = array.shift()`, or similar: if `array` is tainted, then so is `e`.
|
||||
call.(DataFlow::MethodCallNode).calls(pred, ["pop", "shift", "slice", "splice", "at"]) and
|
||||
call.(DataFlow::MethodCallNode)
|
||||
.calls(pred, ["pop", "shift", "slice", "splice", "at", "toSpliced"]) and
|
||||
succ = call
|
||||
or
|
||||
// `e = Array.from(x)`: if `x` is tainted, then so is `e`.
|
||||
@@ -283,7 +294,7 @@ private module ArrayDataFlow {
|
||||
private class ArraySpliceStep extends LegacyPreCallGraphStep {
|
||||
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = "splice" and
|
||||
call.getMethodName() = ["splice", "toSpliced"] and
|
||||
prop = arrayElement() and
|
||||
element = call.getArgument(any(int i | i >= 2)) and
|
||||
call = obj.getAMethodCall()
|
||||
@@ -297,7 +308,7 @@ private module ArrayDataFlow {
|
||||
toProp = arrayElement() and
|
||||
// `array.splice(i, del, ...arr)` variant
|
||||
exists(DataFlow::MethodCallNode mcn |
|
||||
mcn.getMethodName() = "splice" and
|
||||
mcn.getMethodName() = ["splice", "toSpliced"] and
|
||||
pred = mcn.getASpreadArgument() and
|
||||
succ = mcn.getReceiver().getALocalSource()
|
||||
)
|
||||
@@ -320,12 +331,12 @@ private module ArrayDataFlow {
|
||||
}
|
||||
|
||||
/**
|
||||
* A step for modeling that elements from an array `arr` also appear in the result from calling `slice`/`splice`/`filter`.
|
||||
* A step for modeling that elements from an array `arr` also appear in the result from calling `slice`/`splice`/`filter`/`toSpliced`.
|
||||
*/
|
||||
private class ArraySliceStep extends LegacyPreCallGraphStep {
|
||||
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = ["slice", "splice", "filter"] and
|
||||
call.getMethodName() = ["slice", "splice", "filter", "toSpliced"] and
|
||||
prop = arrayElement() and
|
||||
pred = call.getReceiver() and
|
||||
succ = call
|
||||
@@ -444,4 +455,32 @@ private module ArrayLibraries {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint propagating data flow edge arising from in-place array manipulation operations.
|
||||
* The methods return the pointer to `this` array as well.
|
||||
*/
|
||||
private class ArrayInPlaceManipulationTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() in ["sort", "reverse"] and
|
||||
pred = call.getReceiver() and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint propagating data flow edge arising from array transformation operations
|
||||
* that return a new array instead of modifying the original array in place.
|
||||
*/
|
||||
private class ImmutableArrayTransformStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() in ["toSorted", "toReversed", "with"] and
|
||||
pred = call.getReceiver() and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,63 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
private import codeql.threatmodels.ThreatModels
|
||||
|
||||
/**
|
||||
* A data flow source, for a specific threat-model.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `ThreatModelSource::Range` instead.
|
||||
*/
|
||||
class ThreatModelSource extends DataFlow::Node instanceof ThreatModelSource::Range {
|
||||
/**
|
||||
* Gets a string that represents the source kind with respect to threat modeling.
|
||||
*
|
||||
*
|
||||
* See
|
||||
* - https://github.com/github/codeql/blob/main/docs/codeql/reusables/threat-model-description.rst
|
||||
* - https://github.com/github/codeql/blob/main/shared/threat-models/ext/threat-model-grouping.model.yml
|
||||
*/
|
||||
string getThreatModel() { result = super.getThreatModel() }
|
||||
|
||||
/** Gets a string that describes the type of this threat-model source. */
|
||||
string getSourceType() { result = super.getSourceType() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new sources for specific threat-models. */
|
||||
module ThreatModelSource {
|
||||
/**
|
||||
* A data flow source, for a specific threat-model.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `ThreatModelSource` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/**
|
||||
* Gets a string that represents the source kind with respect to threat modeling.
|
||||
*
|
||||
* See
|
||||
* - https://github.com/github/codeql/blob/main/docs/codeql/reusables/threat-model-description.rst
|
||||
* - https://github.com/github/codeql/blob/main/shared/threat-models/ext/threat-model-grouping.model.yml
|
||||
*/
|
||||
abstract string getThreatModel();
|
||||
|
||||
/** Gets a string that describes the type of this threat-model source. */
|
||||
abstract string getSourceType();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow source that is enabled in the current threat model configuration.
|
||||
*/
|
||||
class ActiveThreatModelSource extends ThreatModelSource {
|
||||
ActiveThreatModelSource() {
|
||||
exists(string kind |
|
||||
currentThreatModel(kind) and
|
||||
this.getThreatModel() = kind
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node that executes an operating system command,
|
||||
@@ -65,6 +122,19 @@ abstract class FileSystemReadAccess extends FileSystemAccess {
|
||||
abstract DataFlow::Node getADataNode();
|
||||
}
|
||||
|
||||
/**
|
||||
* A FileSystemReadAccess seen as a ThreatModelSource.
|
||||
*/
|
||||
private class FileSystemReadAccessAsThreatModelSource extends ThreatModelSource::Range {
|
||||
FileSystemReadAccessAsThreatModelSource() {
|
||||
this = any(FileSystemReadAccess access).getADataNode()
|
||||
}
|
||||
|
||||
override string getThreatModel() { result = "file" }
|
||||
|
||||
override string getSourceType() { result = "FileSystemReadAccess" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node that writes data to the file system.
|
||||
*/
|
||||
@@ -91,6 +161,17 @@ abstract class DatabaseAccess extends DataFlow::Node {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A DatabaseAccess seen as a ThreatModelSource.
|
||||
*/
|
||||
private class DatabaseAccessAsThreatModelSource extends ThreatModelSource::Range {
|
||||
DatabaseAccessAsThreatModelSource() { this = any(DatabaseAccess access).getAResult() }
|
||||
|
||||
override string getThreatModel() { result = "database" }
|
||||
|
||||
override string getSourceType() { result = "DatabaseAccess" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node that reads persistent data.
|
||||
*/
|
||||
|
||||
@@ -193,7 +193,7 @@ module MembershipCandidate {
|
||||
or
|
||||
// u.match(/re/) or u.match("re")
|
||||
base = this and
|
||||
m = "match" and
|
||||
m = ["match", "matchAll"] and
|
||||
enumeration = RegExp::getRegExpFromNode(firstArg)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -938,7 +938,7 @@ private predicate isMatchObjectProperty(string name) {
|
||||
|
||||
/** Holds if `call` is a call to `match` whose result is used in a way that is incompatible with Match objects. */
|
||||
private predicate isUsedAsNonMatchObject(DataFlow::MethodCallNode call) {
|
||||
call.getMethodName() = "match" and
|
||||
call.getMethodName() = ["match", "matchAll"] and
|
||||
call.getNumArgument() = 1 and
|
||||
(
|
||||
// Accessing a property that is absent on Match objects
|
||||
@@ -972,7 +972,7 @@ private predicate isUsedAsNumber(DataFlow::LocalSourceNode value) {
|
||||
or
|
||||
exists(DataFlow::CallNode call |
|
||||
call.getCalleeName() =
|
||||
["substring", "substr", "slice", "splice", "charAt", "charCodeAt", "codePointAt"] and
|
||||
["substring", "substr", "slice", "splice", "charAt", "charCodeAt", "codePointAt", "toSpliced"] and
|
||||
value.flowsTo(call.getAnArgument())
|
||||
)
|
||||
}
|
||||
@@ -996,7 +996,7 @@ predicate isInterpretedAsRegExp(DataFlow::Node source) {
|
||||
not isNativeStringMethod(func, methodName)
|
||||
)
|
||||
|
|
||||
methodName = "match" and
|
||||
methodName = ["match", "matchAll"] and
|
||||
source = mce.getArgument(0) and
|
||||
mce.getNumArgument() = 1 and
|
||||
not isUsedAsNonMatchObject(mce)
|
||||
|
||||
@@ -722,7 +722,7 @@ module StringOps {
|
||||
}
|
||||
|
||||
private class MatchCall extends DataFlow::MethodCallNode {
|
||||
MatchCall() { this.getMethodName() = "match" }
|
||||
MatchCall() { this.getMethodName() = ["match", "matchAll"] }
|
||||
}
|
||||
|
||||
private class ExecCall extends DataFlow::MethodCallNode {
|
||||
|
||||
@@ -522,7 +522,7 @@ module TaintTracking {
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::MethodCallNode matchMethodCall() {
|
||||
result.getMethodName() = "match" and
|
||||
result.getMethodName() = ["match", "matchAll"] and
|
||||
exists(DataFlow::AnalyzedNode analyzed |
|
||||
pragma[only_bind_into](analyzed) = result.getArgument(0).analyze() and
|
||||
analyzed.getAType() = TTRegExp()
|
||||
@@ -675,19 +675,6 @@ module TaintTracking {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint propagating data flow edge arising from sorting.
|
||||
*/
|
||||
private class SortTaintStep extends SharedTaintStep {
|
||||
override predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = "sort" and
|
||||
pred = call.getReceiver() and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint step through an exception constructor, such as `x` to `new Error(x)`.
|
||||
*/
|
||||
@@ -723,7 +710,7 @@ module TaintTracking {
|
||||
*/
|
||||
private ControlFlowNode getACaptureSetter(DataFlow::Node input) {
|
||||
exists(DataFlow::MethodCallNode call | result = call.asExpr() |
|
||||
call.getMethodName() = ["search", "replace", "replaceAll", "match"] and
|
||||
call.getMethodName() = ["search", "replace", "replaceAll", "match", "matchAll"] and
|
||||
input = call.getReceiver()
|
||||
or
|
||||
call.getMethodName() = ["test", "exec"] and input = call.getArgument(0)
|
||||
@@ -804,7 +791,7 @@ module TaintTracking {
|
||||
or
|
||||
// u.match(/re/) or u.match("re")
|
||||
base = expr and
|
||||
m = "match" and
|
||||
m = ["match", "matchAll"] and
|
||||
RegExp::isGenericRegExpSanitizer(RegExp::getRegExpFromNode(firstArg.flow()),
|
||||
sanitizedOutcome)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/** Provides modeling for parsed command line arguments. */
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* An object containing command-line arguments, potentially parsed by a library.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `CommandLineArguments::Range` instead.
|
||||
*/
|
||||
class CommandLineArguments extends ThreatModelSource instanceof CommandLineArguments::Range { }
|
||||
|
||||
/** Provides a class for modeling new sources of remote user input. */
|
||||
module CommandLineArguments {
|
||||
/**
|
||||
* An object containing command-line arguments, potentially parsed by a library.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `CommandLineArguments` instead.
|
||||
*/
|
||||
abstract class Range extends ThreatModelSource::Range {
|
||||
override string getThreatModel() { result = "commandargs" }
|
||||
|
||||
override string getSourceType() { result = "CommandLineArguments" }
|
||||
}
|
||||
}
|
||||
|
||||
/** A read of `process.argv`, considered as a threat-model source. */
|
||||
private class ProcessArgv extends CommandLineArguments::Range {
|
||||
ProcessArgv() {
|
||||
// `process.argv[0]` and `process.argv[1]` are paths to `node` and `main`, and
|
||||
// therefore should not be considered a threat-source... However, we don't have an
|
||||
// easy way to exclude them, so we need to allow them.
|
||||
this = NodeJSLib::process().getAPropertyRead("argv")
|
||||
}
|
||||
|
||||
override string getSourceType() { result = "process.argv" }
|
||||
}
|
||||
|
||||
private class DefaultModels extends CommandLineArguments::Range {
|
||||
DefaultModels() {
|
||||
// `require('get-them-args')(...)` => `{ unknown: [], a: ... b: ... }`
|
||||
this = DataFlow::moduleImport("get-them-args").getACall()
|
||||
or
|
||||
// `require('optimist').argv` => `{ _: [], a: ... b: ... }`
|
||||
this = DataFlow::moduleMember("optimist", "argv")
|
||||
or
|
||||
// `require("arg")({...spec})` => `{_: [], a: ..., b: ...}`
|
||||
this = DataFlow::moduleImport("arg").getACall()
|
||||
or
|
||||
// `(new (require(argparse)).ArgumentParser({...spec})).parse_args()` => `{a: ..., b: ...}`
|
||||
this =
|
||||
API::moduleImport("argparse")
|
||||
.getMember("ArgumentParser")
|
||||
.getInstance()
|
||||
.getMember("parse_args")
|
||||
.getACall()
|
||||
or
|
||||
// `require('command-line-args')({...spec})` => `{a: ..., b: ...}`
|
||||
this = DataFlow::moduleImport("command-line-args").getACall()
|
||||
or
|
||||
// `require('meow')(help, {...spec})` => `{a: ..., b: ....}`
|
||||
this = DataFlow::moduleImport("meow").getACall()
|
||||
or
|
||||
// `require("dashdash").createParser(...spec)` => `{a: ..., b: ...}`
|
||||
this =
|
||||
[
|
||||
API::moduleImport("dashdash"),
|
||||
API::moduleImport("dashdash").getMember("createParser").getReturn()
|
||||
].getMember("parse").getACall()
|
||||
or
|
||||
// `require('commander').myCmdArgumentName`
|
||||
this = commander().getAMember().asSource()
|
||||
or
|
||||
// `require('commander').opt()` => `{a: ..., b: ...}`
|
||||
this = commander().getMember("opts").getACall()
|
||||
or
|
||||
this = API::moduleImport("yargs/yargs").getReturn().getMember("argv").asSource()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A step for propagating taint through command line parsing,
|
||||
* such as `var succ = require("minimist")(pred)`.
|
||||
*/
|
||||
private class ArgsParseStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::CallNode call |
|
||||
call = DataFlow::moduleMember("args", "parse").getACall() or
|
||||
call = DataFlow::moduleImport(["yargs-parser", "minimist", "subarg"]).getACall()
|
||||
|
|
||||
succ = call and
|
||||
pred = call.getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Command instance from the `commander` library.
|
||||
*/
|
||||
private API::Node commander() {
|
||||
result = API::moduleImport("commander")
|
||||
or
|
||||
// `require("commander").program === require("commander")`
|
||||
result = commander().getMember("program")
|
||||
or
|
||||
result = commander().getMember("Command").getInstance()
|
||||
or
|
||||
// lots of chainable methods
|
||||
result = commander().getAMember().getReturn()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an instance of `yargs`.
|
||||
* Either directly imported as a module, or through some chained method call.
|
||||
*/
|
||||
private DataFlow::SourceNode yargs() {
|
||||
result = DataFlow::moduleImport("yargs")
|
||||
or
|
||||
// script used to generate list of chained methods: https://gist.github.com/erik-krogh/f8afe952c0577f4b563a993e613269ba
|
||||
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"
|
||||
]
|
||||
|
|
||||
result = yargs().getAMethodCall(method)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of command line arguments (`argv`) parsed by the `yargs` library.
|
||||
*/
|
||||
private class YargsArgv extends CommandLineArguments::Range {
|
||||
YargsArgv() {
|
||||
this = yargs().getAPropertyRead("argv")
|
||||
or
|
||||
this = yargs().getAMethodCall("parse") and
|
||||
this.(DataFlow::MethodCallNode).getNumArgument() = 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
extensions:
|
||||
- addsTo:
|
||||
pack: codeql/javascript-all
|
||||
extensible: sourceModel
|
||||
data:
|
||||
- ['fs', 'Member[promises].Member[readFile].ReturnValue.Member[then].Argument[0].Parameter[0]', 'file']
|
||||
- ['global', 'Member[process].Member[stdin].Member[read].ReturnValue', 'stdin']
|
||||
- ['global', 'Member[process].Member[stdin].Member[on,addListener].WithStringArgument[0=data].Argument[1].Parameter[0]', 'stdin']
|
||||
- ['readline', 'Member[createInterface].ReturnValue.Member[question].Argument[1].Parameter[0]', 'stdin']
|
||||
- ['readline', 'Member[createInterface].ReturnValue.Member[on,addListener].WithStringArgument[0=line].Argument[1].Parameter[0]', 'stdin']
|
||||
@@ -1244,4 +1244,13 @@ module NodeJSLib {
|
||||
result = moduleImport().getAPropertyRead(member)
|
||||
}
|
||||
}
|
||||
|
||||
/** A read of `process.env`, considered as a threat-model source. */
|
||||
private class ProcessEnvThreatSource extends ThreatModelSource::Range {
|
||||
ProcessEnvThreatSource() { this = NodeJSLib::process().getAPropertyRead("env") }
|
||||
|
||||
override string getThreatModel() { result = "environment" }
|
||||
|
||||
override string getSourceType() { result = "process.env" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,19 @@ private class RemoteFlowSourceFromMaD extends RemoteFlowSource {
|
||||
override string getSourceType() { result = "Remote flow" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A threat-model flow source originating from a data extension.
|
||||
*/
|
||||
private class ThreatModelSourceFromDataExtension extends ThreatModelSource::Range {
|
||||
ThreatModelSourceFromDataExtension() { this = ModelOutput::getASourceNode(_).asSource() }
|
||||
|
||||
override string getThreatModel() { this = ModelOutput::getASourceNode(result).asSource() }
|
||||
|
||||
override string getSourceType() {
|
||||
result = "Source node (" + this.getThreatModel() + ") [from data-extension]"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `ModelOutput::summaryStep` but with API nodes mapped to data-flow nodes.
|
||||
*/
|
||||
|
||||
@@ -38,9 +38,16 @@ module ClientSideUrlRedirect {
|
||||
DocumentUrl() { this = "document.url" } // TODO: replace with TaintedUrlSuffix
|
||||
}
|
||||
|
||||
/** A source of remote user input, considered as a flow source for unvalidated URL redirects. */
|
||||
class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource {
|
||||
RemoteFlowSourceAsSource() { not this.(ClientSideRemoteFlowSource).getKind().isPath() }
|
||||
/**
|
||||
* DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead!
|
||||
*/
|
||||
deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource;
|
||||
|
||||
/**
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource {
|
||||
ActiveThreatModelSourceAsSource() { not this.(ClientSideRemoteFlowSource).getKind().isPath() }
|
||||
|
||||
override DataFlow::FlowLabel getAFlowLabel() {
|
||||
if this.(ClientSideRemoteFlowSource).getKind().isUrl()
|
||||
|
||||
@@ -27,8 +27,15 @@ module CodeInjection {
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/** A source of remote user input, considered as a flow source for code injection. */
|
||||
class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { }
|
||||
/**
|
||||
* DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead!
|
||||
*/
|
||||
deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource;
|
||||
|
||||
/**
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { }
|
||||
|
||||
/**
|
||||
* An expression which may be interpreted as an AngularJS expression.
|
||||
|
||||
@@ -25,9 +25,16 @@ module CommandInjection {
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/** A source of remote user input, considered as a flow source for command injection. */
|
||||
class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource {
|
||||
RemoteFlowSourceAsSource() { not this instanceof ClientSideRemoteFlowSource }
|
||||
/**
|
||||
* DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead!
|
||||
*/
|
||||
deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource;
|
||||
|
||||
/**
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource {
|
||||
ActiveThreatModelSourceAsSource() { not this instanceof ClientSideRemoteFlowSource }
|
||||
|
||||
override string getSourceType() { result = "a user-provided value" }
|
||||
}
|
||||
|
||||
@@ -29,10 +29,14 @@ module ConditionalBypass {
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source for bypass of
|
||||
* sensitive action guards.
|
||||
* DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead!
|
||||
*/
|
||||
class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { }
|
||||
deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource;
|
||||
|
||||
/**
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { }
|
||||
|
||||
/**
|
||||
* Holds if `bb` dominates the basic block in which `action` occurs.
|
||||
|
||||
@@ -27,9 +27,16 @@ module CorsMisconfigurationForCredentials {
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/** A source of remote user input, considered as a flow source for CORS misconfiguration. */
|
||||
class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource {
|
||||
RemoteFlowSourceAsSource() { not this instanceof ClientSideRemoteFlowSource }
|
||||
/**
|
||||
* DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead!
|
||||
*/
|
||||
deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource;
|
||||
|
||||
/**
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource {
|
||||
ActiveThreatModelSourceAsSource() { not this instanceof ClientSideRemoteFlowSource }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,7 +23,8 @@ module DeepObjectResourceExhaustion {
|
||||
override DataFlow::FlowLabel getAFlowLabel() { result = TaintedObject::label() }
|
||||
}
|
||||
|
||||
private class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource {
|
||||
/** An active threat-model source, considered as a flow source. */
|
||||
private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource {
|
||||
override DataFlow::FlowLabel getAFlowLabel() { result.isTaint() }
|
||||
}
|
||||
|
||||
|
||||
@@ -355,8 +355,15 @@ module DomBasedXss {
|
||||
isOptionallySanitizedEdgeInternal(_, node)
|
||||
}
|
||||
|
||||
/** A source of remote user input, considered as a flow source for DOM-based XSS. */
|
||||
class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { }
|
||||
/**
|
||||
* DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead!
|
||||
*/
|
||||
deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource;
|
||||
|
||||
/**
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { }
|
||||
|
||||
/**
|
||||
* A flow-label representing tainted values where the prefix is attacker controlled.
|
||||
|
||||
@@ -25,21 +25,6 @@ module IndirectCommandInjection {
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A source of user input from the command-line, considered as a flow source for command injection.
|
||||
*/
|
||||
private class CommandLineArgumentsArrayAsSource extends Source instanceof CommandLineArgumentsArray
|
||||
{ }
|
||||
|
||||
/**
|
||||
* An array of command-line arguments.
|
||||
*/
|
||||
class CommandLineArgumentsArray extends DataFlow::SourceNode {
|
||||
CommandLineArgumentsArray() {
|
||||
this = DataFlow::globalVarRef("process").getAPropertyRead("argv")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A read of `process.env`, considered as a flow source for command injection.
|
||||
*/
|
||||
@@ -82,109 +67,9 @@ module IndirectCommandInjection {
|
||||
}
|
||||
|
||||
/**
|
||||
* An object containing parsed command-line arguments, considered as a flow source for command injection.
|
||||
* An object containing command-line arguments, considered as a flow source for command injection.
|
||||
*/
|
||||
class ParsedCommandLineArgumentsAsSource extends Source {
|
||||
ParsedCommandLineArgumentsAsSource() {
|
||||
// `require('get-them-args')(...)` => `{ unknown: [], a: ... b: ... }`
|
||||
this = DataFlow::moduleImport("get-them-args").getACall()
|
||||
or
|
||||
// `require('optimist').argv` => `{ _: [], a: ... b: ... }`
|
||||
this = DataFlow::moduleMember("optimist", "argv")
|
||||
or
|
||||
// `require("arg")({...spec})` => `{_: [], a: ..., b: ...}`
|
||||
this = DataFlow::moduleImport("arg").getACall()
|
||||
or
|
||||
// `(new (require(argparse)).ArgumentParser({...spec})).parse_args()` => `{a: ..., b: ...}`
|
||||
this =
|
||||
API::moduleImport("argparse")
|
||||
.getMember("ArgumentParser")
|
||||
.getInstance()
|
||||
.getMember("parse_args")
|
||||
.getACall()
|
||||
or
|
||||
// `require('command-line-args')({...spec})` => `{a: ..., b: ...}`
|
||||
this = DataFlow::moduleImport("command-line-args").getACall()
|
||||
or
|
||||
// `require('meow')(help, {...spec})` => `{a: ..., b: ....}`
|
||||
this = DataFlow::moduleImport("meow").getACall()
|
||||
or
|
||||
// `require("dashdash").createParser(...spec)` => `{a: ..., b: ...}`
|
||||
this =
|
||||
[
|
||||
API::moduleImport("dashdash"),
|
||||
API::moduleImport("dashdash").getMember("createParser").getReturn()
|
||||
].getMember("parse").getACall()
|
||||
or
|
||||
// `require('commander').myCmdArgumentName`
|
||||
this = commander().getAMember().asSource()
|
||||
or
|
||||
// `require('commander').opt()` => `{a: ..., b: ...}`
|
||||
this = commander().getMember("opts").getACall()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a command line parsing step from `pred` to `succ`.
|
||||
* E.g: `var succ = require("minimist")(pred)`.
|
||||
*/
|
||||
predicate argsParseStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::CallNode call |
|
||||
call = DataFlow::moduleMember("args", "parse").getACall() or
|
||||
call = DataFlow::moduleImport(["yargs-parser", "minimist", "subarg"]).getACall()
|
||||
|
|
||||
succ = call and
|
||||
pred = call.getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Command instance from the `commander` library.
|
||||
*/
|
||||
private API::Node commander() {
|
||||
result = API::moduleImport("commander")
|
||||
or
|
||||
// `require("commander").program === require("commander")`
|
||||
result = commander().getMember("program")
|
||||
or
|
||||
result = commander().getMember("Command").getInstance()
|
||||
or
|
||||
// lots of chainable methods
|
||||
result = commander().getAMember().getReturn()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an instance of `yargs`.
|
||||
* Either directly imported as a module, or through some chained method call.
|
||||
*/
|
||||
private DataFlow::SourceNode yargs() {
|
||||
result = DataFlow::moduleImport("yargs")
|
||||
or
|
||||
// script used to generate list of chained methods: https://gist.github.com/erik-krogh/f8afe952c0577f4b563a993e613269ba
|
||||
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"
|
||||
]
|
||||
|
|
||||
result = yargs().getAMethodCall(method)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of command line arguments (`argv`) parsed by the `yargs` library.
|
||||
*/
|
||||
class YargsArgv extends Source {
|
||||
YargsArgv() {
|
||||
this = yargs().getAPropertyRead("argv")
|
||||
or
|
||||
this = yargs().getAMethodCall("parse") and
|
||||
this.(DataFlow::MethodCallNode).getNumArgument() = 0
|
||||
}
|
||||
}
|
||||
private class CommandLineArgumentsAsSource extends Source instanceof CommandLineArguments { }
|
||||
|
||||
/**
|
||||
* A command-line argument that effectively is system-controlled, and therefore not likely to be exploitable when used in the execution of another command.
|
||||
@@ -193,7 +78,7 @@ module IndirectCommandInjection {
|
||||
SystemControlledCommandLineArgumentSanitizer() {
|
||||
// `process.argv[0]` and `process.argv[1]` are paths to `node` and `main`.
|
||||
exists(string index | index = "0" or index = "1" |
|
||||
this = any(CommandLineArgumentsArray a).getAPropertyRead(index)
|
||||
this = DataFlow::globalVarRef("process").getAPropertyRead("argv").getAPropertyRead(index)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,8 +58,4 @@ deprecated class Configuration extends TaintTracking::Configuration {
|
||||
override predicate isSink(DataFlow::Node sink) { this.isSinkWithHighlight(sink, _) }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
argsParseStep(pred, succ)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,15 @@ module NosqlInjection {
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/** A source of remote user input, considered as a flow source for NoSql injection. */
|
||||
class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { }
|
||||
/**
|
||||
* DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead!
|
||||
*/
|
||||
deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource;
|
||||
|
||||
/**
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { }
|
||||
|
||||
/** An expression interpreted as a NoSql query, viewed as a sink. */
|
||||
class NosqlQuerySink extends Sink instanceof NoSql::Query { }
|
||||
|
||||
@@ -26,11 +26,15 @@ module RegExpInjection {
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source for regular
|
||||
* expression injection.
|
||||
* DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead!
|
||||
*/
|
||||
class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource {
|
||||
RemoteFlowSourceAsSource() { not this instanceof ClientSideRemoteFlowSource }
|
||||
deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource;
|
||||
|
||||
/**
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource {
|
||||
ActiveThreatModelSourceAsSource() { not this instanceof ClientSideRemoteFlowSource }
|
||||
}
|
||||
|
||||
private import IndirectCommandInjectionCustomizations
|
||||
|
||||
@@ -11,10 +11,9 @@ cached
|
||||
private module Cached {
|
||||
/** A data flow source of remote user input. */
|
||||
cached
|
||||
abstract class RemoteFlowSource extends DataFlow::Node {
|
||||
/** Gets a human-readable string that describes the type of this remote flow source. */
|
||||
abstract class RemoteFlowSource extends ThreatModelSource::Range {
|
||||
cached
|
||||
abstract string getSourceType();
|
||||
override string getThreatModel() { result = "remote" }
|
||||
|
||||
/**
|
||||
* Holds if this can be a user-controlled object, such as a JSON object parsed from user-controlled data.
|
||||
|
||||
@@ -31,10 +31,14 @@ module RemotePropertyInjection {
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source for remote property
|
||||
* injection.
|
||||
* DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead!
|
||||
*/
|
||||
class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { }
|
||||
deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource;
|
||||
|
||||
/**
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { }
|
||||
|
||||
/**
|
||||
* A sink for property writes with dynamically computed property name.
|
||||
|
||||
@@ -39,9 +39,18 @@ module RequestForgery {
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/** A source of server-side remote user input, considered as a flow source for request forgery. */
|
||||
private class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource {
|
||||
RemoteFlowSourceAsSource() { not this.(ClientSideRemoteFlowSource).getKind().isPathOrUrl() }
|
||||
/**
|
||||
* DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead!
|
||||
*/
|
||||
deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource;
|
||||
|
||||
/**
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource {
|
||||
ActiveThreatModelSourceAsSource() {
|
||||
not this.(ClientSideRemoteFlowSource).getKind().isPathOrUrl()
|
||||
}
|
||||
|
||||
override predicate isServerSide() { not this instanceof ClientSideRemoteFlowSource }
|
||||
}
|
||||
|
||||
@@ -46,9 +46,16 @@ module ResourceExhaustion {
|
||||
override predicate sanitizes(boolean outcome, Expr e) { this.blocksExpr(outcome, e) }
|
||||
}
|
||||
|
||||
/** A source of remote user input, considered as a data flow source for resource exhaustion vulnerabilities. */
|
||||
class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource {
|
||||
RemoteFlowSourceAsSource() {
|
||||
/**
|
||||
* DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead!
|
||||
*/
|
||||
deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource;
|
||||
|
||||
/**
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource {
|
||||
ActiveThreatModelSourceAsSource() {
|
||||
// exclude source that only happen client-side
|
||||
not this instanceof ClientSideRemoteFlowSource and
|
||||
not this = DataFlow::parameterNode(any(PostMessageEventHandler pmeh).getEventParameter())
|
||||
|
||||
@@ -22,8 +22,15 @@ module SqlInjection {
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/** A source of remote user input, considered as a flow source for string based query injection. */
|
||||
class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { }
|
||||
/**
|
||||
* DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead!
|
||||
*/
|
||||
deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource;
|
||||
|
||||
/**
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { }
|
||||
|
||||
/** An SQL expression passed to an API call that executes SQL. */
|
||||
class SqlInjectionExprSink extends Sink instanceof SQL::SqlString { }
|
||||
|
||||
@@ -593,16 +593,15 @@ module TaintedPath {
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source for
|
||||
* tainted-path vulnerabilities.
|
||||
* DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead!
|
||||
*/
|
||||
class RemoteFlowSourceAsSource extends Source {
|
||||
RemoteFlowSourceAsSource() {
|
||||
exists(RemoteFlowSource src |
|
||||
this = src and
|
||||
not src instanceof ClientSideRemoteFlowSource
|
||||
)
|
||||
}
|
||||
deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource;
|
||||
|
||||
/**
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource {
|
||||
ActiveThreatModelSourceAsSource() { not this instanceof ClientSideRemoteFlowSource }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,7 +34,8 @@ module TemplateObjectInjection {
|
||||
override DataFlow::FlowLabel getAFlowLabel() { result = TaintedObject::label() }
|
||||
}
|
||||
|
||||
private class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource {
|
||||
/** An active threat-model source, considered as a flow source. */
|
||||
private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource {
|
||||
override DataFlow::FlowLabel getAFlowLabel() { result.isTaint() }
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,15 @@ module UnsafeDeserialization {
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/** A source of remote user input, considered as a flow source for unsafe deserialization. */
|
||||
class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { }
|
||||
/**
|
||||
* DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead!
|
||||
*/
|
||||
deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource;
|
||||
|
||||
/**
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { }
|
||||
|
||||
private API::Node unsafeYamlSchema() {
|
||||
result = API::moduleImport("js-yaml").getMember("DEFAULT_FULL_SCHEMA") // from older versions
|
||||
|
||||
@@ -52,9 +52,14 @@ module UnsafeDynamicMethodAccess {
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a source for unsafe dynamic method access.
|
||||
* DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead!
|
||||
*/
|
||||
class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { }
|
||||
deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource;
|
||||
|
||||
/**
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { }
|
||||
|
||||
/**
|
||||
* A function invocation of an unsafe function, as a sink for remote unsafe dynamic method access.
|
||||
|
||||
@@ -95,9 +95,14 @@ module UnvalidatedDynamicMethodCall {
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a source for unvalidated dynamic method calls.
|
||||
* DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead!
|
||||
*/
|
||||
class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { }
|
||||
deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource;
|
||||
|
||||
/**
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { }
|
||||
|
||||
/**
|
||||
* The page URL considered as a flow source for unvalidated dynamic method calls.
|
||||
|
||||
@@ -23,8 +23,15 @@ module XmlBomb {
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/** A source of remote user input, considered as a flow source for XML bomb vulnerabilities. */
|
||||
class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { }
|
||||
/**
|
||||
* DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead!
|
||||
*/
|
||||
deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource;
|
||||
|
||||
/**
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { }
|
||||
|
||||
/**
|
||||
* An access to `document.location`, considered as a flow source for XML bomb vulnerabilities.
|
||||
|
||||
@@ -23,8 +23,15 @@ module Xxe {
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/** A source of remote user input, considered as a flow source for XXE vulnerabilities. */
|
||||
class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { }
|
||||
/**
|
||||
* DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead!
|
||||
*/
|
||||
deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource;
|
||||
|
||||
/**
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { }
|
||||
|
||||
/**
|
||||
* An access to `document.location`, considered as a flow source for XXE vulnerabilities.
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
## 1.2.3
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.2.2
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -35,7 +35,7 @@ private module Impl implements
|
||||
|
|
||||
name = "replace"
|
||||
or
|
||||
name = "match" and exists(mcn.getAPropertyRead())
|
||||
name = ["match", "matchAll"] and exists(mcn.getAPropertyRead())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
* @security-severity 7.5
|
||||
* @precision high
|
||||
* @id js/actions/actions-artifact-leak
|
||||
* @tags security
|
||||
* @tags actions
|
||||
* security
|
||||
* external/cwe/cwe-312
|
||||
* external/cwe/cwe-315
|
||||
* external/cwe/cwe-359
|
||||
|
||||
3
javascript/ql/src/change-notes/released/1.2.3.md
Normal file
3
javascript/ql/src/change-notes/released/1.2.3.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 1.2.3
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 1.2.2
|
||||
lastReleaseVersion: 1.2.3
|
||||
|
||||
@@ -25,9 +25,16 @@ module CorsPermissiveConfiguration {
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/** A source of remote user input, considered as a flow source for CORS misconfiguration. */
|
||||
class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource {
|
||||
RemoteFlowSourceAsSource() { not this instanceof ClientSideRemoteFlowSource }
|
||||
/**
|
||||
* DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead!
|
||||
*/
|
||||
deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource;
|
||||
|
||||
/**
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource {
|
||||
ActiveThreatModelSourceAsSource() { not this instanceof ClientSideRemoteFlowSource }
|
||||
}
|
||||
|
||||
/** A flow label representing `true` and `null` values. */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/javascript-queries
|
||||
version: 1.2.3-dev
|
||||
version: 1.2.4-dev
|
||||
groups:
|
||||
- javascript
|
||||
- queries
|
||||
|
||||
@@ -25,7 +25,7 @@ app.get('/check-with-axios', req => {
|
||||
} else {
|
||||
axios.get(baseURL + req.params.tainted); // OK
|
||||
}
|
||||
|
||||
|
||||
// Blacklists are not safe
|
||||
if (!req.query.tainted.match(/^[/\.%]+$/)) {
|
||||
axios.get("test.com/" + req.query.tainted); // SSRF
|
||||
@@ -39,8 +39,29 @@ app.get('/check-with-axios', req => {
|
||||
}
|
||||
|
||||
axios.get("test.com/" + req.query.tainted); // OK - False Positive
|
||||
|
||||
if (req.query.tainted.matchAll(/^[0-9a-z]+$/g)) { // letters and numbers
|
||||
axios.get("test.com/" + req.query.tainted); // OK
|
||||
}
|
||||
if (req.query.tainted.matchAll(/^[0-9a-z\-_]+$/g)) { // letters, numbers, - and _
|
||||
axios.get("test.com/" + req.query.tainted); // OK
|
||||
}
|
||||
});
|
||||
|
||||
const isValidPath = path => path.match(/^[0-9a-z]+$/);
|
||||
|
||||
const isInBlackList = path => path.match(/^[/\.%]+$/);
|
||||
|
||||
app.get('/check-with-axios', req => {
|
||||
const baseURL = "test.com/"
|
||||
if (isValidPathMatchAll(req.params.tainted) ) {
|
||||
axios.get(baseURL + req.params.tainted); // OK
|
||||
}
|
||||
if (!isValidPathMatchAll(req.params.tainted) ) {
|
||||
axios.get(baseURL + req.params.tainted); // NOT OK - SSRF
|
||||
} else {
|
||||
axios.get(baseURL + req.params.tainted); // OK
|
||||
}
|
||||
});
|
||||
|
||||
const isValidPathMatchAll = path => path.matchAll(/^[0-9a-z]+$/g);
|
||||
|
||||
@@ -96,4 +96,16 @@
|
||||
sink(["source"].filter((x) => x).pop()); // NOT OK
|
||||
sink(["source"].filter((x) => !!x).pop()); // NOT OK
|
||||
|
||||
var arr8 = [];
|
||||
arr8 = arr8.toSpliced(0, 0, "source");
|
||||
sink(arr8.pop()); // NOT OK
|
||||
|
||||
var arr8_variant = [];
|
||||
arr8_variant = arr8_variant.toSpliced(0, 0, "safe", "source");
|
||||
arr8_variant.pop();
|
||||
sink(arr8_variant.pop()); // NOT OK
|
||||
|
||||
var arr8_spread = [];
|
||||
arr8_spread = arr8_spread.toSpliced(0, 0, ...arr);
|
||||
sink(arr8_spread.pop()); // NOT OK
|
||||
});
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
| tst.js:1:12:1:38 | '^http: ... le.com' | is a regular expression |
|
||||
| tst.js:4:37:4:43 | 'regex' | is a regular expression |
|
||||
| tst.js:9:37:9:43 | 'regex' | is a regular expression |
|
||||
@@ -0,0 +1,4 @@
|
||||
import javascript
|
||||
|
||||
from RegExpPatternSource regex
|
||||
select regex, "is a regular expression"
|
||||
11
javascript/ql/test/library-tests/RegExp/IsRegex/tst.js
Normal file
11
javascript/ql/test/library-tests/RegExp/IsRegex/tst.js
Normal file
@@ -0,0 +1,11 @@
|
||||
new RegExp('^http://test\.example.com'); // NOT OK
|
||||
|
||||
function detectRegexViaSplice(string) {
|
||||
let found = getMyThing().search('regex'); // NOT OK
|
||||
arr.splice(found, 1);
|
||||
};
|
||||
|
||||
function detectRegexViaToSpliced(string) {
|
||||
let found = getMyThing().search('regex'); // NOT OK
|
||||
arr.toSpliced(found, 1);
|
||||
};
|
||||
@@ -38,4 +38,40 @@ function test(x, y) {
|
||||
let j = [];
|
||||
j[j.length] = source();
|
||||
sink(j); // NOT OK
|
||||
|
||||
let k = [];
|
||||
let kSpliced = k.toSpliced(x, y, source());
|
||||
sink(k); // OK
|
||||
sink(kSpliced); // NOT OK
|
||||
|
||||
let l = [];
|
||||
l = l.toSpliced(x, y, source());
|
||||
sink(l); // NOT OK
|
||||
|
||||
let m = [];
|
||||
m = m.toSpliced(q, source(), y);
|
||||
sink(m); // OK
|
||||
|
||||
let n = [];
|
||||
n = n.toSpliced(source(), x);
|
||||
sink(n); // OK
|
||||
|
||||
let o = [];
|
||||
o = o.toSpliced(x, source());
|
||||
sink(o); // OK
|
||||
|
||||
let p = [];
|
||||
p = p.toSpliced(source(), x, y);
|
||||
sink(p); // OK
|
||||
|
||||
let q = [];
|
||||
q.splice(x, y, ...source());
|
||||
sink(q); // NOT OK
|
||||
|
||||
let r = [];
|
||||
let rSpliced = r.toSpliced(x, y, ...source());
|
||||
sink(rSpliced); // NOT OK
|
||||
sink(r); // OK
|
||||
r = r.toSpliced(x, y, ...source());
|
||||
sink(r); // NOT OK
|
||||
}
|
||||
|
||||
@@ -57,4 +57,19 @@ function test() {
|
||||
}
|
||||
|
||||
tagged`foo ${"safe"} bar ${x} baz`;
|
||||
|
||||
sink(x.reverse()); // NOT OK
|
||||
sink(x.toSpliced()); // NOT OK
|
||||
|
||||
sink(x.toSorted()) // NOT OK
|
||||
const xSorted = x.toSorted();
|
||||
sink(xSorted) // NOT OK
|
||||
|
||||
sink(x.toReversed()) // NOT OK
|
||||
const xReversed = x.toReversed();
|
||||
sink(xReversed) // NOT OK
|
||||
|
||||
sink(x.with()) // NOT OK
|
||||
const xWith = x.with();
|
||||
sink(xWith) // NOT OK
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<Blah :colonProp="x" @atProp="x" />
|
||||
<Blah :colonField.field="x" />
|
||||
</template>
|
||||
<script></script>
|
||||
@@ -76,6 +76,7 @@ component
|
||||
| single-file-component-3.vue:0:0:0:0 | single-file-component-3.vue |
|
||||
| single-file-component-4.vue:0:0:0:0 | single-file-component-4.vue |
|
||||
| single-file-component-5.vue:0:0:0:0 | single-file-component-5.vue |
|
||||
| special-syntax.vue:0:0:0:0 | special-syntax.vue |
|
||||
| tst.js:3:1:10:2 | new Vue ... 2\\n\\t}\\n}) |
|
||||
| tst.js:12:1:16:2 | new Vue ... \\t}),\\n}) |
|
||||
| tst.js:18:1:27:2 | Vue.com ... }\\n\\t}\\n}) |
|
||||
@@ -126,6 +127,10 @@ templateElement
|
||||
| single-file-component-5.vue:2:5:18:9 | <p>...</> |
|
||||
| single-file-component-5.vue:4:1:16:9 | <script>...</> |
|
||||
| single-file-component-5.vue:17:1:18:8 | <style>...</> |
|
||||
| special-syntax.vue:1:1:4:11 | <template>...</> |
|
||||
| special-syntax.vue:2:3:2:37 | <blah>...</> |
|
||||
| special-syntax.vue:3:3:3:32 | <blah>...</> |
|
||||
| special-syntax.vue:5:1:5:17 | <script>...</> |
|
||||
xssSink
|
||||
| compont-with-route.vue:2:8:2:21 | v-html=dataA |
|
||||
| single-component-file-1.vue:2:8:2:21 | v-html=dataA |
|
||||
@@ -161,3 +166,15 @@ remoteFlowSource
|
||||
| router.js:30:5:30:14 | from.query |
|
||||
| router.js:34:5:34:12 | to.query |
|
||||
| router.js:35:5:35:14 | from.query |
|
||||
parseErrors
|
||||
attribute
|
||||
| compont-with-route.vue:2:8:2:21 | v-html=dataA | v-html |
|
||||
| single-component-file-1.vue:2:8:2:21 | v-html=dataA | v-html |
|
||||
| single-file-component-2.vue:2:8:2:21 | v-html=dataA | v-html |
|
||||
| single-file-component-3.vue:2:8:2:21 | v-html=dataA | v-html |
|
||||
| single-file-component-3.vue:4:9:4:49 | src=./single-file-component-3-script.js | src |
|
||||
| single-file-component-4.vue:2:8:2:21 | v-html=dataA | v-html |
|
||||
| single-file-component-5.vue:2:8:2:21 | v-html=dataA | v-html |
|
||||
| special-syntax.vue:2:9:2:22 | :colonProp=x | :colonProp |
|
||||
| special-syntax.vue:2:24:2:34 | @atProp=x | @atProp |
|
||||
| special-syntax.vue:3:9:3:29 | :colonField.field=x | :colonField.field |
|
||||
|
||||
@@ -20,3 +20,7 @@ query predicate templateElement(Vue::Template::Element template) { any() }
|
||||
query predicate xssSink(DomBasedXss::Sink s) { any() }
|
||||
|
||||
query RemoteFlowSource remoteFlowSource() { any() }
|
||||
|
||||
query predicate parseErrors(JSParseError err) { exists(err) }
|
||||
|
||||
query predicate attribute(HTML::Attribute attrib, string name) { attrib.getName() = name }
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
| default |
|
||||
| remote |
|
||||
| request |
|
||||
@@ -0,0 +1,7 @@
|
||||
private import codeql.threatmodels.ThreatModels
|
||||
|
||||
from string kind
|
||||
where
|
||||
knownThreatModel(kind) and
|
||||
currentThreatModel(kind)
|
||||
select kind
|
||||
@@ -0,0 +1,2 @@
|
||||
testFailures
|
||||
failures
|
||||
@@ -0,0 +1,38 @@
|
||||
import javascript
|
||||
import testUtilities.InlineExpectationsTest
|
||||
|
||||
class TestSourcesConfiguration extends TaintTracking::Configuration {
|
||||
TestSourcesConfiguration() { this = "TestSources" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof ThreatModelSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(CallExpr call |
|
||||
call.getAnArgument() = sink.asExpr() and
|
||||
call.getCalleeName() = "SINK"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private module InlineTestSources implements TestSig {
|
||||
string getARelevantTag() { result in ["hasFlow", "threat-source"] }
|
||||
|
||||
predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
exists(DataFlow::Node sink |
|
||||
any(TestSourcesConfiguration c).hasFlow(_, sink) and
|
||||
value = "" and
|
||||
location = sink.getLocation() and
|
||||
tag = "hasFlow" and
|
||||
element = sink.toString()
|
||||
)
|
||||
or
|
||||
exists(ThreatModelSource source |
|
||||
value = source.getThreatModel() and
|
||||
location = source.getLocation() and
|
||||
tag = "threat-source" and
|
||||
element = source.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
import MakeTest<InlineTestSources>
|
||||
1698
javascript/ql/test/library-tests/threat-models/sources/externs.js
Normal file
1698
javascript/ql/test/library-tests/threat-models/sources/externs.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,120 @@
|
||||
import 'dummy';
|
||||
|
||||
var x = process.env['foo']; // $ threat-source=environment
|
||||
SINK(x); // $ hasFlow
|
||||
|
||||
var y = process.argv[2]; // $ threat-source=commandargs
|
||||
SINK(y); // $ hasFlow
|
||||
|
||||
|
||||
// Accessing command line arguments using yargs
|
||||
// https://www.npmjs.com/package/yargs/v/17.7.2
|
||||
const yargs = require('yargs/yargs');
|
||||
const { hideBin } = require('yargs/helpers');
|
||||
const argv = yargs(hideBin(process.argv)).argv; // $ threat-source=commandargs
|
||||
|
||||
SINK(argv.foo); // $ hasFlow
|
||||
|
||||
// older version
|
||||
// https://www.npmjs.com/package/yargs/v/7.1.2
|
||||
const yargsOld = require('yargs');
|
||||
const argvOld = yargsOld.argv; // $ threat-source=commandargs
|
||||
|
||||
SINK(argvOld.foo); // $ hasFlow
|
||||
|
||||
// Accessing command line arguments using yargs-parser
|
||||
const yargsParser = require('yargs-parser');
|
||||
const src = process.argv.slice(2); // $ threat-source=commandargs
|
||||
const parsedArgs = yargsParser(src);
|
||||
|
||||
SINK(parsedArgs.foo); // $ hasFlow
|
||||
|
||||
// Accessing command line arguments using minimist
|
||||
const minimist = require('minimist');
|
||||
const args = minimist(process.argv.slice(2)); // $ threat-source=commandargs
|
||||
|
||||
SINK(args.foo); // $ hasFlow
|
||||
|
||||
|
||||
// Accessing command line arguments using commander
|
||||
const { Command } = require('commander'); // $ SPURIOUS: threat-source=commandargs
|
||||
const program = new Command();
|
||||
program.parse(process.argv); // $ threat-source=commandargs
|
||||
|
||||
SINK(program.opts().foo); // $ hasFlow SPURIOUS: threat-source=commandargs
|
||||
|
||||
// ------ reading from database ------
|
||||
|
||||
// Accessing database using mysql
|
||||
const mysql = require('mysql');
|
||||
const connection = mysql.createConnection({host: 'localhost'});
|
||||
connection.connect();
|
||||
connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) { // $ threat-source=database
|
||||
if (error) throw error;
|
||||
SINK(results); // $ hasFlow
|
||||
SINK(results[0]); // $ hasFlow
|
||||
SINK(results[0].solution); // $ hasFlow
|
||||
});
|
||||
|
||||
// ------ reading from file ------
|
||||
|
||||
// Accessing file contents using fs
|
||||
const fs = require('fs');
|
||||
fs.readFile('file.txt', 'utf8', (err, data) => { // $ threat-source=file
|
||||
SINK(data); // $ hasFlow
|
||||
});
|
||||
|
||||
// Accessing file contents using fs.readFileSync
|
||||
const fileContent = fs.readFileSync('file.txt', 'utf8'); // $ threat-source=file
|
||||
SINK(fileContent); // $ hasFlow
|
||||
|
||||
// Accessing file contents using fs.promises
|
||||
fs.promises.readFile('file.txt', 'utf8').then((data) => { // $ threat-source=file
|
||||
SINK(data); // $ hasFlow
|
||||
});
|
||||
|
||||
// Accessing file contents using fs.createReadStream
|
||||
const readStream = fs.createReadStream('file.txt');
|
||||
readStream.on('data', (chunk) => { // $ threat-source=file
|
||||
SINK(chunk); // $ hasFlow
|
||||
});
|
||||
const data = readStream.read(); // $ threat-source=file
|
||||
SINK(data); // $ hasFlow
|
||||
|
||||
// using readline
|
||||
const readline = require('readline');
|
||||
const rl_file = readline.createInterface({
|
||||
input: fs.createReadStream('file.txt')
|
||||
});
|
||||
rl_file.on("line", (line) => { // $ SPURIOUS: threat-source=stdin MISSING: threat-source=file
|
||||
SINK(line); // $ hasFlow
|
||||
});
|
||||
|
||||
|
||||
// ------ reading from stdin ------
|
||||
|
||||
// Accessing stdin using process.stdin
|
||||
process.stdin.on('data', (data) => { // $ threat-source=stdin
|
||||
SINK(data); // $ hasFlow
|
||||
});
|
||||
|
||||
const stdin_line = process.stdin.read(); // $ threat-source=stdin
|
||||
SINK(stdin_line); // $ hasFlow
|
||||
|
||||
// Accessing stdin using readline
|
||||
const readline = require('readline');
|
||||
const rl_stdin = readline.createInterface({
|
||||
input: process.stdin
|
||||
});
|
||||
rl_stdin.question('<question>', (answer) => { // $ threat-source=stdin
|
||||
SINK(answer); // $ hasFlow
|
||||
});
|
||||
|
||||
function handler(answer) { // $ threat-source=stdin
|
||||
SINK(answer); // $ hasFlow
|
||||
}
|
||||
rl_stdin.question('<question>', handler);
|
||||
|
||||
rl_stdin.on("line", (line) => { // $ threat-source=stdin
|
||||
SINK(line); // $ hasFlow
|
||||
});
|
||||
@@ -25,3 +25,4 @@
|
||||
| tst-IncompleteHostnameRegExp.js:53:14:53:35 | test.example.com$ | This regular expression has an unescaped '.' before 'example.com', so it might match more hosts than expected. | tst-IncompleteHostnameRegExp.js:53:13:53:36 | 'test.' ... e.com$' | here |
|
||||
| tst-IncompleteHostnameRegExp.js:55:14:55:38 | ^http://test.example.com | This regular expression has an unescaped '.' before 'example.com', so it might match more hosts than expected. | tst-IncompleteHostnameRegExp.js:55:13:55:39 | '^http: ... le.com' | here |
|
||||
| tst-IncompleteHostnameRegExp.js:59:5:59:20 | foo.example\\.com | This regular expression has an unescaped '.' before 'example\\.com', so it might match more hosts than expected. | tst-IncompleteHostnameRegExp.js:59:2:59:32 | /^(foo. ... ever)$/ | here |
|
||||
| tst-IncompleteHostnameRegExp.js:61:18:61:41 | ^http://test.example.com | This regular expression has an unescaped '.' before 'example.com', so it might match more hosts than expected. | tst-IncompleteHostnameRegExp.js:61:17:61:42 | "^http: ... le.com" | here |
|
||||
|
||||
@@ -57,4 +57,6 @@
|
||||
/^http:\/\/(..|...)\.example\.com\/index\.html/; // OK, wildcards are intentional
|
||||
/^http:\/\/.\.example\.com\/index\.html/; // OK, the wildcard is intentional
|
||||
/^(foo.example\.com|whatever)$/; // kinda OK - one disjunction doesn't even look like a hostname
|
||||
|
||||
if (s.matchAll("^http://test.example.com")) {} // NOT OK
|
||||
});
|
||||
|
||||
@@ -59,3 +59,12 @@
|
||||
| tst-UnanchoredUrlRegExp.js:26:3:26:22 | "^https?://good.com" | This hostname pattern may match any domain name, as it is missing a '$' or '/' at the end. |
|
||||
| tst-UnanchoredUrlRegExp.js:35:2:35:32 | /https? ... 0-9]+)/ | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
|
||||
| tst-UnanchoredUrlRegExp.js:77:11:77:32 | /vimeo\\ ... 0-9]+)/ | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
|
||||
| tst-UnanchoredUrlRegExp.js:111:50:111:68 | "https?://good.com" | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
|
||||
| tst-UnanchoredUrlRegExp.js:112:61:112:79 | "https?://good.com" | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
|
||||
| tst-UnanchoredUrlRegExp.js:113:50:113:69 | "^https?://good.com" | This hostname pattern may match any domain name, as it is missing a '$' or '/' at the end. |
|
||||
| tst-UnanchoredUrlRegExp.js:114:50:114:72 | /^https ... d.com/g | This hostname pattern may match any domain name, as it is missing a '$' or '/' at the end. |
|
||||
| tst-UnanchoredUrlRegExp.js:115:50:115:94 | "(^http ... 2.com)" | This hostname pattern may match any domain name, as it is missing a '$' or '/' at the end. |
|
||||
| tst-UnanchoredUrlRegExp.js:116:50:116:93 | "(https ... e.com)" | This hostname pattern may match any domain name, as it is missing a '$' or '/' at the end. |
|
||||
| tst-UnanchoredUrlRegExp.js:117:50:117:59 | "good.com" | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
|
||||
| tst-UnanchoredUrlRegExp.js:118:50:118:68 | "https?://good.com" | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
|
||||
| tst-UnanchoredUrlRegExp.js:119:50:119:73 | "https? ... m:8080" | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
|
||||
|
||||
@@ -105,4 +105,27 @@
|
||||
|
||||
/\.com|\.org/; // OK, has no domain name
|
||||
/example\.com|whatever/; // OK, the other disjunction doesn't match a hostname
|
||||
|
||||
// MatchAll test cases:
|
||||
// Vulnerable patterns
|
||||
if ("http://evil.com/?http://good.com".matchAll("https?://good.com")) {} // NOT OK
|
||||
if ("http://evil.com/?http://good.com".matchAll(new RegExp("https?://good.com"))) {} // NOT OK
|
||||
if ("http://evil.com/?http://good.com".matchAll("^https?://good.com")) {} // NOT OK - missing post-anchor
|
||||
if ("http://evil.com/?http://good.com".matchAll(/^https?:\/\/good.com/g)) {} // NOT OK - missing post-anchor
|
||||
if ("http://evil.com/?http://good.com".matchAll("(^https?://good1.com)|(^https?://good2.com)")) {} // NOT OK - missing post-anchor
|
||||
if ("http://evil.com/?http://good.com".matchAll("(https?://good.com)|(^https?://goodie.com)")) {} // NOT OK - missing post-anchor
|
||||
if ("http://evil.com/?http://good.com".matchAll("good.com")) {} // NOT OK - missing protocol
|
||||
if ("http://evil.com/?http://good.com".matchAll("https?://good.com")) {} // NOT OK
|
||||
if ("http://evil.com/?http://good.com".matchAll("https?://good.com:8080")) {} // NOT OK
|
||||
|
||||
// Non-vulnerable patterns
|
||||
if ("something".matchAll("other")) {} // OK
|
||||
if ("something".matchAll("x.commissary")) {} // OK
|
||||
if ("http://evil.com/?http://good.com".matchAll("^https?://good.com$")) {} // OK
|
||||
if ("http://evil.com/?http://good.com".matchAll(new RegExp("^https?://good.com$"))) {} // OK
|
||||
if ("http://evil.com/?http://good.com".matchAll("^https?://good.com/$")) {} // OK
|
||||
if ("http://evil.com/?http://good.com".matchAll(/^https?:\/\/good.com\/$/)) {} // OK
|
||||
if ("http://evil.com/?http://good.com".matchAll("(^https?://good1.com$)|(^https?://good2.com$)")) {} // OK
|
||||
if ("http://evil.com/?http://good.com".matchAll("(https?://good.com$)|(^https?://goodie.com$)")) {} // OK
|
||||
|
||||
});
|
||||
|
||||
@@ -407,3 +407,25 @@ app.get('/join-spread', (req, res) => {
|
||||
fs.readFileSync(pathModule.join('foo', ...req.query.x.split('/'))); // NOT OK
|
||||
fs.readFileSync(pathModule.join(...req.query.x.split('/'))); // NOT OK
|
||||
});
|
||||
|
||||
app.get('/dotdot-matchAll-regexp', (req, res) => {
|
||||
let path = pathModule.normalize(req.query.x);
|
||||
if (pathModule.isAbsolute(path))
|
||||
return;
|
||||
fs.readFileSync(path); // NOT OK
|
||||
if (!path.matchAll(/\./)) {
|
||||
fs.readFileSync(path); // OK
|
||||
}
|
||||
if (!path.matchAll(/\.\./)) {
|
||||
fs.readFileSync(path); // OK
|
||||
}
|
||||
if (!path.matchAll(/\.\.\//)) {
|
||||
fs.readFileSync(path); // OK
|
||||
}
|
||||
if (!path.matchAll(/\.\.\/foo/)) {
|
||||
fs.readFileSync(path); // NOT OK
|
||||
}
|
||||
if (!path.matchAll(/(\.\.\/|\.\.\\)/)) {
|
||||
fs.readFileSync(path); // OK
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
nodes
|
||||
| test.js:4:5:4:29 | temp |
|
||||
| test.js:4:12:4:22 | process.env |
|
||||
| test.js:4:12:4:22 | process.env |
|
||||
| test.js:4:12:4:29 | process.env['foo'] |
|
||||
| test.js:7:14:7:61 | 'SELECT ... + temp |
|
||||
| test.js:7:14:7:61 | 'SELECT ... + temp |
|
||||
| test.js:7:58:7:61 | temp |
|
||||
edges
|
||||
| test.js:4:5:4:29 | temp | test.js:7:58:7:61 | temp |
|
||||
| test.js:4:12:4:22 | process.env | test.js:4:12:4:29 | process.env['foo'] |
|
||||
| test.js:4:12:4:22 | process.env | test.js:4:12:4:29 | process.env['foo'] |
|
||||
| test.js:4:12:4:29 | process.env['foo'] | test.js:4:5:4:29 | temp |
|
||||
| test.js:7:58:7:61 | temp | test.js:7:14:7:61 | 'SELECT ... + temp |
|
||||
| test.js:7:58:7:61 | temp | test.js:7:14:7:61 | 'SELECT ... + temp |
|
||||
#select
|
||||
| test.js:7:14:7:61 | 'SELECT ... + temp | test.js:4:12:4:22 | process.env | test.js:7:14:7:61 | 'SELECT ... + temp | This query string depends on a $@. | test.js:4:12:4:22 | process.env | user-provided value |
|
||||
@@ -0,0 +1,6 @@
|
||||
extensions:
|
||||
- addsTo:
|
||||
pack: codeql/threat-models
|
||||
extensible: threatModelConfiguration
|
||||
data:
|
||||
- ["local", true, 0]
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-089/SqlInjection.ql
|
||||
@@ -0,0 +1,9 @@
|
||||
const mysql = require('mysql');
|
||||
const pool = mysql.createPool(getConfig());
|
||||
|
||||
let temp = process.env['foo'];
|
||||
pool.getConnection(function(err, connection) {
|
||||
connection.query({
|
||||
sql: 'SELECT * FROM `books` WHERE `author` = ' + temp, // NOT OK
|
||||
}, function(error, results, fields) {});
|
||||
});
|
||||
@@ -116,4 +116,15 @@ const server4 = http.createServer((req, res) => {
|
||||
});
|
||||
server.start();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const serverMatchAll = http.createServer((req, res) => {
|
||||
let username = url.parse(req.url, true).query.username;
|
||||
let otherStr = username.matchAll(/.*/g)[0]; // BAD
|
||||
console.log(otherStr);
|
||||
});
|
||||
|
||||
const serverMatchAl2l = http.createServer((req, res) => {
|
||||
const result = url.parse(req.url, true).query.username.matchAll(/(\d+)/g); // BAD
|
||||
console.log("First captured group:", RegExp.$1);
|
||||
});
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
Security/CWE-611/Xxe.ql
|
||||
query: Security/CWE-611/Xxe.ql
|
||||
postprocess: testUtilities/InlineExpectationsTestQuery.ql
|
||||
@@ -1,5 +1,5 @@
|
||||
function test() {
|
||||
var src = document.location.search;
|
||||
var src = document.location.search; // $ Source=search
|
||||
|
||||
if (window.DOMParser) {
|
||||
// OK: DOMParser only expands internal general entities
|
||||
@@ -8,10 +8,10 @@ function test() {
|
||||
var parser;
|
||||
try {
|
||||
// NOT OK: XMLDOM expands external entities by default
|
||||
(new ActiveXObject("Microsoft.XMLDOM")).loadXML(src);
|
||||
(new ActiveXObject("Microsoft.XMLDOM")).loadXML(src); // $ Alert=search
|
||||
} catch (e) {
|
||||
// NOT OK: MSXML expands external entities by default
|
||||
(new ActiveXObject("Msxml2.DOMDocument")).loadXML(src);
|
||||
(new ActiveXObject("Msxml2.DOMDocument")).loadXML(src); // $ Alert=search
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
const express = require('express');
|
||||
const libxmljs = require('libxmljs');
|
||||
|
||||
express().get('/some/path', function(req) {
|
||||
express().get('/some/path', function (req) {
|
||||
// NOT OK: unguarded entity expansion
|
||||
libxmljs.parseXml(req.param("some-xml"), { noent: true });
|
||||
libxmljs.parseXml(req.param("some-xml"), { noent: true }); // $ Alert
|
||||
});
|
||||
|
||||
express().post('/some/path', function(req, res) {
|
||||
express().post('/some/path', function (req, res) {
|
||||
// NOT OK: unguarded entity expansion
|
||||
libxmljs.parseXml(req.param("some-xml"), { noent: true });
|
||||
libxmljs.parseXml(req.param("some-xml"), { noent: true }); // $ Alert
|
||||
|
||||
// NOT OK: unguarded entity expansion
|
||||
libxmljs.parseXmlString(req.param("some-xml"), {noent:true})
|
||||
libxmljs.parseXmlString(req.param("some-xml"), { noent: true }) // $ Alert
|
||||
// NOT OK: unguarded entity expansion
|
||||
libxmljs.parseXmlString(req.files.products.data.toString('utf8'), {noent:true})
|
||||
|
||||
libxmljs.parseXmlString(req.files.products.data.toString('utf8'), { noent: true })// $ Source=files $ Alert=files
|
||||
|
||||
// OK - no entity expansion
|
||||
libxmljs.parseXmlString(req.files.products.data.toString('utf8'), {noent:false})
|
||||
libxmljs.parseXmlString(req.files.products.data.toString('utf8'), { noent: false })
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const express = require('express');
|
||||
const libxmljs = require('libxmljs');
|
||||
|
||||
express().get('/some/path', function(req) {
|
||||
express().get('/some/path', function (req) {
|
||||
const parser = new libxmljs.SaxParser();
|
||||
parser.parseString(req.param("some-xml")); // NOT OK: the SAX parser expands external entities by default
|
||||
parser.parseString(req.param("some-xml")); // $ Alert: the SAX parser expands external entities by default
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const express = require('express');
|
||||
const libxmljs = require('libxmljs');
|
||||
|
||||
express().get('/some/path', function(req) {
|
||||
express().get('/some/path', function (req) {
|
||||
const parser = new libxmljs.SaxPushParser();
|
||||
parser.push(req.param("some-xml")); // NOT OK: the SAX parser expands external entities by default
|
||||
parser.push(req.param("some-xml")); // $ Alert: the SAX parser expands external entities by default
|
||||
});
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @kind test-postprocess
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
private import codeql.util.test.InlineExpectationsTest as T
|
||||
private import internal.InlineExpectationsTestImpl
|
||||
import T::TestPostProcessing
|
||||
import T::TestPostProcessing::Make<Impl, Input>
|
||||
|
||||
private module Input implements T::TestPostProcessing::InputSig<Impl> {
|
||||
string getRelativeUrl(Location location) {
|
||||
exists(File f, int startline, int startcolumn, int endline, int endcolumn |
|
||||
location.hasLocationInfo(_, startline, startcolumn, endline, endcolumn) and
|
||||
f = location.getFile()
|
||||
|
|
||||
result =
|
||||
f.getRelativePath() + ":" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user