mirror of
https://github.com/github/codeql.git
synced 2026-04-30 11:15:13 +02:00
JavaScript: Allow specifying additional remote flow sources through JSON.
This commit is contained in:
@@ -16,3 +16,98 @@ abstract class RemoteFlowSource extends DataFlow::Node {
|
||||
*/
|
||||
predicate isUserControlledObject() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A specification of a remote flow source in a JSON file included in the database.
|
||||
*
|
||||
* The JSON file must be named `codeql-javascript-remote-flow-sources.json` and consist of a JSON
|
||||
* object. The object's keys are source types (in the sense of `RemoteFlowSource.getSourceType()`),
|
||||
* each mapping to a list of strings. Each string in that list must be of the form `window.f.props`
|
||||
* where `f` is the name of a global variable, and `props` is a possibly empty sequence of property
|
||||
* names separated by dots. It declares any value stored in the given property sequece of `f` to be
|
||||
* a remote flow source of the type specified by the key.
|
||||
*
|
||||
* For example, consider the following specification:
|
||||
*
|
||||
* ```json
|
||||
* {
|
||||
* "user input": [ "window.user.name", "window.user.address", "window.dob" ]
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* It declares that the contents of global variable `dob`, as well as the contents of properties
|
||||
* `name` and `address` of global variable `user` should be considered as remote flow sources with
|
||||
* source type "user input".
|
||||
*/
|
||||
private class RemoteFlowSourceAccessPath extends JSONString {
|
||||
string sourceType;
|
||||
|
||||
RemoteFlowSourceAccessPath() {
|
||||
exists(JSONObject specs |
|
||||
specs.isTopLevel() and
|
||||
this.getFile().getBaseName() = "codeql-javascript-remote-flow-sources.json" and
|
||||
this = specs.getPropValue(sourceType).(JSONArray).getElementValue(_) and
|
||||
this.getValue().regexpMatch("window(\\.\\w+)+")
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the source type of this remote flow source. */
|
||||
string getSourceType() { result = sourceType }
|
||||
|
||||
/** Gets the `i`th component of the access path specifying this remote flow source. */
|
||||
string getComponent(int i) {
|
||||
exists(string raw | raw = this.getValue().splitAt(".", i + 1) |
|
||||
i = 0 and
|
||||
result = "ExternalRemoteFlowSourceSpec " + raw
|
||||
or
|
||||
i > 0 and
|
||||
result = API::EdgeLabel::member(raw)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the index of the last component of this access path. */
|
||||
int getMaxComponentIndex() { result = max(int i | exists(getComponent(i))) }
|
||||
|
||||
/**
|
||||
* Gets the API node to which the prefix of the access path up to and including `i` resolves.
|
||||
*
|
||||
* As a special base case, resolving up to -1 gives the root API node.
|
||||
*/
|
||||
private API::Node resolveUpTo(int i) {
|
||||
i = -1 and
|
||||
result = API::root()
|
||||
or
|
||||
result = resolveUpTo(i - 1).getASuccessor(getComponent(i))
|
||||
}
|
||||
|
||||
/** Gets the API node to which this access path resolves. */
|
||||
API::Use resolve() { result = resolveUpTo(getMaxComponentIndex()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* The global variable referenced by a `RemoteFlowSourceAccessPath`, declared as an API
|
||||
* entry point.
|
||||
*/
|
||||
private class ExternalRemoteFlowSourceSpecEntryPoint extends API::EntryPoint {
|
||||
string name;
|
||||
|
||||
ExternalRemoteFlowSourceSpecEntryPoint() {
|
||||
this = any(RemoteFlowSourceAccessPath s).getComponent(0) and
|
||||
this = "ExternalRemoteFlowSourceSpec " + name
|
||||
}
|
||||
|
||||
override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef(name) }
|
||||
|
||||
override DataFlow::Node getARhs() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A remote flow source induced by a `RemoteFlowSourceAccessPath`.
|
||||
*/
|
||||
private class ExternalRemoteFlowSource extends RemoteFlowSource {
|
||||
RemoteFlowSourceAccessPath ap;
|
||||
|
||||
ExternalRemoteFlowSource() { this = ap.resolve().getAnImmediateUse() }
|
||||
|
||||
override string getSourceType() { result = ap.getSourceType() }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
missing
|
||||
spurious
|
||||
@@ -0,0 +1,25 @@
|
||||
import javascript
|
||||
|
||||
predicate remoteFlowSourceSpec(Comment c, string path, int line, string sourceType) {
|
||||
c.getLocation().hasLocationInfo(path, line, _, _, _) and
|
||||
sourceType = c.getText().regexpCapture("\\s*RemoteFlowSource\\s*:\\s*(.+)", 1)
|
||||
}
|
||||
|
||||
predicate remoteFlowSource(RemoteFlowSource rfs, string path, int line, string sourceType) {
|
||||
rfs.hasLocationInfo(path, line, _, _, _) and
|
||||
sourceType = rfs.getSourceType()
|
||||
}
|
||||
|
||||
query predicate missing(Comment c, string sourceType) {
|
||||
exists(string path, int line |
|
||||
remoteFlowSourceSpec(c, path, line, sourceType) and
|
||||
not remoteFlowSource(_, path, line, sourceType)
|
||||
)
|
||||
}
|
||||
|
||||
query predicate spurious(RemoteFlowSource rfs, string sourceType) {
|
||||
exists(string path, int line |
|
||||
not remoteFlowSourceSpec(_, path, line, sourceType) and
|
||||
remoteFlowSource(rfs, path, line, sourceType)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"user input": [
|
||||
"window.user.name",
|
||||
"window.user.address",
|
||||
"window.dob"
|
||||
],
|
||||
"uncontrolled path": [
|
||||
"window.upload"
|
||||
]
|
||||
}
|
||||
21
javascript/ql/test/library-tests/RemoteFlowSources/tst.js
Normal file
21
javascript/ql/test/library-tests/RemoteFlowSources/tst.js
Normal file
@@ -0,0 +1,21 @@
|
||||
window.user.name; // RemoteFlowSource: user input
|
||||
window.user.address; // RemoteFlowSource: user input
|
||||
window.dob; // RemoteFlowSource: user input
|
||||
window.upload; // RemoteFlowSource: uncontrolled path
|
||||
|
||||
this.user.name; // RemoteFlowSource: user input
|
||||
this.user.address; // RemoteFlowSource: user input
|
||||
this.dob; // RemoteFlowSource: user input
|
||||
this.upload; // RemoteFlowSource: uncontrolled path
|
||||
|
||||
user.name; // RemoteFlowSource: user input
|
||||
user.address; // RemoteFlowSource: user input
|
||||
dob; // RemoteFlowSource: user input
|
||||
upload; // RemoteFlowSource: uncontrolled path
|
||||
|
||||
(function (global) {
|
||||
global.user.name; // RemoteFlowSource: user input
|
||||
global.user.address; // RemoteFlowSource: user input
|
||||
global.dob; // RemoteFlowSource: user input
|
||||
global.upload; // RemoteFlowSource: uncontrolled path
|
||||
})(this);
|
||||
Reference in New Issue
Block a user