Files
codeql/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll
2022-03-11 11:10:33 +01:00

182 lines
6.2 KiB
Plaintext

/**
* Provides a class for modeling sources of remote user input.
*/
import javascript
import semmle.javascript.frameworks.HTTP
import semmle.javascript.security.dataflow.DOM
private import semmle.javascript.internal.CachedStages
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. */
cached
abstract string getSourceType();
/**
* Holds if this can be a user-controlled object, such as a JSON object parsed from user-controlled data.
*/
cached
predicate isUserControlledObject() { none() }
}
/**
* A source of remote input in a web browser environment.
*/
cached
abstract class ClientSideRemoteFlowSource extends RemoteFlowSource {
/** Gets a string indicating what part of the browser environment this was derived from. */
cached
abstract ClientSideRemoteFlowKind getKind();
}
}
import Cached
/**
* A type of remote flow source that is specific to the browser environment.
*/
class ClientSideRemoteFlowKind extends string {
ClientSideRemoteFlowKind() { this = ["query", "fragment", "path", "url", "name"] }
/**
* Holds if this is the `query` kind, describing sources derived from the query parameters of the browser URL,
* such as `location.search`.
*/
predicate isQuery() { this = "query" }
/**
* Holds if this is the `frgament` kind, describing sources derived from the fragment part of the browser URL,
* such as `location.hash`.
*/
predicate isFragment() { this = "fragment" }
/**
* Holds if this is the `path` kind, describing sources derived from the pathname of the browser URL,
* such as `location.pathname`.
*/
predicate isPath() { this = "path" }
/**
* Holds if this is the `url` kind, describing sources derived from the browser URL,
* where the untrusted part of the URL is prefixed by trusted data, such as the scheme and hostname.
*/
predicate isUrl() { this = "url" }
/** Holds if this is the `query` or `fragment` kind. */
predicate isQueryOrFragment() { this.isQuery() or this.isFragment() }
/** Holds if this is the `path`, `query`, or `fragment` kind. */
predicate isPathOrQueryOrFragment() { this.isPath() or this.isQuery() or this.isFragment() }
/** Holds if this is the `path` or `url` kind. */
predicate isPathOrUrl() { this.isPath() or this.isUrl() }
/** Holds if this is the `name` kind, describing sources derived from the window name, such as `window.name`. */
predicate isWindowName() { this = "name" }
}
/**
* 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).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. */
API::Label::ApiLabel getComponent(int i) {
exists(string raw | raw = this.getValue().splitAt(".", i + 1) |
i = 0 and
result =
API::Label::entryPoint(any(ExternalRemoteFlowSourceSpecEntryPoint e | e.getName() = raw))
or
i > 0 and
result = API::Label::member(raw)
)
}
/** Gets the first part of this access path. E.g. for "window.user.name" the result is "window". */
string getRootPath() { result = this.getValue().splitAt(".", 1) }
/** Gets the index of the last component of this access path. */
int getMaxComponentIndex() { result = max(int i | exists(this.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 = this.resolveUpTo(i - 1).getASuccessor(this.getComponent(i))
}
/** Gets the API node to which this access path resolves. */
API::Use resolve() { result = this.resolveUpTo(this.getMaxComponentIndex()) }
}
/**
* The global variable referenced by a `RemoteFlowSourceAccessPath`, declared as an API
* entry point.
*/
private class ExternalRemoteFlowSourceSpecEntryPoint extends API::EntryPoint {
string name;
ExternalRemoteFlowSourceSpecEntryPoint() {
name = any(RemoteFlowSourceAccessPath s).getRootPath() and
this = "ExternalRemoteFlowSourceSpec " + name
}
string getName() { result = 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() { Stages::Taint::ref() and this = ap.resolve().getAnImmediateUse() }
override string getSourceType() { result = ap.getSourceType() }
}