Merge pull request #4694 from asgerf/js/flow-to-external-api

JS: Add UntrustedDataToExternalAPI query
This commit is contained in:
Asger F
2020-11-20 15:56:04 +00:00
committed by GitHub
12 changed files with 795 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
express().get('/news', (req, res) => {
let topic = req.query.topic;
res.send(`<h1>${topic}</h1>`);
});

View File

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

View File

@@ -0,0 +1,48 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>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.</p>
<p>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 <code>[param x]</code> where <code>x</code>
indicates the position of the parameter receiving the untrusted data.</p>
</overview>
<recommendation>
<p>For each result:</p>
<ul>
<li>If the result highlights a known sink, no action is required.</li>
<li>If the result highlights an unknown sink for a problem, then add modeling for the sink to the relevant query.</li>
<li>If the result represents a call to an external API which transfers taint, add the appropriate modeling, and
re-run the query to determine what new results have appeared due to this additional modeling.</li>
</ul>
<p>Otherwise, the result is likely uninteresting. Custom versions of this query can extend the <code>SafeExternalAPIMethod</code>
class to exclude known safe external APIs from future analysis.</p>
</recommendation>
<example>
<p>If the query were to return the API <code>express().get.[callback].[param 'res'].send() [param 0]</code>,
this could correspond to the <code>X</code> in <code>express().get('/foo', (req, res) => res.send(X))</code>.
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.</p>
<p>If the query were to return the API <code>url.parse java.lang.StringBuilder.append(java.lang.String) [param 0]</code>, 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.</p>
<p>Note that both examples are correctly handled by the standard taint tracking library and XSS query.</p>
</example>
<references>
</references>
</qhelp>

View File

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

View File

@@ -0,0 +1,57 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>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.</p>
<p>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.</p>
</overview>
<recommendation>
<p>For each result:</p>
<ul>
<li>If the result highlights a known sink, confirm that the result is reported by the relevant query, or
that the result is a false positive because this data is sanitized.</li>
<li>If the result highlights an unknown sink for a problem, then add modeling for the sink to the relevant query,
and confirm that the result is either found, or is safe due to appropriate sanitization.</li>
<li>If the result represents a call to an external API that transfers taint, add the appropriate modeling, and
re-run the query to determine what new results have appeared due to this additional modeling.</li>
</ul>
<p>Otherwise, the result is likely uninteresting. Custom versions of this query can extend the <code>SafeExternalAPIMethod</code>
class to exclude known safe external APIs from future analysis.</p>
</recommendation>
<example>
<p>In this first example, a query parameter is read from the <code>req</code> parameter and then ultimately used in a call to the
<code>res.send</code> external API:</p>
<sample src="ExternalAPISinkExample.js" />
<p>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.</p>
<p>In this second example, again a query parameter is read from <code>req</code>.</p>
<sample src="ExternalAPITaintStepExample.js" />
<p>If the query reported the call to <code>path.join</code> 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
<code>path.join</code> will be used as a file path, leading to a path traversal vulnerability.</p>
<p>Note that both examples are correctly handled by the standard taint tracking library and security queries.</p>
</example>
<references>
</references>
</qhelp>

View File

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

View File

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

View File

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

View File

@@ -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]"
}
}
}

View File

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

View File

@@ -0,0 +1 @@
Security/CWE-020/UntrustedDataToExternalAPI.ql

View File

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