8000 Merge pull request #17256 from RasmusWL/js-threat-models · github/codeql@8f80c24 · GitHub
[go: up one dir, main page]

Skip to content

Commit 8f80c24

Browse files
authored
Merge pull request #17256 from RasmusWL/js-threat-models
JS: Add support for threat models
2 parents 34e8ea1 + c0ad9ba commit 8f80c24

File tree

45 files changed

+2361
-185
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+2361
-185
lines changed

docs/codeql/codeql-language-guides/customizing-library-models-for-javascript.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ Kinds
506506
Source kinds
507507
~~~~~~~~~~~~
508508

509-
- **remote**: A generic source of remote flow. Most taint-tracking queries will use such a source. Currently this is the only supported source kind.
509+
See documentation below for :ref:`Threat models <threat-models-javascript>`.
510510

511511
Sink kinds
512512
~~~~~~~~~~
@@ -529,3 +529,10 @@ Summary kinds
529529

530530
- **taint**: A summary that propagates taint. This means the output is not necessarily equal to the input, but it was derived from the input in an unrestrictive way. An attacker who controls the input will have significant control over the output as well.
531531
- **value**: A summary that preserves the value of the input or creates a copy of the input such that all of its object properties are preserved.
532+
533+
.. _threat-models-javascript:
534+
535+
Threat models
536+
-------------
537+
538+
.. include:: ../reusables/threat-model-description.rst

docs/codeql/reusables/beta-note-threat-models.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
Note
44

5-
Threat models are currently in beta and subject to change. During the beta, threat models are supported only by Java, C# and Python analysis.
5+
Threat models are currently in beta and subject to change. During the beta, threat models are supported only by Java, C#, Python and JavaScript/TypeScript analysis.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: feature
3+
---
4+
* 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.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/threat-models
4+
extensible: threatModelConfiguration
5+
data:
6+
# Since responses are enabled by default in the shared threat-models configuration,
7+
# we need to disable it here to keep existing behavior for the javascript analysis.
8+
- ["response", false, -2147483647]

javascript/ql/lib/javascript.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ import semmle.javascript.frameworks.Classnames
8181
import semmle.javascript.frameworks.ClassValidator
8282
import semmle.javascript.frameworks.ClientRequests
8383
import semmle.javascript.frameworks.ClosureLibrary
84+
import semmle.javascript.frameworks.CommandLineArguments
8485
import semmle.javascript.frameworks.CookieLibraries
8586
import semmle.javascript.frameworks.Credentials
8687
import semmle.javascript.frameworks.CryptoLibraries

javascript/ql/lib/qlpack.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ dependencies:
99
codeql/dataflow: ${workspace}
1010
codeql/mad: ${workspace}
1111
codeql/regex: ${workspace}
12+
codeql/threat-models: ${workspace}
1213
codeql/tutorial: ${workspace}
1314
codeql/util: ${workspace}
1415
codeql/xml: ${workspace}
@@ -17,4 +18,5 @@ dataExtensions:
1718
- semmle/javascript/frameworks/**/model.yml
1819
- semmle/javascript/frameworks/**/*.model.yml
1920
- semmle/javascript/security/domains/**/*.model.yml
21+
- ext/*.model.yml
2022
warnOnImplicitThis: true

javascript/ql/lib/semmle/javascript/Concepts.qll

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,63 @@
55
*/
66

77
import javascript
8+
private import codeql.threatmodels.ThreatModels
9+
10+
/**
11+
* A data flow source, for a specific threat-model.
12+
*
13+
* Extend this class to refine existing API models. If you want to model new APIs,
14+
* extend `ThreatModelSource::Range` instead.
15+
*/
16+
class ThreatModelSource extends DataFlow::Node instanceof ThreatModelSource::Range {
17+
/**
18+
* Gets a string that represents the source kind with respect to threat modeling.
19+
*
20+
*
21+
* See
22+
* - https://github.com/github/codeql/blob/main/docs/codeql/reusables/threat-model-description.rst
23+
* - https://github.com/github/codeql/blob/main/shared/threat-models/ext/threat-model-grouping.model.yml
24+
*/
25+
string getThreatModel() { result = super.getThreatModel() }
26+
27+
/** Gets a string that describes the type of this threat-model source. */
28+
string getSourceType() { result = super.getSourceType() }
29+
}
30+
31+
/** Provides a class for modeling new sources for specific threat-models. */
32+
module ThreatModelSource {
33+
/**
34+
* A data flow source, for a specific threat-model.
35+
*
36+
* Extend this class to model new APIs. If you want to refine existing API models,
37+
* extend `ThreatModelSource` instead.
38+
*/
39+
abstract class Range extends DataFlow::Node {
40+
/**
41+
* Gets a string that represents the source kind with respect to threat modeling.
42+
*
43+
* See
44+
* - https://github.com/github/codeql/blob/main/docs/codeql/reusables/threat-model-description.rst
45+
* - https://github.com/github/codeql/blob/main/shared/threat-models/ext/threat-model-grouping.model.yml
46+
*/
47+
abstract string getThreatModel();
48+
49+
/** Gets a string that describes the type of this threat-model source. */
50+
abstract string getSourceType();
51+
}
52+
}
53+
54+
/**
55+
* A data flow source that is enabled in the current threat model configuration.
56+
*/
57+
class ActiveThreatModelSource extends ThreatModelSource {
58+
ActiveThreatModelSource() {
59+
exists(string kind |
60+
currentThreatModel(kind) and
61+
this.getThreatModel() = kind
62+
)
63+
}
64+
}
865

