diff --git a/javascript/ql/src/Security/CWE-020/ExternalAPISinkExample.js b/javascript/ql/src/Security/CWE-020/ExternalAPISinkExample.js new file mode 100644 index 000000000000..109191caf401 --- /dev/null +++ b/javascript/ql/src/Security/CWE-020/ExternalAPISinkExample.js @@ -0,0 +1,4 @@ +express().get('/news', (req, res) => { + let topic = req.query.topic; + res.send(`

${topic}

`); +}); diff --git a/javascript/ql/src/Security/CWE-020/ExternalAPITaintStepExample.js b/javascript/ql/src/Security/CWE-020/ExternalAPITaintStepExample.js new file mode 100644 index 000000000000..8e0875439d14 --- /dev/null +++ b/javascript/ql/src/Security/CWE-020/ExternalAPITaintStepExample.js @@ -0,0 +1,6 @@ +let path = require('path'); + +express().get('/data', (req, res) => { + let file = path.join(HOME_DIR, 'public', req.query.file); + res.sendFile(file); +}); diff --git a/javascript/ql/src/Security/CWE-020/ExternalAPIsUsedWithUntrustedData.qhelp b/javascript/ql/src/Security/CWE-020/ExternalAPIsUsedWithUntrustedData.qhelp new file mode 100644 index 000000000000..2d446c7f3442 --- /dev/null +++ b/javascript/ql/src/Security/CWE-020/ExternalAPIsUsedWithUntrustedData.qhelp @@ -0,0 +1,48 @@ + + + +

Using unsanitized untrusted data in an external API can cause a variety of security issues. This query reports +all external APIs that are used with untrusted data, along with how frequently the API is used, and how many +unique sources of untrusted data flow to this API. This query is designed primarily to help identify which APIs +may be relevant for security analysis of this application.

+ +

An external API is defined as a call to a function that is not defined in the source code, not overridden +in the source code, and is not modeled as a taint step in the default taint library. External APIs may be from the +third party dependencies or from internal dependencies. The query will report the external package name, followed +by an access path leading to the function, followed by [param x] where x +indicates the position of the parameter receiving the untrusted data.

+ +
+ + +

For each result:

+ + + +

Otherwise, the result is likely uninteresting. Custom versions of this query can extend the SafeExternalAPIMethod +class to exclude known safe external APIs from future analysis.

+ +
+ + +

If the query were to return the API express().get.[callback].[param 'res'].send() [param 0], +this could correspond to the X in express().get('/foo', (req, res) => res.send(X)). +First we should consider whether this a security relevant sink. In this case, this is writing to a HTTP response, so we should +consider whether this is an XSS sink. If it is, we should confirm that it is handled by the reflected XSS query.

+ +

If the query were to return the API url.parse java.lang.StringBuilder.append(java.lang.String) [param 0], then this should be +reviewed as a possible taint step, because tainted data would flow from the 0th argument to the qualifier of the call.

+ +

Note that both examples are correctly handled by the standard taint tracking library and XSS query.

+
+ + + +
diff --git a/javascript/ql/src/Security/CWE-020/ExternalAPIsUsedWithUntrustedData.ql b/javascript/ql/src/Security/CWE-020/ExternalAPIsUsedWithUntrustedData.ql new file mode 100644 index 000000000000..6f087448cfe2 --- /dev/null +++ b/javascript/ql/src/Security/CWE-020/ExternalAPIsUsedWithUntrustedData.ql @@ -0,0 +1,17 @@ +/** + * @name Frequency counts for external APIs that are used with untrusted data + * @description This reports the external APIs that are used with untrusted data, along with how + * frequently the API is called, and how many unique sources of untrusted data flow + * to it. + * @id js/count-untrusted-data-external-api + * @kind table + * @tags security external/cwe/cwe-20 + */ + +import javascript +import semmle.javascript.security.dataflow.ExternalAPIUsedWithUntrustedData::ExternalAPIUsedWithUntrustedData + +from ExternalAPIUsedWithUntrustedData externalAPI +select externalAPI, count(externalAPI.getUntrustedDataNode()) as numberOfUses, + externalAPI.getNumberOfUntrustedSources() as numberOfUntrustedSources order by + numberOfUntrustedSources desc diff --git a/javascript/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.qhelp b/javascript/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.qhelp new file mode 100644 index 000000000000..226eb9ac77fb --- /dev/null +++ b/javascript/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.qhelp @@ -0,0 +1,57 @@ + + + +

Using unsanitized untrusted data in an external API can cause a variety of security issues. This query reports +external APIs that use untrusted data. The results are not filtered so that you can audit all examples. The query provides data for security reviews of the application and you can also use it to identify external APIs that should be modeled as either taint steps, or sinks for specific problems.

+ +

