JavaScript: Allow specifying additional remote flow sources through JSON.

This commit is contained in:
Max Schaefer
2020-12-09 11:36:28 +00:00
parent 0ccfe4f135
commit 9f8508fdc7
5 changed files with 153 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,2 @@
missing
spurious

View File

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

View File

@@ -0,0 +1,10 @@
{
"user input": [
"window.user.name",
"window.user.address",
"window.dob"
],
"uncontrolled path": [
"window.upload"
]
}

View 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);