966
/**
1067
* A data flow node that executes an operating system command,
@@ -65,6 +122,19 @@ abstract class FileSystemReadAccess extends FileSystemAccess {
65122
abstract DataFlow::Node getADataNode();
66123
}
67124

125+
/**
126+
* A FileSystemReadAccess seen as a ThreatModelSource.
127+
*/
128+
private class FileSystemReadAccessAsThreatModelSource extends ThreatModelSource::Range {
129+
FileSystemReadAccessAsThreatModelSource() {
130+
this = any(FileSystemReadAccess access).getADataNode()
131+
}
132+
133+
override string getThreatModel() { result = "file" }
134+
135+
override string getSourceType() { result = "FileSystemReadAccess" }
136+
}
137+
68138
/**
69139
* A data flow node that writes data to the file system.
70140
*/
@@ -91,6 +161,17 @@ abstract class DatabaseAccess extends DataFlow::Node {
91161
}
92162
}
93163

164+
/**
165+
* A DatabaseAccess seen as a ThreatModelSource.
166+
*/
167+
private class DatabaseAccessAsThreatModelSource extends ThreatModelSource::Range {
168+
DatabaseAccessAsThreatModelSource() { this = any(DatabaseAccess access).getAResult() }
169+
170+
override string getThreatModel() { result = "database" }
171+
172+
override string getSourceType() { result = "DatabaseAccess" }
173+
}
174+
94175
/**
95176
* A data flow node that reads persistent data.
96177
*/
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/** Provides modeling for parsed command line arguments. */
2+
3+
import javascript
4+
5+
/**
6+
* An object containing command-line arguments, potentially parsed by a library.
7+
*
8+
* Extend this class to refine existing API models. If you want to model new APIs,
9+
* extend `CommandLineArguments::Range` instead.
10+
*/
11+
class CommandLineArguments extends ThreatModelSource instanceof CommandLineArguments::Range { }
12+
13+
/** Provides a class for modeling new sources of remote user input. */
14+
module CommandLineArguments {
15+
/**
16+
* An object containing command-line arguments, potentially parsed by a library.
17+
*
18+
* Extend this class to model new APIs. If you want to refine existing API models,
19+
* extend `CommandLineArguments` instead.
20+
*/
21+
abstract class Range extends ThreatModelSource::Range {
22+
override string getThreatModel() { result = "commandargs" }
23+
24+
override string getSourceType() { result = "CommandLineArguments" }
25+
}
26+
}
27+
28+
/** A read of `process.argv`, considered as a threat-model source. */
29+
private class ProcessArgv extends CommandLineArguments::Range {
30+
ProcessArgv() {
31+
// `process.argv[0]` and `process.argv[1]` are paths to `node` and `main`, and
32+
// therefore should not be considered a threat-source... However, we don't have an
33+
// easy way to exclude them, so we need to allow them.
34+
this = NodeJSLib::process().getAPropertyRead("argv")
35+
}
36+
37+
override string getSourceType() { result = "process.argv" }
38+
}
39+
40+
private class DefaultModels extends CommandLineArguments::Range {
41+
DefaultModels() {
42+
// `require('get-them-args')(...)` => `{ unknown: [], a: ... b: ... }`
43+
this = DataFlow::moduleImport("get-them-args").getACall()
44+
or
45+
// `require('optimist').argv` => `{ _: [], a: ... b: ... }`
46+
this = DataFlow::moduleMember("optimist", "argv")
47+
or
48+
// `require("arg")({...spec})` => `{_: [], a: ..., b: ...}`
49+
this = DataFlow::moduleImport("arg").getACall()
50+
or
51+
// `(new (require(argparse)).ArgumentParser({...spec})).parse_args()` => `{a: ..., b: ...}`
52+
this =
53+
API::moduleImport("argparse")
54+
.getMember("ArgumentParser")
55+
.getInstance()
56+
.getMember("parse_args")
57+
.getACall()
58+
or
59+
// `require('command-line-args')({...spec})` => `{a: ..., b: ...}`
60+
this = DataFlow::moduleImport("command-line-args").getACall()
61+
or
62+
// `require('meow')(help, {...spec})` => `{a: ..., b: ....}`
63+
this = DataFlow::moduleImport("meow").getACall()
64+
or
65+
// `require("dashdash").createParser(...spec)` => `{a: ..., b: ...}`
66+
this =
67+
[
68+
API::moduleImport("dashdash"),
69+
API::moduleImport("dashdash").getMember("createParser").getReturn()
70+
].getMember("parse").getACall()
71+
or
72+
// `require('commander').myCmdArgumentName`
73+
this = commander().getAMember().asSource()
74+
or
75+
// `require('commander').opt()` => `{a: ..., b: ...}`
76+
this = commander().getMember("opts").getACall()
77+
or
78+
this = API::moduleImport("yargs/yargs").getReturn().getMember("argv").asSource()
79+
}
80+
}
81+
82+
/**
83+
* A step for propagating taint through command line parsing,
84+
* such as `var succ = require("minimist")(pred)`.
85+
*/
86+
private class ArgsParseStep extends TaintTracking::SharedTaintStep {
87+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
88+
exists(DataFlow::CallNode call |
89+
call = DataFlow::moduleMember("args", "parse").getACall() or
90+
call = DataFlow::moduleImport(["yargs-parser", "minimist", "subarg"]).getACall()
91+
|
92+
succ = call and
93+
pred = call.getArgument(0)
94+
)
95+
}
96+
}
97+
98+
/**
99+
* Gets a Command instance from the `commander` library.
100+
*/
101+
private API::Node commander() {
102+
result = API::moduleImport("commander")
103+
or
104+
// `require("commander").program === require("commander")`
105+
result = commander().getMember("program")
106+
or
107+
result = commander().getMember("Command").getInstance()
108+
or
109+
// lots of chainable methods
110+
result = commander().getAMember().getReturn()
111+
}
112+
113+
/**
114+
* Gets an instance of `yargs`.
115+
* Either directly imported as a module, or through some chained method call.
116+
*/
117+
private DataFlow::SourceNode yargs() {
118+
result = DataFlow::moduleImport("yargs")
119+
or
120+
// script used to generate list of chained methods: https://gist.github.com/erik-krogh/f8afe952c0577f4b563a993e613269ba
121+
exists(string method |
122+
not method =
123+
// the methods that does not return a chained `yargs` object.
124+
[
125+
"getContext", "getDemandedOptions", "getDemandedCommands", "getDeprecatedOptions",
126+
"_getParseContext", "getOptions", "getGroups", "getStrict", "getStrictCommands",
127+
"getExitProcess", "locale", "getUsageInstance", "getCommandInstance"
128+
]
129+
|
130+
result = yargs().getAMethodCall(method)
131+
)
132+
}
133+
134+
/**
135+
* An array of command line arguments (`argv`) parsed by the `yargs` library.
136+
*/
137+
private class YargsArgv extends CommandLineArguments::Range {
138+
YargsArgv() {
139+
this = yargs().getAPropertyRead("argv")
140+
or
141+
this = yargs().getAMethodCall("parse") and
142+
this.(DataFlow::MethodCallNode).getNumArgument() = 0
143+
}
144+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/javascript-all
4+
extensible: sourceModel
5+
data:
6+
- ['fs', 'Member[promises].Member[readFile].ReturnValue.Member[then].Argument[0].Parameter[0]', 'file']
7+
- ['global', 'Member[process].Member[stdin].Member[read].ReturnValue', 'stdin']
8+
- ['global', 'Member[process].Member[stdin].Member[on,addListener].WithStringArgument[0=data].Argument[1].Parameter[0]', 'stdin']
9+
- ['readline', 'Member[createInterface].ReturnValue.Member[question].Argument[1].Parameter[0]', 'stdin']
10+
- ['readline', 'Member[createInterface].ReturnValue.Member[on,addListener].WithStringArgument[0=line].Argument[1].Parameter[0]', 'stdin']

javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,4 +1244,13 @@ module NodeJSLib {
12441244
result = moduleImport().getAPropertyRead(member)
12451245
}
12461246
}
1247+
1248+
/** A read of `process.env`, considered as a threat-model source. */
1249+
private class ProcessEnvThreatSource extends ThreatModelSource::Range {
1250+
ProcessEnvThreatSource() { this = NodeJSLib::process().getAPropertyRead("env") }
1251+
1252+
override string getThreatModel() { result = "environment" }
1253+
1254+
override string getSourceType() { result = "process.env" }
1255+
}
12471256
}

0 commit comments

Comments
 (0)
0