An external API is defined as a method call to a method that is not defined in the source code, not overridden +in the source code, and is not modeled as a taint step in the default taint library. External APIs may be from the +third-party dependencies or from internal dependencies. The query reports uses of +untrusted data one of the arguments of external API call or in the return value from a callback passed to an external API.

+ +
+ + +

For each result:

+ + + +

Otherwise, the result is likely uninteresting. Custom versions of this query can extend the SafeExternalAPIMethod +class to exclude known safe external APIs from future analysis.

+ +
+ + +

In this first example, a query parameter is read from the req parameter and then ultimately used in a call to the +res.send external API:

+ + + +

This is a reflected XSS sink. The XSS query should therefore be reviewed to confirm that this sink is appropriately modeled, +and if it is, to confirm that the query reports this particular result, or that the result is a false positive due to +some existing sanitization.

+ +

In this second example, again a query parameter is read from req.

+ + + +

If the query reported the call to path.join on line 4, this would suggest that this external API is +not currently modeled as a taint step in the taint tracking library. The next step would be to model this as a taint step, then +re-run the query to determine what additional results might be found. In this example, it seems the result of the +path.join will be used as a file path, leading to a path traversal vulnerability.

+ +

Note that both examples are correctly handled by the standard taint tracking library and security queries.

+
+ + + +
diff --git a/javascript/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql b/javascript/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql new file mode 100644 index 000000000000..a35c2e57f229 --- /dev/null +++ b/javascript/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql @@ -0,0 +1,19 @@ +/** + * @name Untrusted data passed to external API + * @description Data provided remotely is used in this external API without sanitization, which could be a security risk. + * @id js/untrusted-data-to-external-api + * @kind path-problem + * @precision low + * @problem.severity error + * @tags security external/cwe/cwe-20 + */ + +import javascript +import semmle.javascript.security.dataflow.ExternalAPIUsedWithUntrustedData::ExternalAPIUsedWithUntrustedData +import DataFlow::PathGraph + +from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink +where config.hasFlowPath(source, sink) +select sink, source, sink, + "Call to " + sink.getNode().(Sink).getApiName() + " with untrusted data from $@.", source, + source.toString() diff --git a/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll index d995caabf2b2..d7edc446f7ee 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll @@ -519,6 +519,11 @@ class FunctionNode extends DataFlow::ValueNode, DataFlow::SourceNode { */ class ObjectLiteralNode extends DataFlow::ValueNode, DataFlow::SourceNode { override ObjectExpr astNode; + + /** Gets the value of a spread property of this object literal, such as `x` in `{...x}` */ + DataFlow::Node getASpreadProperty() { + result = astNode.getAProperty().(SpreadProperty).getInit().(SpreadElement).getOperand().flow() + } } /** diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/ExternalAPIUsedWithUntrustedData.qll b/javascript/ql/src/semmle/javascript/security/dataflow/ExternalAPIUsedWithUntrustedData.qll new file mode 100644 index 000000000000..939d111f374b --- /dev/null +++ b/javascript/ql/src/semmle/javascript/security/dataflow/ExternalAPIUsedWithUntrustedData.qll @@ -0,0 +1,106 @@ +/** + * Provides a taint tracking configuration for reasoning about untrusted + * data flowing to an external API call. + * + * Note, for performance reasons: only import this file if + * `ExternalAPIUsedWithUntrustedData::Configuration` is needed, otherwise + * `ExternalAPIUsedWithUntrustedDataCustomizations` should be imported instead. + */ + +import javascript + +/** + * Provides a taint tracking configuration for reasoning about untrusted + * data flowing to an external API call. + */ +module ExternalAPIUsedWithUntrustedData { + import ExternalAPIUsedWithUntrustedDataCustomizations::ExternalAPIUsedWithUntrustedData + + /** Flow label for objects from which a tainted value is reachable. */ + private class ObjectWrapperFlowLabel extends DataFlow::FlowLabel { + ObjectWrapperFlowLabel() { this = "object-wrapper" } + } + + /** + * A taint tracking configuration for untrusted data flowing to an external API. + */ + class Configuration extends TaintTracking::Configuration { + Configuration() { this = "ExternalAPIUsedWithUntrustedData" } + + override predicate isSource(DataFlow::Node source) { source instanceof Source } + + override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel lbl) { + sink instanceof Sink and + (lbl.isTaint() or lbl instanceof ObjectWrapperFlowLabel) + } + + override predicate isSanitizer(DataFlow::Node node) { + super.isSanitizer(node) or + node instanceof Sanitizer + } + + override predicate isAdditionalFlowStep( + DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predLbl, + DataFlow::FlowLabel succLbl + ) { + // Step into an object and switch to the 'object-wrapper' label. + exists(DataFlow::PropWrite write | + pred = write.getRhs() and + succ = write.getBase().getALocalSource() and + (predLbl.isTaint() or predLbl instanceof ObjectWrapperFlowLabel) and + succLbl instanceof ObjectWrapperFlowLabel + ) + } + + override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { + // Block flow from the location to its properties, as the relevant properties (hash and search) are taint sources of their own. + // The location source is only used for propagating through API calls like `new URL(location)` and into external APIs where + // the whole location object escapes. + exists(DataFlow::PropRead read | + read = DOM::locationRef().getAPropertyRead() and + pred = read.getBase() and + succ = read + ) + } + } + + /** A node representing data being passed to an external API. */ + class ExternalAPIDataNode extends DataFlow::Node { + ExternalAPIDataNode() { this instanceof Sink } + } + + /** A node representing untrusted data being passed to an external API. */ + class UntrustedExternalAPIDataNode extends ExternalAPIDataNode { + UntrustedExternalAPIDataNode() { any(Configuration c).hasFlow(_, this) } + + /** Gets a source of untrusted data which is passed to this external API data node. */ + DataFlow::Node getAnUntrustedSource() { any(Configuration c).hasFlow(result, this) } + } + + /** + * Name of an external API sink, boxed in a newtype for consistency with other languages. + */ + private newtype TExternalApi = + MkExternalApiNode(string name) { + exists(Sink sink | + any(Configuration c).hasFlow(_, sink) and + name = sink.getApiName() + ) + } + + /** An external API which is used with untrusted data. */ + class ExternalAPIUsedWithUntrustedData extends TExternalApi { + /** Gets a possibly untrusted use of this external API. */ + UntrustedExternalAPIDataNode getUntrustedDataNode() { + this = MkExternalApiNode(result.(Sink).getApiName()) + } + + /** Gets the number of untrusted sources used with this external API. */ + int getNumberOfUntrustedSources() { + result = count(getUntrustedDataNode().getAnUntrustedSource()) + } + + /** Gets a textual representation of this element. */ + string toString() { this = MkExternalApiNode(result) } + } +} diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/ExternalAPIUsedWithUntrustedDataCustomizations.qll b/javascript/ql/src/semmle/javascript/security/dataflow/ExternalAPIUsedWithUntrustedDataCustomizations.qll new file mode 100644 index 000000000000..c1933beed7b1 --- /dev/null +++ b/javascript/ql/src/semmle/javascript/security/dataflow/ExternalAPIUsedWithUntrustedDataCustomizations.qll @@ -0,0 +1,383 @@ +/** + * Provides sources, sinks and sanitizers for reasoning about flow of + * untrusted data into an external API. + */ + +import javascript + +/** + * Provides sources, sinks and sanitizers for reasoning about flow of + * untrusted data into an external API. + */ +module ExternalAPIUsedWithUntrustedData { + /** + * A source of untrusted data. + */ + abstract class Source extends DataFlow::Node { } + + /** + * An input to an external API call. + */ + abstract class Sink extends DataFlow::Node { + /** + * Gets a human-readable name for the external API which this value flows into. + * + * This has the form of a pseudo-access path leading to the sink. Some ambiguity + * is tolerated in exchange for better readability here, as the user will typically + * have to scan over many irrelevant sinks in order to pick out the interesting ones. + */ + abstract string getApiName(); + } + + /** + * A value that is treated as a generic deep object sink. + * + * By default, this includes the objects passed to a `PropertyProjection` or `ExtendCall`. + * + * Such objects tend to have lots of application-defined properties which don't represent + * distinct API usages, so the query will avoid generating API names from them. + */ + abstract class DeepObjectSink extends DataFlow::Node { } + + private class DefaultDeepObjectSink extends DeepObjectSink { + DefaultDeepObjectSink() { + this = any(PropertyProjection p).getObject() + or + this = any(ExtendCall c).getAnOperand() + } + } + + /** Holds if `node` corresponds to a deep object argument. */ + private predicate isDeepObjectSink(API::Node node) { node.getARhs() instanceof DeepObjectSink } + + /** + * A sanitizer for data flowing to an external API. + */ + abstract class Sanitizer extends DataFlow::Node { } + + private class RemoteFlowAsSource extends Source { + RemoteFlowAsSource() { this instanceof RemoteFlowSource } + } + + private class LocationSource extends Source { + LocationSource() { + this = DOM::locationRef().getAPropertyRead(["hash", "search"]) + or + this = DOM::locationSource() + } + } + + /** + * A package name whose entire API is considered "safe" for the purpose of this query. + */ + abstract class SafeExternalAPIPackage extends string { + SafeExternalAPIPackage() { exists(API::moduleImport(this)) } + } + + private class DefaultSafeExternalAPIPackage extends SafeExternalAPIPackage { + DefaultSafeExternalAPIPackage() { + // Promise libraries are safe and generate too much noise if included + this = + [ + "bluebird", "q", "deferred", "when", "promise", "promises", "es6-promise", + "promise-polyfill" + ] + } + } + + /** + * A function that is considered a "safe" external API from a security perspective. + */ + abstract class SafeExternalAPIFunction extends API::Node { } + + /** Holds if data read from a use of `f` may originate from an imported package. */ + private predicate mayComeFromLibrary(API::Node f) { + // base case: import + exists(string path | + f = API::moduleImport(path) and + not path instanceof SafeExternalAPIPackage and + // Exclude paths that can be resolved to a file in the project + not exists(Import imprt | + imprt.getImportedPath().getValue() = path and exists(imprt.getImportedModule()) + ) + ) + or + // covariant recursive cases: instances, members, results, and promise contents + // of something that comes from a library may themselves come from that library + exists(API::Node base | mayComeFromLibrary(base) | + f = base.getInstance() or + f = base.getAMember() or + f = base.getReturn() or + f = base.getPromised() + ) + or + // contravariant recursive case: parameters of something that escapes to a library + // may come from that library + exists(API::Node base | mayEscapeToLibrary(base) | f = base.getAParameter()) + } + + /** + * Holds if data written to a definition of `f` may flow to an imported package. + */ + private predicate mayEscapeToLibrary(API::Node f) { + // covariant recursive case: members, results, and promise contents of something that + // escapes to a library may themselves escape to that library + exists(API::Node base | mayEscapeToLibrary(base) and not isDeepObjectSink(base) | + f = base.getAMember() or + f = base.getPromised() or + f = base.getReturn() + ) + or + // contravariant recursive case: arguments (other than the receiver) passed to a function + // that comes from a library may escape to that library + exists(API::Node base | mayComeFromLibrary(base) | + f = base.getAParameter() and not f = base.getReceiver() + ) + } + + /** + * Holds if `node` may be part of an access path leading to an external API call. + */ + private predicate nodeIsRelevant(API::Node node) { + mayComeFromLibrary(node) and + not node instanceof SafeExternalAPIFunction + or + nodeIsRelevant(node.getASuccessor()) and + not node = API::moduleImport(any(SafeExternalAPIPackage p)) + } + + /** Holds if the edge `pred -> succ` may lead to an external API call. */ + private predicate edge(API::Node pred, API::Node succ) { + nodeIsRelevant(succ) and + pred.getASuccessor() = succ + } + + /** + * Gets the depth of `node` from the API graph root, not including paths that go through + * irrelevant nodes, such as a package marked as safe. + */ + private int getDepth(API::Node node) = shortestDistances(API::root/0, edge/2)(_, node, result) + + /** + * Gets a parameter of `base` with name `name`, or a property named `name` of a destructuring parameter. + */ + private API::Node getNamedParameter(API::Node base, string name) { + exists(API::Node param | + param = base.getAParameter() and + not param = base.getReceiver() + | + result = param and + name = param.getAnImmediateUse().asExpr().(Parameter).getName() + or + param.getAnImmediateUse().asExpr() instanceof DestructuringPattern and + result = param.getMember(name) + ) + } + + /** + * Gets a simplified name for the access path leading to `node`. + */ + private string getSimplifiedName(API::Node node) { + node = API::moduleImport(result) + or + exists(API::Node base, string basename | + getDepth(base) < getDepth(node) and basename = getSimplifiedName(base) + | + // In practice there is no need to distinguish between 'new X' and 'X()' + node = [base.getInstance(), base.getReturn()] and + result = basename + "()" + or + exists(string member | + node = base.getMember(member) and + not node = base.getUnknownMember() and + not isNumericString(member) and + not (member = "default" and base = API::moduleImport(_)) and + not member = "then" // use the 'promised' edges for .then callbacks + | + if member.regexpMatch("[a-zA-Z_$]\\w*") + then result = basename + "." + member + else result = basename + "['" + member.regexpReplaceAll("'", "\\'") + "']" + ) + or + ( + node = base.getUnknownMember() or + node = base.getMember(any(string s | isNumericString(s))) + ) and + result = basename + "[]" + or + // just collapse promises + node = base.getPromised() and + result = basename + or + // Name callback parameters after their name in the source code. + // For example, the 'res' parameter in, + // + // express.get('/foo', (req, res) => {...})` + // + // will be named `express().get.[callback].[param 'res']` + exists(string paramName | + node = getNamedParameter(base.getAParameter(), paramName) and + result = basename + ".[callback].[param '" + paramName + "']" + or + exists(string callbackName, string index | + node = + getNamedParameter(base.getASuccessor("parameter " + index).getMember(callbackName), + paramName) and + index != "-1" and // ignore receiver + result = + basename + ".[callback " + index + " '" + callbackName + "'].[param '" + paramName + + "']" + ) + ) + ) + } + + bindingset[str] + private predicate isNumericString(string str) { exists(str.toInt()) } + + /** + * Holds if `name` is the name of a built-in method on Object, Array, or String that + * takes one or more arguments (methods not taking arguments are unlikely to be called + * by a call that actually has arguments, so they are excluded). + */ + private predicate isCommonBuiltinMethodName(string name) { + exists(ExternalInstanceMemberDecl member | + member.getBaseName() in ["Object", "Array", "String"] and + name = member.getName() and + member.getInit().(Function).getNumParameter() > 0 + ) + } + + /** + * A call to an external API. + */ + private class ExternalApiInvocation extends DataFlow::InvokeNode { + API::Node callee; + + ExternalApiInvocation() { + mayComeFromLibrary(callee) and + this = callee.getAnInvocation() and + // Ignore arguments to a method such as 'indexOf' that's likely called on a string or array value + not isCommonBuiltinMethodName(this.(DataFlow::CallNode).getCalleeName()) and + // Not already modeled as a flow/taint step + not exists(DataFlow::Node arg | + arg = this.getAnArgument() and not arg instanceof DeepObjectSink + | + any(TaintTracking::AdditionalTaintStep s).step(arg, _) + or + exists(DataFlow::AdditionalFlowStep s | + s.step(arg, _) or + s.step(arg, _, _, _) or + s.loadStep(arg, _, _) or + s.storeStep(arg, _, _) or + s.loadStoreStep(arg, _, _) + ) + ) + } + + /** Gets the API name representing this call. */ + string getApiName() { result = getSimplifiedName(callee) + "()" } + } + + /** + * Holds if `object` can be seen as a record of named arguments to a call. + * + * This holds for all object literals except deep object sinks. + */ + private predicate isNamedArgumentObject(DataFlow::ObjectLiteralNode object) { + not object instanceof DeepObjectSink + } + + /** An argument to an external API call, seen as a sink. */ + private class DirectParameterSink extends Sink { + ExternalApiInvocation invoke; + int index; + + DirectParameterSink() { + this = invoke.getArgument(index) and + not isNamedArgumentObject(this) // handled by NamedParameterSink + or + this = invoke.getArgument(index).(DataFlow::ObjectLiteralNode).getASpreadProperty() + } + + override string getApiName() { result = invoke.getApiName() + " [param " + index + "]" } + } + + /** A spread argument or an unknown-index argument to an external API. */ + private class SpreadParameterSink extends Sink { + ExternalApiInvocation invoke; + + SpreadParameterSink() { + this = invoke.getASpreadArgument() + or + exists(InvokeExpr expr, int i | expr = invoke.asExpr() | + this = expr.getArgument(i).flow() and + expr.getArgument([0 .. i - 1]) instanceof SpreadElement + ) + } + + override string getApiName() { result = invoke.getApiName() + " [param *]" } + } + + /** A "named argument" to an external API call, seen as a sink. */ + private class NamedParameterSink extends Sink { + ExternalApiInvocation invoke; + int index; + string prop; + + NamedParameterSink() { + exists(DataFlow::ObjectLiteralNode object, DataFlow::PropWrite write | + object = invoke.getArgument(index) and + isNamedArgumentObject(object) and + write = object.getAPropertyWrite() and + this = write.getRhs() and + ( + prop = write.getPropertyName() + or + not exists(write.getPropertyName()) and + prop = "*" + ) + ) + } + + override string getApiName() { + result = invoke.getApiName() + " [param " + index + " '" + prop + "']" + } + } + + /** The return value from a direct callback to an external API call, seen as a sink */ + private class CallbackSink extends Sink { + ExternalApiInvocation invoke; + int index; + + CallbackSink() { + this = invoke.getCallback(index).getAReturn() and + // Exclude promise-related method names for callback-return sinks + not invoke.getCalleeName() = ["then", "catch", "finally"] + } + + override string getApiName() { + result = invoke.getApiName() + " [callback " + index + " result]" + } + } + + /** The return value from a named callback to an external API call, seen as a sink. */ + private class NamedCallbackSink extends Sink { + ExternalApiInvocation invoke; + int index; + string prop; + + NamedCallbackSink() { + this = + invoke + .getOptionArgument(index, prop) + .getALocalSource() + .(DataFlow::FunctionNode) + .getAReturn() + } + + override string getApiName() { + result = invoke.getApiName() + " [callback " + index + " '" + prop + "' result]" + } + } +} diff --git a/javascript/ql/test/query-tests/Security/CWE-020/UntrustedDataToExternalAPI.expected b/javascript/ql/test/query-tests/Security/CWE-020/UntrustedDataToExternalAPI.expected new file mode 100644 index 000000000000..010d75077cf2 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-020/UntrustedDataToExternalAPI.expected @@ -0,0 +1,104 @@ +nodes +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | +| tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | +| tst-UntrustedDataToExternalAPI.js:5:13:5:21 | untrusted | +| tst-UntrustedDataToExternalAPI.js:5:13:5:21 | untrusted | +| tst-UntrustedDataToExternalAPI.js:6:17:6:25 | untrusted | +| tst-UntrustedDataToExternalAPI.js:6:17:6:25 | untrusted | +| tst-UntrustedDataToExternalAPI.js:7:16:7:24 | untrusted | +| tst-UntrustedDataToExternalAPI.js:7:16:7:24 | untrusted | +| tst-UntrustedDataToExternalAPI.js:8:31:8:39 | untrusted | +| tst-UntrustedDataToExternalAPI.js:8:31:8:39 | untrusted | +| tst-UntrustedDataToExternalAPI.js:9:18:9:26 | untrusted | +| tst-UntrustedDataToExternalAPI.js:9:18:9:26 | untrusted | +| tst-UntrustedDataToExternalAPI.js:10:13:10:33 | ['x', u ... d, 'y'] | +| tst-UntrustedDataToExternalAPI.js:10:13:10:33 | ['x', u ... d, 'y'] | +| tst-UntrustedDataToExternalAPI.js:10:13:10:33 | ['x', u ... d, 'y'] | +| tst-UntrustedDataToExternalAPI.js:10:19:10:27 | untrusted | +| tst-UntrustedDataToExternalAPI.js:11:20:11:28 | untrusted | +| tst-UntrustedDataToExternalAPI.js:11:20:11:28 | untrusted | +| tst-UntrustedDataToExternalAPI.js:13:8:17:5 | {\\n ... }\\n } | +| tst-UntrustedDataToExternalAPI.js:13:8:17:5 | {\\n ... }\\n } | +| tst-UntrustedDataToExternalAPI.js:14:12:16:9 | {\\n ... } | +| tst-UntrustedDataToExternalAPI.js:15:16:15:24 | untrusted | +| tst-UntrustedDataToExternalAPI.js:21:12:27:5 | {\\n ... }\\n } | +| tst-UntrustedDataToExternalAPI.js:22:12:26:9 | {\\n ... } | +| tst-UntrustedDataToExternalAPI.js:23:16:25:13 | {\\n ... } | +| tst-UntrustedDataToExternalAPI.js:24:20:24:42 | [JSON.p ... usted)] | +| tst-UntrustedDataToExternalAPI.js:24:20:24:42 | [JSON.p ... usted)] | +| tst-UntrustedDataToExternalAPI.js:24:21:24:41 | JSON.pa ... rusted) | +| tst-UntrustedDataToExternalAPI.js:24:32:24:40 | untrusted | +| tst-UntrustedDataToExternalAPI.js:30:13:30:30 | getDeepUntrusted() | +| tst-UntrustedDataToExternalAPI.js:30:13:30:30 | getDeepUntrusted() | +| tst-UntrustedDataToExternalAPI.js:30:13:30:30 | getDeepUntrusted() | +| tst-UntrustedDataToExternalAPI.js:33:14:33:22 | untrusted | +| tst-UntrustedDataToExternalAPI.js:33:14:33:22 | untrusted | +| tst-UntrustedDataToExternalAPI.js:34:34:34:42 | untrusted | +| tst-UntrustedDataToExternalAPI.js:34:34:34:42 | untrusted | +| tst-UntrustedDataToExternalAPI.js:41:11:45:1 | {\\n x ... usted\\n} | +| tst-UntrustedDataToExternalAPI.js:41:11:45:1 | {\\n x ... usted\\n} | +| tst-UntrustedDataToExternalAPI.js:42:8:42:16 | untrusted | +| tst-UntrustedDataToExternalAPI.js:43:8:43:16 | untrusted | +| tst-UntrustedDataToExternalAPI.js:44:8:44:16 | untrusted | +edges +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:5:13:5:21 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:5:13:5:21 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:6:17:6:25 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:6:17:6:25 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:7:16:7:24 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:7:16:7:24 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:8:31:8:39 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:8:31:8:39 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:9:18:9:26 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:9:18:9:26 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:10:19:10:27 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:11:20:11:28 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:11:20:11:28 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:15:16:15:24 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:24:32:24:40 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:30:13:30:30 | getDeepUntrusted() | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:30:13:30:30 | getDeepUntrusted() | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:33:14:33:22 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:33:14:33:22 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:34:34:34:42 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:34:34:34:42 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:42:8:42:16 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:43:8:43:16 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | tst-UntrustedDataToExternalAPI.js:44:8:44:16 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | +| tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | tst-UntrustedDataToExternalAPI.js:3:5:3:27 | untrusted | +| tst-UntrustedDataToExternalAPI.js:10:19:10:27 | untrusted | tst-UntrustedDataToExternalAPI.js:10:13:10:33 | ['x', u ... d, 'y'] | +| tst-UntrustedDataToExternalAPI.js:10:19:10:27 | untrusted | tst-UntrustedDataToExternalAPI.js:10:13:10:33 | ['x', u ... d, 'y'] | +| tst-UntrustedDataToExternalAPI.js:10:19:10:27 | untrusted | tst-UntrustedDataToExternalAPI.js:10:13:10:33 | ['x', u ... d, 'y'] | +| tst-UntrustedDataToExternalAPI.js:14:12:16:9 | {\\n ... } | tst-UntrustedDataToExternalAPI.js:13:8:17:5 | {\\n ... }\\n } | +| tst-UntrustedDataToExternalAPI.js:14:12:16:9 | {\\n ... } | tst-UntrustedDataToExternalAPI.js:13:8:17:5 | {\\n ... }\\n } | +| tst-UntrustedDataToExternalAPI.js:15:16:15:24 | untrusted | tst-UntrustedDataToExternalAPI.js:14:12:16:9 | {\\n ... } | +| tst-UntrustedDataToExternalAPI.js:21:12:27:5 | {\\n ... }\\n } | tst-UntrustedDataToExternalAPI.js:30:13:30:30 | getDeepUntrusted() | +| tst-UntrustedDataToExternalAPI.js:21:12:27:5 | {\\n ... }\\n } | tst-UntrustedDataToExternalAPI.js:30:13:30:30 | getDeepUntrusted() | +| tst-UntrustedDataToExternalAPI.js:22:12:26:9 | {\\n ... } | tst-UntrustedDataToExternalAPI.js:21:12:27:5 | {\\n ... }\\n } | +| tst-UntrustedDataToExternalAPI.js:23:16:25:13 | {\\n ... } | tst-UntrustedDataToExternalAPI.js:22:12:26:9 | {\\n ... } | +| tst-UntrustedDataToExternalAPI.js:24:20:24:42 | [JSON.p ... usted)] | tst-UntrustedDataToExternalAPI.js:23:16:25:13 | {\\n ... } | +| tst-UntrustedDataToExternalAPI.js:24:20:24:42 | [JSON.p ... usted)] | tst-UntrustedDataToExternalAPI.js:23:16:25:13 | {\\n ... } | +| tst-UntrustedDataToExternalAPI.js:24:21:24:41 | JSON.pa ... rusted) | tst-UntrustedDataToExternalAPI.js:24:20:24:42 | [JSON.p ... usted)] | +| tst-UntrustedDataToExternalAPI.js:24:21:24:41 | JSON.pa ... rusted) | tst-UntrustedDataToExternalAPI.js:24:20:24:42 | [JSON.p ... usted)] | +| tst-UntrustedDataToExternalAPI.js:24:32:24:40 | untrusted | tst-UntrustedDataToExternalAPI.js:24:21:24:41 | JSON.pa ... rusted) | +| tst-UntrustedDataToExternalAPI.js:42:8:42:16 | untrusted | tst-UntrustedDataToExternalAPI.js:41:11:45:1 | {\\n x ... usted\\n} | +| tst-UntrustedDataToExternalAPI.js:42:8:42:16 | untrusted | tst-UntrustedDataToExternalAPI.js:41:11:45:1 | {\\n x ... usted\\n} | +| tst-UntrustedDataToExternalAPI.js:43:8:43:16 | untrusted | tst-UntrustedDataToExternalAPI.js:41:11:45:1 | {\\n x ... usted\\n} | +| tst-UntrustedDataToExternalAPI.js:43:8:43:16 | untrusted | tst-UntrustedDataToExternalAPI.js:41:11:45:1 | {\\n x ... usted\\n} | +| tst-UntrustedDataToExternalAPI.js:44:8:44:16 | untrusted | tst-UntrustedDataToExternalAPI.js:41:11:45:1 | {\\n x ... usted\\n} | +| tst-UntrustedDataToExternalAPI.js:44:8:44:16 | untrusted | tst-UntrustedDataToExternalAPI.js:41:11:45:1 | {\\n x ... usted\\n} | +#select +| tst-UntrustedDataToExternalAPI.js:5:13:5:21 | untrusted | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | tst-UntrustedDataToExternalAPI.js:5:13:5:21 | untrusted | Call to external-lib() [param 0] with untrusted data from $@. | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | window.name | +| tst-UntrustedDataToExternalAPI.js:6:17:6:25 | untrusted | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | tst-UntrustedDataToExternalAPI.js:6:17:6:25 | untrusted | Call to external-lib() [param 0 'x'] with untrusted data from $@. | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | window.name | +| tst-UntrustedDataToExternalAPI.js:7:16:7:24 | untrusted | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | tst-UntrustedDataToExternalAPI.js:7:16:7:24 | untrusted | Call to external-lib() [param *] with untrusted data from $@. | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | window.name | +| tst-UntrustedDataToExternalAPI.js:8:31:8:39 | untrusted | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | tst-UntrustedDataToExternalAPI.js:8:31:8:39 | untrusted | Call to external-lib() [param *] with untrusted data from $@. | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | window.name | +| tst-UntrustedDataToExternalAPI.js:9:18:9:26 | untrusted | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | tst-UntrustedDataToExternalAPI.js:9:18:9:26 | untrusted | Call to external-lib() [param 0] with untrusted data from $@. | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | window.name | +| tst-UntrustedDataToExternalAPI.js:10:13:10:33 | ['x', u ... d, 'y'] | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | tst-UntrustedDataToExternalAPI.js:10:13:10:33 | ['x', u ... d, 'y'] | Call to external-lib() [param 0] with untrusted data from $@. | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | window.name | +| tst-UntrustedDataToExternalAPI.js:11:20:11:28 | untrusted | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | tst-UntrustedDataToExternalAPI.js:11:20:11:28 | untrusted | Call to external-lib() [param 1] with untrusted data from $@. | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | window.name | +| tst-UntrustedDataToExternalAPI.js:13:8:17:5 | {\\n ... }\\n } | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | tst-UntrustedDataToExternalAPI.js:13:8:17:5 | {\\n ... }\\n } | Call to external-lib() [param 0 'x'] with untrusted data from $@. | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | window.name | +| tst-UntrustedDataToExternalAPI.js:30:13:30:30 | getDeepUntrusted() | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | tst-UntrustedDataToExternalAPI.js:30:13:30:30 | getDeepUntrusted() | Call to external-lib() [param 0] with untrusted data from $@. | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | window.name | +| tst-UntrustedDataToExternalAPI.js:33:14:33:22 | untrusted | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | tst-UntrustedDataToExternalAPI.js:33:14:33:22 | untrusted | Call to external-lib.get.[callback].[param 'res'].send() [param 0] with untrusted data from $@. | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | window.name | +| tst-UntrustedDataToExternalAPI.js:34:34:34:42 | untrusted | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | tst-UntrustedDataToExternalAPI.js:34:34:34:42 | untrusted | Call to external-lib.get.[callback].[param 'req'].app.locals.something.foo() [param 0] with untrusted data from $@. | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | window.name | +| tst-UntrustedDataToExternalAPI.js:41:11:45:1 | {\\n x ... usted\\n} | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | tst-UntrustedDataToExternalAPI.js:41:11:45:1 | {\\n x ... usted\\n} | Call to lodash.merge() [param 1] with untrusted data from $@. | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | window.name | diff --git a/javascript/ql/test/query-tests/Security/CWE-020/UntrustedDataToExternalAPI.qlref b/javascript/ql/test/query-tests/Security/CWE-020/UntrustedDataToExternalAPI.qlref new file mode 100644 index 000000000000..7752378db17d --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-020/UntrustedDataToExternalAPI.qlref @@ -0,0 +1 @@ +Security/CWE-020/UntrustedDataToExternalAPI.ql diff --git a/javascript/ql/test/query-tests/Security/CWE-020/tst-UntrustedDataToExternalAPI.js b/javascript/ql/test/query-tests/Security/CWE-020/tst-UntrustedDataToExternalAPI.js new file mode 100644 index 000000000000..864b2b68a01c --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-020/tst-UntrustedDataToExternalAPI.js @@ -0,0 +1,45 @@ +let externalLib = require('external-lib'); + +let untrusted = window.name; + +externalLib(untrusted); +externalLib({x: untrusted}); +externalLib(...untrusted); +externalLib(...window.CONFIG, untrusted); +externalLib({ ...untrusted }); +externalLib(['x', untrusted, 'y']); +externalLib('foo', untrusted); +externalLib({ + x: { + y: { + z: untrusted + } + } +}); + +function getDeepUntrusted() { + return { + x: { + y: { + z: [JSON.parse(untrusted)] + } + } + } +} + +externalLib(getDeepUntrusted()); + +externalLib.get('/foo', (req, res) => { + res.send(untrusted); + req.app.locals.something.foo(untrusted); +}); + +let jsonSafeParse = require('json-safe-parse'); +jsonSafeParse(untrusted); // no need to report; has known taint step + +let merge = require('lodash.merge'); +merge({}, { + x: untrusted, // should not be treated as individual named parameters + y: untrusted, + z: untrusted +});