Merge branch 'github:main' into napalys/matchAll-support

This commit is contained in:
Napalys Klicius
2024-11-05 09:31:30 +01:00
committed by GitHub
1221 changed files with 96473 additions and 82197 deletions

View File

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

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

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 2.0.2
lastReleaseVersion: 2.1.0

View File

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

View File

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

View File

@@ -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
@@ -9,6 +9,7 @@ dependencies:
codeql/dataflow: ${workspace}
codeql/mad: ${workspace}
codeql/regex: ${workspace}
codeql/threat-models: ${workspace}
codeql/tutorial: ${workspace}
codeql/util: ${workspace}
codeql/xml: ${workspace}
@@ -17,4 +18,5 @@ dataExtensions:
- semmle/javascript/frameworks/**/model.yml
- semmle/javascript/frameworks/**/*.model.yml
- semmle/javascript/security/domains/**/*.model.yml
- ext/*.model.yml
warnOnImplicitThis: true

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -38,9 +38,16 @@ module ClientSideUrlRedirect {
DocumentUrl() { this = "document.url" }
}
/** 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()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -331,8 +331,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.

View File

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

View File

@@ -28,8 +28,4 @@ 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)
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -31,9 +31,16 @@ module ResourceExhaustion {
*/
abstract class Sanitizer extends DataFlow::Node { }
/** 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())

View File

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

View File

@@ -572,16 +572,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 }
}
/**

View File

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

View File

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

View File

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

View File

@@ -71,9 +71,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.

View File

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

View File

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

View File

@@ -1,3 +1,7 @@
## 1.2.3
No user-facing changes.
## 1.2.2
No user-facing changes.

View File

@@ -0,0 +1,3 @@
## 1.2.3
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 1.2.2
lastReleaseVersion: 1.2.3

View File

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

View File

@@ -1,5 +1,5 @@
name: codeql/javascript-queries
version: 1.2.3-dev
version: 1.2.4-dev
groups:
- javascript
- queries

View File

@@ -0,0 +1,5 @@
<template>
<Blah :colonProp="x" @atProp="x" />
<Blah :colonField.field="x" />
</template>
<script></script>

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
| default |
| remote |
| request |

View File

@@ -0,0 +1,7 @@
private import codeql.threatmodels.ThreatModels
from string kind
where
knownThreatModel(kind) and
currentThreatModel(kind)
select kind

View File

@@ -0,0 +1,2 @@
testFailures
failures

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
extensions:
- addsTo:
pack: codeql/threat-models
extensible: threatModelConfiguration
data:
- ["local", true, 0]

View File

@@ -0,0 +1 @@
Security/CWE-089/SqlInjection.ql

View File

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

View File

@@ -1 +1,2 @@
Security/CWE-611/Xxe.ql
query: Security/CWE-611/Xxe.ql
postprocess: testUtilities/InlineExpectationsTestQuery.ql

View File

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

View File

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

View File

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

View File

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

View File

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