Merge remote-tracking branch 'upstream/master' into loginjection

This commit is contained in:
ubuntu
2020-06-24 19:15:27 +02:00
326 changed files with 12406 additions and 7552 deletions

View File

@@ -7,7 +7,7 @@
* @id js/node/unused-npm-dependency
* @tags maintainability
* frameworks/node.js
* @precision medium
* @precision low
*/
import javascript

View File

@@ -1,10 +1,10 @@
/**
* @name Cross-site scripting through exception
* @description Inserting data from an exception containing user
* input into the DOM may enable cross-site scripting.
* @name Exception text reinterpreted as HTML
* @description Reinterpreting text from an exception as HTML
* can lead to a cross-site scripting vulnerability.
* @kind path-problem
* @problem.severity error
* @precision medium
* @problem.severity warning
* @precision high
* @id js/xss-through-exception
* @tags security
* external/cwe/cwe-079
@@ -18,5 +18,5 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
sink.getNode().(Xss::Shared::Sink).getVulnerabilityKind() + " vulnerability due to $@.",
source.getNode(), "user-provided value"
"$@ is reinterpreted as HTML without escaping meta-characters.", source.getNode(),
"Exception text"

View File

@@ -1,10 +1,10 @@
/**
* @name Cross-site scripting through DOM
* @description Writing user-controlled DOM to HTML can allow for
* a cross-site scripting vulnerability.
* @name DOM text reinterpreted as HTML
* @description Reinterpreting text from the DOM as HTML
* can lead to a cross-site scripting vulnerability.
* @kind path-problem
* @problem.severity error
* @precision medium
* @problem.severity warning
* @precision high
* @id js/xss-through-dom
* @tags security
* external/cwe/cwe-079
@@ -17,5 +17,5 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to $@.",
source.getNode(), "DOM text"
select sink.getNode(), source, sink,
"$@ is reinterpreted as HTML without escaping meta-characters.", source.getNode(), "DOM text"

View File

@@ -0,0 +1,38 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Using string concatenation to construct JavaScript code can be error-prone, or in the worst
case, enable code injection if an input is constructed by an attacker.
</p>
</overview>
<recommendation>
<p>
If using <code>JSON.stringify</code> or a HTML sanitizer to sanitize a string inserted into
JavaScript code, then make sure to perform additional sanitization or remove potentially
dangerous characters.
</p>
</recommendation>
<example>
<p>
The example below constructs a function that assigns the number 42 to the property <code>key</code>
on an object <code>obj</code>. However, if <code>key</code> contains <code>&lt;/script&gt;</code>, then
the generated code will break out of a <code>&lt;/script&gt;</code> if inserted into a
<code>&lt;/script&gt;</code> tag.
</p>
<sample src="examples/ImproperCodeSanitization.js" />
<p>
The issue has been fixed by escaping potentially dangerous characters, as shown below.
</p>
<sample src="examples/ImproperCodeSanitizationFixed.js" />
</example>
<references>
<li>OWASP: <a href="https://www.owasp.org/index.php/Code_Injection">Code Injection</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,71 @@
/**
* @name Improper code sanitization
* @description Escaping code as HTML does not provide protection against code injection.
* @kind path-problem
* @problem.severity error
* @precision high
* @id js/bad-code-sanitization
* @tags security
* external/cwe/cwe-094
* external/cwe/cwe-079
* external/cwe/cwe-116
*/
import javascript
import semmle.javascript.security.dataflow.ImproperCodeSanitization::ImproperCodeSanitization
import DataFlow::PathGraph
private import semmle.javascript.heuristics.HeuristicSinks
private import semmle.javascript.security.dataflow.CodeInjectionCustomizations
/**
* Gets a type-tracked instance of `RemoteFlowSource` using type-tracker `t`.
*/
private DataFlow::Node remoteFlow(DataFlow::TypeTracker t) {
t.start() and
result instanceof RemoteFlowSource
or
exists(DataFlow::TypeTracker t2, DataFlow::Node prev | prev = remoteFlow(t2) |
t2 = t.smallstep(prev, result)
or
any(TaintTracking::AdditionalTaintStep dts).step(prev, result) and
t = t2
)
}
/**
* Gets a type-tracked reference to a `RemoteFlowSource`.
*/
private DataFlow::Node remoteFlow() { result = remoteFlow(DataFlow::TypeTracker::end()) }
/**
* Gets a type-back-tracked instance of a code injection sink using type-tracker `t`.
*/
private DataFlow::Node endsInCodeInjectionSink(DataFlow::TypeBackTracker t) {
t.start() and
(
result instanceof CodeInjection::Sink
or
result instanceof HeuristicCodeInjectionSink and
not result instanceof StringOps::ConcatenationRoot // the heuristic CodeInjection sink looks for string-concats, we are not interrested in those here.
)
or
exists(DataFlow::TypeBackTracker t2 | t = t2.smallstep(result, endsInCodeInjectionSink(t2)))
}
/**
* Gets a reference to to a data-flow node that ends in a code injection sink.
*/
private DataFlow::Node endsInCodeInjectionSink() {
result = endsInCodeInjectionSink(DataFlow::TypeBackTracker::end())
}
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where
cfg.hasFlowPath(source, sink) and
// Basic detection of duplicate results with `js/code-injection`.
not (
sink.getNode().(StringOps::ConcatenationLeaf).getRoot() = endsInCodeInjectionSink() and
remoteFlow() = source.getNode().(DataFlow::InvokeNode).getAnArgument()
)
select sink.getNode(), source, sink, "$@ flows to here and is used to construct code.",
source.getNode(), "Improperly sanitized value"

View File

@@ -0,0 +1,4 @@
function createObjectWrite() {
const assignment = `obj[${JSON.stringify(key)}]=42`;
return `(function(){${assignment}})` // NOT OK
}

View File

@@ -0,0 +1,23 @@
const charMap = {
'<': '\\u003C',
'>' : '\\u003E',
'/': '\\u002F',
'\\': '\\\\',
'\b': '\\b',
'\f': '\\f',
'\n': '\\n',
'\r': '\\r',
'\t': '\\t',
'\0': '\\0',
'\u2028': '\\u2028',
'\u2029': '\\u2029'
};
function escapeUnsafeChars(str) {
return str.replace(/[<>\b\f\n\r\t\0\u2028\u2029]/g, x => charMap[x])
}
function createObjectWrite() {
const assignment = `obj[${escapeUnsafeChars(JSON.stringify(key))}]=42`;
return `(function(){${assignment}})` // OK
}

View File

@@ -0,0 +1,8 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<include src="IncompleteSanitization.qhelp" />
</qhelp>

View File

@@ -0,0 +1,81 @@
/**
* @name Incomplete multi-character sanitization
* @description A sanitizer that removes a sequence of characters may reintroduce the dangerous sequence.
* @kind problem
* @problem.severity warning
* @precision high
* @id js/incomplete-multi-character-sanitization
* @tags correctness
* security
* external/cwe/cwe-116
* external/cwe/cwe-20
*/
import javascript
import semmle.javascript.security.IncompleteBlacklistSanitizer
predicate isDangerous(RegExpTerm t) {
// path traversals
t.getAMatchedString() = ["..", "/..", "../"]
or
exists(RegExpTerm start |
start = t.(RegExpSequence).getAChild() and
start.getConstantValue() = "." and
start.getSuccessor().getConstantValue() = "." and
not [start.getPredecessor(), start.getSuccessor().getSuccessor()].getConstantValue() = "."
)
or
// HTML comments
t.getAMatchedString() = "<!--"
or
// HTML scripts
t.getAMatchedString().regexpMatch("(?i)<script.*")
or
exists(RegExpSequence seq | seq = t |
t.getChild(0).getConstantValue() = "<" and
// the `cript|scrip` case has been observed in the wild, not sure what the goal of that pattern is...
t
.getChild(0)
.getSuccessor+()
.getAMatchedString()
.regexpMatch("(?i)iframe|script|cript|scrip|style")
)
or
// HTML attributes
exists(string dangerousPrefix | dangerousPrefix = ["ng-", "on"] |
t.getAMatchedString().regexpMatch("(i?)" + dangerousPrefix + "[a-z]+")
or
exists(RegExpTerm start, RegExpTerm event | start = t.getAChild() |
start.getConstantValue().regexpMatch("(?i)[^a-z]*" + dangerousPrefix) and
event = start.getSuccessor() and
exists(RegExpTerm quantified | quantified = event.(RegExpQuantifier).getChild(0) |
quantified
.(RegExpCharacterClass)
.getAChild()
.(RegExpCharacterRange)
.isRange(["a", "A"], ["z", "Z"]) or
[quantified, quantified.(RegExpRange).getAChild()].(RegExpCharacterClassEscape).getValue() =
"w"
)
)
)
}
from StringReplaceCall replace, RegExpTerm regexp, RegExpTerm dangerous
where
[replace.getRawReplacement(), replace.getCallback(1).getAReturn()].mayHaveStringValue("") and
replace.isGlobal() and
regexp = replace.getRegExp().getRoot() and
dangerous.getRootTerm() = regexp and
isDangerous(dangerous) and
// avoid anchored terms
not exists(RegExpAnchor a | a.getRootTerm() = regexp) and
// avoid flagging wrappers
not (
dangerous instanceof RegExpAlt or
dangerous instanceof RegExpGroup
) and
// don't flag replace operations in a loop
not replace.getReceiver().getALocalSource() = replace
select replace, "The replaced string may still contain a substring that starts matching at $@.",
dangerous, dangerous.toString()

View File

@@ -0,0 +1,40 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Libraries like <code>express</code> provide easy methods for serving entire
directories of static files from a web server.
However, using these can sometimes lead to accidental information exposure.
If for example the <code>node_modules</code> folder is served, then an attacker
can access the <code>_where</code> field from a <code>package.json</code> file,
which gives access to the absolute path of the file.
</p>
</overview>
<recommendation>
<p>
Limit which folders of static files are served from a web server.
</p>
</recommendation>
<example>
<p>
In the example below, all the files from the <code>node_modules</code> are served.
This allows clients to easily access all the files inside that folder,
which includes potentially private information inside <code>package.json</code> files.
</p>
<sample src="examples/PrivateFileExposure.js"/>
<p>
The issue has been fixed below by only serving specific folders within the
<code>node_modules</code> folder.
</p>
<sample src="examples/PrivateFileExposureFixed.js"/>
</example>
<references>
<li>OWASP: <a href="https://www.owasp.org/index.php/Top_10-2017_A3-Sensitive_Data_Exposure">Sensitive Data Exposure</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,129 @@
/**
* @name Exposure of private files
* @description Exposing a node_modules folder, or the project folder to the public, can cause exposure
* of private information.
* @kind problem
* @problem.severity warning
* @id js/exposure-of-private-files
* @tags security
* external/cwe/cwe-200
* @precision high
*/
import javascript
/**
* Holds if `folder` is a node_modules folder, and at most 1 subdirectory down.
*/
bindingset[folder]
predicate isNodeModuleFolder(string folder) {
folder.regexpMatch("(\\.?\\.?/)*node_modules(/|(/[a-zA-Z@_-]+/?))?")
}
/**
* Get a data-flow node that represents a path to the node_modules folder represented by the string-literal `path`.
*/
DataFlow::Node getANodeModulePath(string path) {
result.getStringValue() = path and
isNodeModuleFolder(path)
or
exists(DataFlow::CallNode resolve |
resolve = DataFlow::moduleMember("path", ["resolve", "join"]).getACall()
|
result = resolve and
resolve.getLastArgument() = getANodeModulePath(path)
)
or
exists(StringOps::ConcatenationRoot root | root = result |
root.getLastLeaf() = getANodeModulePath(path)
)
or
result.getAPredecessor() = getANodeModulePath(path) // local data-flow
or
exists(string base, string folder |
path = base + folder and
folder.regexpMatch("(/)?[a-zA-Z@_-]+/?") and
base.regexpMatch("(\\.?\\.?/)*node_modules(/)?") // node_modules, without any sub-folders.
|
exists(StringOps::ConcatenationRoot root | root = result |
root.getNumOperand() = 2 and
root.getFirstLeaf() = getANodeModulePath(base) and
root.getLastLeaf().getStringValue() = folder
)
or
exists(DataFlow::CallNode resolve |
resolve = DataFlow::moduleMember("path", ["resolve", "join"]).getACall()
|
result = resolve and
resolve.getNumArgument() = 2 and
resolve.getArgument(0) = getANodeModulePath(path) and
resolve.getArgument(1).mayHaveStringValue(folder)
)
)
}
/**
* Gets a folder that contains a `package.json` file.
*/
pragma[noinline]
Folder getAPackageJSONFolder() { result = any(PackageJSON json).getFile().getParentContainer() }
/**
* Gets a reference to `dirname`, the home folder, the current working folder, or the root folder.
* All of these might cause information to be leaked.
*
* For `dirname` that can happen if there is a `package.json` file in the same folder.
* It is assumed that the presence of a `package.json` file means that a `node_modules` folder can also exist.
*
* For the root/home/working folder, they contain so much information that they must leak information somehow (e.g. ssh keys in the `~/.ssh` folder).
*/
DataFlow::Node getALeakingFolder(string description) {
exists(ModuleScope ms | result.asExpr() = ms.getVariable("__dirname").getAnAccess()) and
result.getFile().getParentContainer() = getAPackageJSONFolder() and
description = "the folder " + result.getFile().getParentContainer().getRelativePath()
or
result = DataFlow::moduleImport("os").getAMemberCall("homedir") and
description = "the home folder "
or
result.mayHaveStringValue("/") and
description = "the root folder"
or
result.getStringValue() = [".", "./"] and
description = "the current working folder"
or
result.getAPredecessor() = getALeakingFolder(description)
or
exists(StringOps::ConcatenationRoot root | root = result |
root.getNumOperand() = 2 and
root.getOperand(0) = getALeakingFolder(description) and
root.getOperand(1).getStringValue() = "/"
)
}
/**
* Gets a data-flow node that represents a path to the private folder `path`.
*/
DataFlow::Node getAPrivateFolderPath(string description) {
exists(string path |
result = getANodeModulePath(path) and description = "the folder \"" + path + "\""
)
or
result = getALeakingFolder(description)
}
/**
* Gest a call that serves the folder `path` to the public.
*/
DataFlow::CallNode servesAPrivateFolder(string description) {
result = DataFlow::moduleMember(["express", "connect"], "static").getACall() and
result.getArgument(0) = getAPrivateFolderPath(description)
or
result = DataFlow::moduleImport("serve-static").getACall() and
result.getArgument(0) = getAPrivateFolderPath(description)
}
from Express::RouteSetup setup, string path
where
setup.isUseCall() and
setup.getArgument([0 .. 1]) = servesAPrivateFolder(path).getEnclosingExpr()
select setup, "Serves " + path + ", which can contain private information."

View File

@@ -0,0 +1,6 @@
var express = require('express');
var app = express();
app.use('/node_modules', express.static(path.resolve(__dirname, '../node_modules')));

View File

@@ -0,0 +1,7 @@
var express = require('express');
var app = express();
app.use("jquery", express.static('./node_modules/jquery/dist'));
app.use("bootstrap", express.static('./node_modules/bootstrap/dist'));

View File

@@ -0,0 +1,71 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Certificate validation is the standard authentication method of a
secure TLS connection. Without it, there is no guarantee about who the other party of a TLS connection is, making man-in-the-middle attacks more likely to occur
</p>
<p>
When testing software that uses TLS connections, it may be useful to
disable the certificate validation temporarily. But disabling it in
production environments is strongly discouraged, unless an alternative
method of authentication is used.
</p>
</overview>
<recommendation>
<p>
Do not disable certificate validation for TLS connections.
</p>
</recommendation>
<example>
<p>
The following example shows a HTTPS connection that
transfers confidential information to a remote server. But the
connection is not secure since the <code>rejectUnauthorized</code>
option of the connection is set to <code>false</code>. As a
consequence, anyone can impersonate the remote server, and receive the
confidential information.
</p>
<sample src="examples/DisablingCertificateValidation.js"/>
<p>
To make the connection secure, the
<code>rejectUnauthorized</code> option should have its default value,
or be explicitly set to <code>true</code>.
</p>
</example>
<references>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Transport_Layer_Security">Transport Layer Security (TLS)</a></li>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Man-in-the-middle_attack">Man-in-the-middle attack</a></li>
<li>Node.js: <a href="https://nodejs.org/api/tls.html">TLS (SSL)</a></li>
</references>
</qhelp>

View File

@@ -0,0 +1,44 @@
/**
* @name Disabling certificate validation
* @description Disabling cryptographic certificate validation can cause security vulnerabilities.
* @kind problem
* @problem.severity error
* @precision very-high
* @id js/disabling-certificate-validation
* @tags security
* external/cwe-295
*/
import javascript
/**
* Gets an options object for a TLS connection.
*/
DataFlow::ObjectLiteralNode tlsOptions() {
exists(DataFlow::InvokeNode invk | result.flowsTo(invk.getAnArgument()) |
invk instanceof NodeJSLib::NodeJSClientRequest
or
invk = DataFlow::moduleMember("https", "Agent").getAnInstantiation()
or
exists(DataFlow::NewNode new |
new = DataFlow::moduleMember("tls", "TLSSocket").getAnInstantiation()
|
invk = new or
invk = new.getAMethodCall("renegotiate")
)
or
invk = DataFlow::moduleMember("tls", ["connect", "createServer"]).getACall()
)
}
from DataFlow::PropWrite disable
where
exists(DataFlow::SourceNode env |
env = NodeJSLib::process().getAPropertyRead("env") and
disable = env.getAPropertyWrite("NODE_TLS_REJECT_UNAUTHORIZED") and
disable.getRhs().mayHaveStringValue("0")
)
or
disable = tlsOptions().getAPropertyWrite("rejectUnauthorized") and
disable.getRhs().(AnalyzedNode).getTheBooleanValue() = false
select disable, "Disabling certificate validation is strongly discouraged."

View File

@@ -0,0 +1,14 @@
let https = require("https");
https.request(
{
hostname: "secure.my-online-bank.com",
port: 443,
method: "POST",
path: "send-confidential-information",
rejectUnauthorized: false // BAD
},
response => {
// ... communicate with secure.my-online-bank.com
}
);

View File

@@ -0,0 +1,36 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Sensitive information included in a build artifact can allow an attacker to access
the sensitive information if the artifact is published.
</p>
</overview>
<recommendation>
<p>
Only store information that is meant to be publicly available in a build artifact.
</p>
</recommendation>
<example>
<p>
The following example creates a <code>webpack</code> configuration that inserts all environment
variables from the host into the build artifact:
</p>
<sample src="examples/build-leak.js"/>
<p>
The environment variables might include API keys or other sensitive information, and the build-system
should instead insert only the environment variables that are supposed to be public.
</p>
<p>
The issue has been fixed below, where only the <code>DEBUG</code> environment variable is inserted into the artifact.
</p>
<sample src="examples/build-leak-fixed.js"/>
</example>
<references>
<li>webpack: <a href="https://webpack.js.org/plugins/define-plugin/">DefinePlugin API</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,23 @@
/**
* @name Storage of sensitive information in build artifact
* @description Including sensitive information in a build artifact can
* expose it to an attacker.
* @kind path-problem
* @problem.severity error
* @precision high
* @id js/build-artifact-leak
* @tags security
* external/cwe/cwe-312
* external/cwe/cwe-315
* external/cwe/cwe-359
*/
import javascript
import semmle.javascript.security.dataflow.BuildArtifactLeak::BuildArtifactLeak
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"Sensitive data returned by $@ is stored in a build artifact here.", source.getNode(),
source.getNode().(CleartextLogging::Source).describe()

View File

@@ -0,0 +1,9 @@
const webpack = require("webpack");
module.exports = [{
plugins: [
new webpack.DefinePlugin({
'process.env': JSON.stringify({ DEBUG: process.env.DEBUG })
})
]
}];

View File

@@ -0,0 +1,9 @@
const webpack = require("webpack");
module.exports = [{
plugins: [
new webpack.DefinePlugin({
"process.env": JSON.stringify(process.env)
})
]
}];

View File

@@ -0,0 +1,65 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Generating secure random numbers can be an important part of creating a
secure software system. This can be done using APIs that create
cryptographically secure random numbers.
</p>
<p>
However, using some mathematical operations on these cryptographically
secure random numbers can create biased results, where some outcomes
are more likely than others.
Such biased results can make it easier for an attacker to guess the random
numbers, and thereby break the security of the software system.
</p>
</overview>
<recommendation>
<p>
Be very careful not to introduce bias when performing mathematical operations
on cryptographically secure random numbers.
</p>
<p>
If possible, avoid performing mathematical operations on cryptographically secure
random numbers at all, and use a preexisting library instead.
</p>
</recommendation>
<example>
<p>
The example below uses the modulo operator to create an array of 10 random digits
using random bytes as the source for randomness.
</p>
<sample src="examples/bad-random.js" />
<p>
The random byte is a uniformly random value between 0 and 255, and thus the result
from using the modulo operator is slightly more likely to be between 0 and 5 than
between 6 and 9.
</p>
<p>
The issue has been fixed in the code below by using a library that correctly generates
cryptographically secure random values.
</p>
<sample src="examples/bad-random-fixed.js" />
<p>
Alternatively, the issue can be fixed by fixing the math in the original code.
In the code below the random byte is discarded if the value is greater than or equal to 250.
Thus the modulo operator is used on a uniformly random number between 0 and 249, which
results in a uniformly random digit between 0 and 9.
</p>
<sample src="examples/bad-random-fixed2.js" />
</example>
<references>
<li>Stack Overflow: <a href="https://stackoverflow.com/questions/3956478/understanding-randomness">Understanding “randomness”</a>.</li>
<li>OWASP: <a href="https://owasp.org/www-community/vulnerabilities/Insecure_Randomness">Insecure Randomness</a>.</li>
<li>OWASP: <a
href="https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html#rule---use-strong-approved-authenticated-encryption">Rule
- Use strong approved cryptographic algorithms</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,184 @@
/**
* @name Creating biased random numbers from a cryptographically secure source.
* @description Some mathematical operations on random numbers can cause bias in
* the results and compromise security.
* @kind problem
* @problem.severity warning
* @precision high
* @id js/biased-cryptographic-random
* @tags security
* external/cwe/cwe-327
*/
import javascript
private import semmle.javascript.dataflow.internal.StepSummary
private import semmle.javascript.security.dataflow.InsecureRandomnessCustomizations
private import semmle.javascript.dataflow.InferredTypes
/**
* Gets a number that is a power of 2.
*/
private int powerOfTwo() {
result = 1
or
result = 2 * powerOfTwo() and
not result < 0
}
/**
* Gets a node that has value 2^n for some n.
*/
private DataFlow::Node isPowerOfTwo() {
exists(DataFlow::Node prev |
prev.getIntValue() = powerOfTwo()
or
// Getting around the 32 bit ints in QL. These are some hex values of the form 0x10000000
prev.asExpr().(NumberLiteral).getValue() =
["281474976710656", "17592186044416", "1099511627776", "68719476736", "4294967296"]
|
result = prev.getASuccessor*()
)
}
/**
* Gets a node that has value (2^n)-1 for some n.
*/
private DataFlow::Node isPowerOfTwoMinusOne() {
exists(DataFlow::Node prev |
prev.getIntValue() = powerOfTwo() - 1
or
// Getting around the 32 bit ints in QL. These are some hex values of the form 0xfffffff
prev.asExpr().(NumberLiteral).getValue() =
["281474976710655", "17592186044415", "1099511627775", "68719476735", "4294967295"]
|
result = prev.getASuccessor*()
)
}
/**
* Gets the pseudo-property used to track elements inside a Buffer.
* The API for `Set` is close enough to the API for `Buffer` that we can reuse the type-tracking steps.
*/
private string prop() { result = DataFlow::PseudoProperties::setElement() }
/**
* Gets a reference to a cryptographically secure random number produced by `source` and type tracked using `t`.
*/
private DataFlow::Node goodRandom(DataFlow::TypeTracker t, DataFlow::SourceNode source) {
t.startInProp(prop()) and
result = InsecureRandomness::randomBufferSource() and
result = source
or
// Loading a number from a `Buffer`.
exists(DataFlow::TypeTracker t2 | t = t2.append(LoadStep(prop())) |
// the random generators return arrays/Buffers of random numbers, we therefore track through an indexed read.
exists(DataFlow::PropRead read | result = read |
read.getBase() = goodRandom(t2, source) and
not read.getPropertyNameExpr() instanceof Label
)
or
// reading a number from a Buffer.
exists(DataFlow::MethodCallNode call | result = call |
call.getReceiver() = goodRandom(t2, source) and
call
.getMethodName()
.regexpMatch("read(BigInt|BigUInt|Double|Float|Int|UInt)(8|16|32|64)?(BE|LE)?")
)
)
or
exists(DataFlow::TypeTracker t2 | t = t2.smallstep(goodRandom(t2, source), result))
or
// re-using the collection steps for `Set`.
exists(DataFlow::TypeTracker t2 |
result = CollectionsTypeTracking::collectionStep(goodRandom(t2, source), t, t2)
)
or
InsecureRandomness::isAdditionalTaintStep(goodRandom(t.continue(), source), result) and
// bit shifts and multiplication by powers of two are generally used for constructing larger numbers from smaller numbers.
not exists(BinaryExpr binop | binop = result.asExpr() |
binop.getOperator().regexpMatch(".*(<|>).*")
or
binop.getOperator() = "*" and isPowerOfTwo().asExpr() = binop.getAnOperand()
or
// string concat does not produce a number
unique(InferredType type | type = binop.flow().analyze().getAType()) = TTString()
)
}
/**
* Gets a reference to a cryptographically secure random number produced by `source`.
*/
DataFlow::Node goodRandom(DataFlow::SourceNode source) {
result = goodRandom(DataFlow::TypeTracker::end(), source)
}
/**
* Gets a node that is passed to a rounding function from `Math`, using type-backtracker `t`.
*/
DataFlow::Node isRounded(DataFlow::TypeBackTracker t) {
t.start() and
result = DataFlow::globalVarRef("Math").getAMemberCall(["round", "floor", "ceil"]).getArgument(0)
or
exists(DataFlow::TypeBackTracker t2 | t2 = t.smallstep(result, isRounded(t2)))
or
InsecureRandomness::isAdditionalTaintStep(result, isRounded(t.continue()))
}
/**
* Gets a node that that produces a biased result from otherwise cryptographically secure random numbers produced by `source`.
*/
DataFlow::Node badCrypto(string description, DataFlow::SourceNode source) {
// addition and multiplication - always bad when both the lhs and rhs are random.
exists(BinaryExpr binop | result.asExpr() = binop |
goodRandom(_).asExpr() = binop.getLeftOperand() and
goodRandom(_).asExpr() = binop.getRightOperand() and
goodRandom(source).asExpr() = binop.getAnOperand() and
(
binop.getOperator() = "+" and description = "addition"
or
binop.getOperator() = "*" and description = "multiplication"
)
)
or
// division - bad if result is rounded.
exists(DivExpr div | result.asExpr() = div |
goodRandom(source).asExpr() = div.getLeftOperand() and
description = "division and rounding the result" and
not div.getRightOperand() = isPowerOfTwoMinusOne().asExpr() and // division by (2^n)-1 most of the time produces a uniformly random number between 0 and 1.
div = isRounded(DataFlow::TypeBackTracker::end()).asExpr()
)
or
// modulo - only bad if not by a power of 2 - and the result is not checked for bias
exists(ModExpr mod, DataFlow::Node random | result.asExpr() = mod and mod.getOperator() = "%" |
description = "modulo" and
goodRandom(source) = random and
random.asExpr() = mod.getLeftOperand() and
// division by a power of 2 is OK. E.g. if `x` is uniformly random is in the range [0..255] then `x % 32` is uniformly random in the range [0..31].
not mod.getRightOperand() = isPowerOfTwo().asExpr() and
// not exists a comparison that checks if the result is potentially biased.
not exists(BinaryExpr comparison | comparison.getOperator() = [">", "<", "<=", ">="] |
AccessPath::getAnAliasedSourceNode(random.getALocalSource())
.flowsToExpr(comparison.getAnOperand())
or
exists(DataFlow::PropRead otherRead |
otherRead = random.(DataFlow::PropRead).getBase().getALocalSource().getAPropertyRead() and
not exists(otherRead.getPropertyName()) and
otherRead.flowsToExpr(comparison.getAnOperand())
)
)
)
or
// create a number from a string - always a bad idea.
exists(DataFlow::CallNode number, StringOps::ConcatenationRoot root | result = number |
number = DataFlow::globalVarRef(["Number", "parseInt", "parseFloat"]).getACall() and
root = number.getArgument(0) and
goodRandom(source) = root.getALeaf() and
exists(root.getALeaf().getStringValue()) and
description = "string concatenation"
)
}
from DataFlow::Node node, string description, DataFlow::SourceNode source
where node = badCrypto(description, source)
select node, "Using " + description + " on a $@ produces biased results.", source,
"cryptographically secure random number"

View File

@@ -0,0 +1,3 @@
const cryptoRandomString = require('crypto-random-string');
const digits = cryptoRandomString({length: 10, type: 'numeric'});

View File

@@ -0,0 +1,10 @@
const crypto = require('crypto');
const digits = [];
while (digits.length < 10) {
const byte = crypto.randomBytes(1)[0];
if (byte >= 250) {
continue;
}
digits.push(byte % 10); // OK
}

View File

@@ -0,0 +1,6 @@
const crypto = require('crypto');
const digits = [];
for (let i = 0; i < 10; i++) {
digits.push(crypto.randomBytes(1)[0] % 10); // NOT OK
}

View File

@@ -0,0 +1,38 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Downloading executeables or other sensitive files over an unencrypted connection
can leave a server open to man-in-the-middle attacks (MITM).
Such an attack can allow an attacker to insert arbitrary content
into the downloaded file, and in the worst case, allow the attacker to execute
arbitrary code on the vulnerable system.
</p>
</overview>
<recommendation>
<p>
Use a secure transfer protocol when downloading executables or other sensitive files.
</p>
</recommendation>
<example>
<p>
In this example, a server downloads a shell script from a remote URL using the <code>node-fetch</code>
library, and then executes this shell script.
</p>
<sample src="examples/insecure-download.js" />
<p>
The HTTP protocol is vulnerable to MITM, and thus an attacker could potentially replace the downloaded
shell script with arbitrary code, which gives the attacker complete control over the system.
</p>
<p>
The issue has been fixed in the example below by replacing the HTTP protocol with the HTTPS protocol.
</p>
<sample src="examples/insecure-download.js" />
</example>
<references>
<li>OWASP: <a href="https://owasp.org/www-community/attacks/Man-in-the-middle_attack">Man-in-the-middle attack</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,20 @@
/**
* @name Download of sensitive file through insecure connection
* @description Downloading executables and other sensitive files over an insecure connection
* opens up for potential man-in-the-middle attacks.
* @kind path-problem
* @problem.severity error
* @precision high
* @id js/insecure-download
* @tags security
* external/cwe/cwe-829
*/
import javascript
import semmle.javascript.security.dataflow.InsecureDownload::InsecureDownload
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "$@ of sensitive file from $@.",
sink.getNode().(Sink).getDownloadCall(), "Download", source.getNode(), "HTTP source"

View File

@@ -0,0 +1,6 @@
const fetch = require("node-fetch");
const cp = require("child_process");
fetch('http://mydownload.example.org/myscript.sh')
.then(res => res.text())
.then(script => cp.execSync(script));

View File

@@ -0,0 +1,6 @@
const fetch = require("node-fetch");
const cp = require("child_process");
fetch('https://mydownload.example.org/myscript.sh')
.then(res => res.text())
.then(script => cp.execSync(script));

View File

@@ -0,0 +1,40 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>If you use cross-origin communication between Window objects and do expect to receive messages from other sites, always verify the sender's identity using the origin and possibly source properties of the recevied `MessageEvent`. </p>
<p>Unexpected behaviours, like `DOM-based XSS` could occur, if the event handler for incoming data does not check the origin of the data received and handles the data in an unsafe way.</p>
</overview>
<recommendation>
<p>
Always verify the sender's identity of incoming messages.
</p>
</recommendation>
<example>
<p>In the first example, the `MessageEvent.data` is passed to the `eval` function withouth checking the origin. This means that any window can send arbitrary messages that will be executed in the window receiving the message</p>
<sample src="examples/postMessageNoOriginCheck.js" />
<p> In the second example, the `MessageEvent.origin` is verified with an unsecure check. For example, using `event.origin.indexOf('www.example.com') > -1` can be bypassed because the string `www.example.com` could appear anywhere in `event.origin` (i.e. `www.example.com.mydomain.com`)</p>
<sample src="examples/postMessageInsufficientCheck.js" />
<p> In the third example, the `MessageEvent.origin` is properly checked against a trusted origin. </p>
<sample src="examples/postMessageWithOriginCheck.js" />
</example>
<references>
<li><a href="https://cwe.mitre.org/data/definitions/20.html">CWE-20: Improper Input Validation</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage">Window.postMessage()</a></li>
<li><a href="https://portswigger.net/web-security/dom-based/web-message-manipulation">Web-message manipulation</a></li>
<li><a href="https://labs.detectify.com/2016/12/08/the-pitfalls-of-postmessage/">The pitfalls of postMessage</a></li>
</references>
</qhelp>

View File

@@ -0,0 +1,64 @@
/**
* @name Missing `MessageEvent.origin` verification in `postMessage` handlers
* @description Missing the `MessageEvent.origin` verification in `postMessage` handlers, allows any windows to send arbitrary data to the `MessageEvent` listener.
* This could lead to unexpected behaviour, especially when `MessageEvent.data` is used in an unsafe way.
* @kind problem
* @problem.severity warning
* @precision high
* @id js/missing-postmessageorigin-verification
* @tags correctness
* security
* external/cwe/cwe-20
*/
import javascript
import semmle.javascript.security.dataflow.DOM
/**
* A method call for the insecure functions used to verify the `MessageEvent.origin`.
*/
class InsufficientOriginChecks extends DataFlow::Node {
InsufficientOriginChecks() {
exists(DataFlow::Node node |
this.(StringOps::StartsWith).getSubstring() = node or
this.(StringOps::Includes).getSubstring() = node or
this.(StringOps::EndsWith).getSubstring() = node
)
}
}
/**
* A function handler for the `MessageEvent`.
*/
class PostMessageHandler extends DataFlow::FunctionNode {
PostMessageHandler() { this.getFunction() instanceof PostMessageEventHandler }
}
/**
* The `MessageEvent` parameter received by the handler
*/
class PostMessageEvent extends DataFlow::SourceNode {
PostMessageEvent() { exists(PostMessageHandler handler | this = handler.getParameter(0)) }
/**
* Holds if an access on `MessageEvent.origin` is in an `EqualityTest` and there is no call of an insufficient verification method on `MessageEvent.origin`
*/
predicate hasOriginChecked() {
exists(EqualityTest test |
this.getAPropertyRead(["origin", "source"]).flowsToExpr(test.getAnOperand())
)
}
/**
* Holds if there is an insufficient method call (i.e indexOf) used to verify `MessageEvent.origin`
*/
predicate hasOriginInsufficientlyChecked() {
exists(InsufficientOriginChecks insufficientChecks |
this.getAPropertyRead("origin").getAMethodCall*() = insufficientChecks
)
}
}
from PostMessageEvent event
where not event.hasOriginChecked() or event.hasOriginInsufficientlyChecked()
select event, "Missing or unsafe origin verification."

View File

@@ -0,0 +1,14 @@
function postMessageHandler(event) {
let origin = event.origin.toLowerCase();
let host = window.location.host;
// BAD
if (origin.indexOf(host) === -1)
return;
eval(event.data);
}
window.addEventListener('message', postMessageHandler, false);

View File

@@ -0,0 +1,9 @@
function postMessageHandler(event) {
let origin = event.origin.toLowerCase();
console.log(origin)
// BAD: the origin property is not checked
eval(event.data);
}
window.addEventListener('message', postMessageHandler, false);

View File

@@ -0,0 +1,9 @@
function postMessageHandler(event) {
console.log(event.origin)
// GOOD: the origin property is checked
if (event.origin === 'www.example.com') {
// do something
}
}
window.addEventListener('message', postMessageHandler, false);

View File

@@ -0,0 +1,32 @@
/**
* Helpers to generating meta metrics, that is, metrics about the CodeQL analysis and extractor.
*/
private import javascript
private import semmle.javascript.dependencies.Dependencies
private import semmle.javascript.dependencies.FrameworkLibraries
private import semmle.javascript.frameworks.Testing
/**
* Gets the root folder of the snapshot.
*
* This is selected as the location for project-wide metrics.
*/
Folder projectRoot() { result.getRelativePath() = "" }
/** A file we ignore because it is a test file or compiled/generated/bundled code. */
class IgnoredFile extends File {
IgnoredFile() {
any(Test t).getFile() = this
or
getRelativePath().regexpMatch("(?i).*/test(case)?s?/.*")
or
getBaseName().regexpMatch("(?i)(.*[._\\-]|^)(min|bundle|concat|spec|tests?)\\.[a-zA-Z]+")
or
exists(TopLevel tl | tl.getFile() = this |
tl.isExterns()
or
tl instanceof FrameworkLibraryInstance
)
}
}

View File

@@ -9,30 +9,7 @@ private import semmle.javascript.dependencies.Dependencies
private import semmle.javascript.dependencies.FrameworkLibraries
private import semmle.javascript.frameworks.Testing
private import DataFlow
/**
* Gets the root folder of the snapshot.
*
* This is selected as the location for project-wide metrics.
*/
Folder projectRoot() { result.getRelativePath() = "" }
/** A file we ignore because it is a test file or compiled/generated/bundled code. */
class IgnoredFile extends File {
IgnoredFile() {
any(Test t).getFile() = this
or
getRelativePath().regexpMatch("(?i).*/test(case)?s?/.*")
or
getBaseName().regexpMatch("(?i)(.*[._\\-]|^)(min|bundle|concat|spec|tests?)\\.[a-zA-Z]+")
or
exists(TopLevel tl | tl.getFile() = this |
tl.isExterns()
or
tl instanceof FrameworkLibraryInstance
)
}
}
import meta.MetaMetrics
/** An call site that is relevant for analysis quality. */
class RelevantInvoke extends InvokeNode {

View File

@@ -0,0 +1,17 @@
/**
* @name Resolvable imports
* @description The number of imports that could be resolved to its target.
* @kind metric
* @metricType project
* @metricAggregate sum
* @tags meta
* @id js/meta/resolvable-imports
*/
import javascript
import CallGraphQuality
Import relevantImport() { not result.getFile() instanceof IgnoredFile }
select projectRoot(),
count(Import imprt | imprt = relevantImport() and exists(imprt.getImportedModule()))

View File

@@ -0,0 +1,16 @@
/**
* @name Route handlers
* @description The number of HTTP route handler functions found.
* @kind metric
* @metricType project
* @metricAggregate sum
* @tags meta
* @id js/meta/route-handlers
*/
import javascript
import CallGraphQuality
HTTP::RouteHandler relevantRouteHandler() { not result.getFile() instanceof IgnoredFile }
select projectRoot(), count(relevantRouteHandler())

View File

@@ -0,0 +1,22 @@
/**
* @name Sanitizers reachable from source
* @description The number of sanitizers reachable from a recognized taint source.
* @kind metric
* @metricType project
* @metricAggregate sum
* @tags meta
* @id js/meta/sanitizers-reachable-from-source
*/
import javascript
import TaintMetrics
class BasicTaintConfiguration extends TaintTracking::Configuration {
BasicTaintConfiguration() { this = "BasicTaintConfiguration" }
override predicate isSource(DataFlow::Node node) { node = relevantTaintSource() }
override predicate isSink(DataFlow::Node node) { node = relevantSanitizerInput() }
}
select projectRoot(), count(DataFlow::Node node | any(BasicTaintConfiguration cfg).hasFlow(_, node))

View File

@@ -0,0 +1,22 @@
/**
* @name Sinks reachable from sanitizer
* @description The number of sinks reachable from a recognized sanitizer call.
* @kind metric
* @metricType project
* @metricAggregate sum
* @tags meta
* @id js/meta/sinks-reachable-from-sanitizer
*/
import javascript
import TaintMetrics
class BasicTaintConfiguration extends TaintTracking::Configuration {
BasicTaintConfiguration() { this = "BasicTaintConfiguration" }
override predicate isSource(DataFlow::Node node) { node = relevantSanitizerOutput() }
override predicate isSink(DataFlow::Node node) { node = relevantTaintSink() }
}
select projectRoot(), count(DataFlow::Node node | any(BasicTaintConfiguration cfg).hasFlow(_, node))

View File

@@ -0,0 +1,89 @@
/**
* Provides predicates for measuring taint-tracking coverage.
*/
private import javascript
import meta.MetaMetrics
private import semmle.javascript.security.dataflow.ClientSideUrlRedirectCustomizations
private import semmle.javascript.security.dataflow.CodeInjectionCustomizations
private import semmle.javascript.security.dataflow.CommandInjectionCustomizations
private import semmle.javascript.security.dataflow.Xss as Xss
private import semmle.javascript.security.dataflow.NosqlInjectionCustomizations
private import semmle.javascript.security.dataflow.PrototypePollutionCustomizations
private import semmle.javascript.security.dataflow.RegExpInjectionCustomizations
private import semmle.javascript.security.dataflow.RequestForgeryCustomizations
private import semmle.javascript.security.dataflow.ServerSideUrlRedirectCustomizations
private import semmle.javascript.security.dataflow.SqlInjectionCustomizations
private import semmle.javascript.security.dataflow.TaintedPathCustomizations
private import semmle.javascript.security.dataflow.UnsafeDeserializationCustomizations
private import semmle.javascript.security.dataflow.XmlBombCustomizations
private import semmle.javascript.security.dataflow.XpathInjectionCustomizations
private import semmle.javascript.security.dataflow.XxeCustomizations
private import semmle.javascript.security.dataflow.ZipSlipCustomizations
/**
* Gets a relevant taint sink.
*
* To ensure this metric isn't dominated by a few queries with a huge number of sinks,
* we only include sinks for queries that have fairly specific sinks and/or have high severity
* relative to the number of sinks.
*
* Examples of excluded queries:
* - UnsafeDynamicMethodAccess: high severity (RCE) but has way too many sinks (every callee).
* - ClearTextLogging: not severe enough relative to number of sinks.
*/
DataFlow::Node relevantTaintSink() {
not result.getFile() instanceof IgnoredFile and
(
result instanceof ClientSideUrlRedirect::Sink or
result instanceof CodeInjection::Sink or
result instanceof CommandInjection::Sink or
result instanceof Xss::Shared::Sink or
result instanceof NosqlInjection::Sink or
result instanceof PrototypePollution::Sink or
result instanceof RegExpInjection::Sink or
result instanceof RequestForgery::Sink or
result instanceof ServerSideUrlRedirect::Sink or
result instanceof SqlInjection::Sink or
result instanceof TaintedPath::Sink or
result instanceof UnsafeDeserialization::Sink or
result instanceof XmlBomb::Sink or
result instanceof XpathInjection::Sink or
result instanceof Xxe::Sink or
result instanceof ZipSlip::Sink
)
}
/**
* Gets a remote flow source or `document.location` source.
*/
DataFlow::Node relevantTaintSource() {
not result.getFile() instanceof IgnoredFile and
(
result instanceof RemoteFlowSource
or
result = DOM::locationSource()
)
}
/**
* Gets the output of a call that shows intent to sanitize a value
* (indicating a likely vulnerability if the sanitizer was removed).
*
* Currently we only recognize HTML sanitizers.
*/
DataFlow::Node relevantSanitizerOutput() {
result = any(HtmlSanitizerCall call) and
not result.getFile() instanceof IgnoredFile
}
/**
* Gets the input to a call that shows intent to sanitize a value
* (indicating a likely vulnerability if the sanitizer was removed).
*
* Currently we only recognize HTML sanitizers.
*/
DataFlow::Node relevantSanitizerInput() {
result = any(HtmlSanitizerCall call).getInput() and
not result.getFile() instanceof IgnoredFile
}

View File

@@ -0,0 +1,14 @@
/**
* @name Taint sinks
* @description The number of high-severity taint sinks.
* @kind metric
* @metricType project
* @metricAggregate sum
* @tags meta
* @id js/meta/taint-sinks
*/
import javascript
import TaintMetrics
select projectRoot(), count(relevantTaintSink())

View File

@@ -0,0 +1,14 @@
/**
* @name Taint sources
* @description The number of remote flow sources and document.location sources
* @kind metric
* @metricType project
* @metricAggregate sum
* @tags meta
* @id js/meta/taint-sources
*/
import javascript
import TaintMetrics
select projectRoot(), count(relevantTaintSource())

View File

@@ -0,0 +1,24 @@
/**
* @name Taint steps
* @description The number of default taint steps.
* @kind metric
* @metricType project
* @metricAggregate sum
* @tags meta
* @id js/meta/taint-steps
*/
import javascript
import CallGraphQuality
class BasicTaintConfiguration extends TaintTracking::Configuration {
BasicTaintConfiguration() { this = "BasicTaintConfiguration" }
}
predicate relevantStep(DataFlow::Node pred, DataFlow::Node succ) {
any(BasicTaintConfiguration cfg).isAdditionalFlowStep(pred, succ) and
not pred.getFile() instanceof IgnoredFile and
not succ.getFile() instanceof IgnoredFile
}
select projectRoot(), count(DataFlow::Node pred, DataFlow::Node succ | relevantStep(pred, succ))

View File

@@ -0,0 +1,27 @@
/**
* @name Tainted expressions
* @description The number of expressions reachable from a remote flow source
* via default taint-tracking steps.
* @kind metric
* @metricType project
* @metricAggregate sum
* @tags meta
* @id js/meta/tainted-nodes
*/
import javascript
import TaintMetrics
class BasicTaintConfiguration extends TaintTracking::Configuration {
BasicTaintConfiguration() { this = "BasicTaintConfiguration" }
override predicate isSource(DataFlow::Node node) { node = relevantTaintSource() }
override predicate isSink(DataFlow::Node node) {
// To reduce noise from synthetic nodes, only count value nodes
node instanceof DataFlow::ValueNode and
not node.getFile() instanceof IgnoredFile
}
}
select projectRoot(), count(DataFlow::Node node | any(BasicTaintConfiguration cfg).hasFlow(_, node))

View File

@@ -0,0 +1,17 @@
/**
* @name Typed expressions
* @description The number of expressions for which the TypeScript extractor could
* extract a type other than 'any'.
* @kind metric
* @metricType project
* @metricAggregate sum
* @tags meta
* @id js/meta/typed-expressions
*/
import javascript
import meta.MetaMetrics
predicate isProperType(Type t) { not t instanceof AnyType }
select projectRoot(), count(Expr e | isProperType(e.getType()))

View File

@@ -41,6 +41,11 @@ module ArrayTaintTracking {
succ = call
)
or
// `array.reduce` with tainted value in callback
call.(DataFlow::MethodCallNode).getMethodName() = "reduce" and
pred = call.getArgument(0).(DataFlow::FunctionNode).getAReturn() and // Require the argument to be a closure to avoid spurious call/return flow
succ = call
or
// `array.push(e)`, `array.unshift(e)`: if `e` is tainted, then so is `array`.
exists(string name |
name = "push" or

View File

@@ -291,11 +291,27 @@ module DOM {
*/
abstract class Range extends DataFlow::Node { }
private string getADomPropertyName() {
exists(ExternalInstanceMemberDecl decl |
result = decl.getName() and
isDomRootType(decl.getDeclaringType().getASupertype*())
)
}
private class DefaultRange extends Range {
DefaultRange() {
this.asExpr().(VarAccess).getVariable() instanceof DOMGlobalVariable
or
this = domValueRef().getAPropertyRead()
exists(DataFlow::PropRead read |
this = read and
read = domValueRef().getAPropertyRead()
|
not read.mayHavePropertyName(_)
or
read.mayHavePropertyName(getADomPropertyName())
or
read.mayHavePropertyName(any(string s | exists(s.toInt())))
)
or
this = domElementCreationOrQuery()
or

View File

@@ -1519,7 +1519,8 @@ class AddExpr extends @addexpr, BinaryExpr {
override string getOperator() { result = "+" }
override string getStringValue() {
result = getLeftOperand().getStringValue() + getRightOperand().getStringValue()
result = getLeftOperand().getStringValue() + getRightOperand().getStringValue() and
result.length() < 1000 * 1000
}
}

View File

@@ -174,7 +174,8 @@ abstract class Import extends ASTNode {
result = resolveAsProvidedModule() or
result = resolveImportedPath() or
result = resolveFromTypeRoot() or
result = resolveFromTypeScriptSymbol()
result = resolveFromTypeScriptSymbol() or
result = resolveNeighbourPackage(this.getImportedPath().getValue())
)
}
@@ -196,3 +197,28 @@ abstract deprecated class PathExprInModule extends PathExpr {
this.(Comment).getTopLevel() instanceof Module
}
}
/**
* Gets a module imported from another package in the same repository.
*
* No support for importing from folders inside the other package.
*/
private Module resolveNeighbourPackage(PathString importPath) {
exists(PackageJSON json | importPath = json.getPackageName() and result = json.getMainModule())
or
exists(string package |
result.getFile().getParentContainer() = getPackageFolder(package) and
importPath = package + "/" + [result.getFile().getBaseName(), result.getFile().getStem()]
)
}
/**
* Gets the folder for a package that has name `package` according to a package.json file in the resulting folder.
*/
pragma[noinline]
private Folder getPackageFolder(string package) {
exists(PackageJSON json |
json.getPackageName() = package and
result = json.getFile().getParentContainer()
)
}

View File

@@ -82,20 +82,20 @@ File tryExtensions(Folder dir, string basename, int priority) {
* Gets the main module described by `pkg` with the given `priority`.
*/
File resolveMainModule(PackageJSON pkg, int priority) {
if exists(MainModulePath::of(pkg))
then
exists(PathExpr main | main = MainModulePath::of(pkg) |
result = main.resolve() and priority = 0
or
result = tryExtensions(main.resolve(), "index", priority)
or
not exists(main.resolve()) and
not exists(main.getExtension()) and
exists(int n | n = main.getNumComponent() |
result = tryExtensions(main.resolveUpTo(n - 1), main.getComponent(n - 1), priority)
)
exists(PathExpr main | main = MainModulePath::of(pkg) |
result = main.resolve() and priority = 0
or
result = tryExtensions(main.resolve(), "index", priority)
or
not exists(main.resolve()) and
not exists(main.getExtension()) and
exists(int n | n = main.getNumComponent() |
result = tryExtensions(main.resolveUpTo(n - 1), main.getComponent(n - 1), priority)
)
else result = tryExtensions(pkg.getFile().getParentContainer(), "index", priority)
)
or
result =
tryExtensions(pkg.getFile().getParentContainer(), "index", priority - prioritiesPerCandidate())
}
/**

View File

@@ -68,4 +68,9 @@ private DataFlow::Node getAnExportFromModule(Module mod) {
result.analyze().getAValue() = mod.(NodeModule).getAModuleExportsValue()
or
exists(ASTNode export | result.getEnclosingExpr() = export | mod.exports(_, export))
or
exists(ExportDeclaration export |
result = export.getSourceNode(_) and
mod = export.getTopLevel()
)
}

View File

@@ -576,6 +576,22 @@ module Bluebird {
override DataFlow::Node getArrayNode() { result = getArgument(0) }
}
/**
* An async function created using a call to `bluebird.coroutine`.
*/
class BluebirdCoroutineDefinition extends DataFlow::CallNode {
BluebirdCoroutineDefinition() { this = bluebird().getAMemberCall("coroutine") }
}
private class BluebirdCoroutineDefinitionAsPartialInvoke extends DataFlow::PartialInvokeNode::Range,
BluebirdCoroutineDefinition {
override DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) {
boundArgs = 0 and
callback = getArgument(0) and
result = this
}
}
}
/**

View File

@@ -720,14 +720,8 @@ module StringOps {
override DataFlow::Node getStringOperand() { result = getArgument(0) }
}
private class MatchesCall extends Range, DataFlow::MethodCallNode {
MatchesCall() { getMethodName() = "matches" }
override DataFlow::Node getRegExpOperand(boolean coerced) {
result = getArgument(0) and coerced = true
}
override DataFlow::Node getStringOperand() { result = getReceiver() }
private class MatchCall extends DataFlow::MethodCallNode {
MatchCall() { getMethodName() = "match" }
}
private class ExecCall extends DataFlow::MethodCallNode {
@@ -777,5 +771,22 @@ module StringOps {
override boolean getPolarity() { result = polarity }
}
private class MatchTest extends Range, DataFlow::ValueNode {
MatchCall match;
boolean polarity;
MatchTest() {
exists(Expr use | match.flowsToExpr(use) | impliesNotNull(astNode, use, polarity))
}
override DataFlow::Node getRegExpOperand(boolean coerced) {
result = match.getArgument(0) and coerced = true
}
override DataFlow::Node getStringOperand() { result = match.getReceiver() }
override boolean getPolarity() { result = polarity }
}
}
}

View File

@@ -267,6 +267,12 @@ module TaintTracking {
pred = DataFlow::valueNode(fos.getIterationDomain()) and
succ = DataFlow::lvalueNode(fos.getLValue())
)
or
// taint-tracking rest patterns in l-values. E.g. `const {...spread} = foo()` or `const [...spread] = foo()`.
exists(DestructuringPattern pattern |
pred = DataFlow::lvalueNode(pattern) and
succ = DataFlow::lvalueNode(pattern.getRest())
)
}
/**

View File

@@ -620,4 +620,45 @@ module ClientRequest {
override DataFlow::Node getADataNode() { none() }
}
/**
* A call to `nugget` that downloads one of more files to a destination determined by an options object given as the second argument.
*/
class Nugget extends ClientRequest::Range, DataFlow::CallNode {
Nugget() { this = DataFlow::moduleImport("nugget").getACall() }
override DataFlow::Node getUrl() { result = getArgument(0) }
override DataFlow::Node getHost() { none() }
override DataFlow::Node getADataNode() { none() }
}
/**
* A shell execution of `curl` that downloads some file.
*/
class CurlDownload extends ClientRequest::Range {
SystemCommandExecution cmd;
CurlDownload() {
this = cmd and
(
cmd.getACommandArgument().getStringValue() = "curl" or
cmd
.getACommandArgument()
.(StringOps::ConcatenationRoot)
.getConstantStringParts()
.regexpMatch("curl .*")
)
}
override DataFlow::Node getUrl() {
result = cmd.getArgumentList().getALocalSource().getAPropertyWrite().getRhs() or
result = cmd.getACommandArgument().(StringOps::ConcatenationRoot).getALeaf()
}
override DataFlow::Node getHost() { none() }
override DataFlow::Node getADataNode() { none() }
}
}

View File

@@ -44,6 +44,9 @@ module Express {
isRouter(e, _)
or
e.getType().hasUnderlyingType("express", "Router")
or
// created by `webpack-dev-server`
WebpackDevServer::webpackDevServerApp().flowsToExpr(e)
}
/**
@@ -903,4 +906,32 @@ module Express {
override DataFlow::ValueNode getARouteHandlerArg() { result = routeHandlerArg }
}
private module WebpackDevServer {
/**
* Gets a source for the options given to an instantiation of `webpack-dev-server`.
*/
private DataFlow::SourceNode devServerOptions(DataFlow::TypeBackTracker t) {
t.start() and
result =
DataFlow::moduleImport("webpack-dev-server")
.getAnInstantiation()
.getArgument(1)
.getALocalSource()
or
exists(DataFlow::TypeBackTracker t2 | result = devServerOptions(t2).backtrack(t2, t))
}
/**
* Gets an instance of the `express` app created by `webpack-dev-server`.
*/
DataFlow::ParameterNode webpackDevServerApp() {
result =
devServerOptions(DataFlow::TypeBackTracker::end())
.getAPropertyWrite(["after", "before", "setup"])
.getRhs()
.getAFunctionValue()
.getParameter(0)
}
}
}

View File

@@ -2,6 +2,7 @@ import semmle.javascript.frameworks.Express
import semmle.javascript.frameworks.Hapi
import semmle.javascript.frameworks.Koa
import semmle.javascript.frameworks.NodeJSLib
import semmle.javascript.frameworks.Micro
import semmle.javascript.frameworks.Restify
import semmle.javascript.frameworks.Connect
import semmle.javascript.frameworks.Fastify

View File

@@ -54,7 +54,7 @@ private module Console {
name = getAStandardLoggerMethodName() or
name = "assert"
) and
this = console().getAMethodCall(name)
this = console().getAMemberCall(name)
}
override DataFlow::Node getAMessageComponent() {

View File

@@ -0,0 +1,114 @@
/**
* Provides a model of the `micro` NPM package.
*/
private import javascript
private module Micro {
private import DataFlow
/**
* A node that should be interpreted as a route handler, to use as starting
* point for back-tracking.
*/
Node microRouteHandlerSink() {
result = moduleMember("micro", "run").getACall().getLastArgument()
or
result = moduleImport("micro").getACall().getArgument(0)
}
/** Gets a data flow node interpreted as a route handler. */
private DataFlow::SourceNode microRouteHandler(DataFlow::TypeBackTracker t) {
t.start() and
result = microRouteHandlerSink().getALocalSource()
or
exists(DataFlow::TypeBackTracker t2 | result = microRouteHandler(t2).backtrack(t2, t))
or
exists(DataFlow::CallNode transformer |
transformer = moduleImport("micro-compress").getACall()
or
transformer instanceof Bluebird::BluebirdCoroutineDefinition
|
microRouteHandler(t.continue()) = transformer and
result = transformer.getArgument(0).getALocalSource()
)
}
/** Gets a data flow node interpreted as a route handler. */
DataFlow::SourceNode microRouteHandler() {
result = microRouteHandler(DataFlow::TypeBackTracker::end())
}
/**
* A function passed to `micro` or `micro.run`.
*/
class MicroRouteHandler extends HTTP::Servers::StandardRouteHandler, DataFlow::FunctionNode {
MicroRouteHandler() { this = microRouteHandler().getAFunctionValue() }
}
class MicroRequestSource extends HTTP::Servers::RequestSource {
MicroRouteHandler h;
MicroRequestSource() { this = h.getParameter(0) }
override HTTP::RouteHandler getRouteHandler() { result = h }
}
class MicroResponseSource extends HTTP::Servers::ResponseSource {
MicroRouteHandler h;
MicroResponseSource() { this = h.getParameter(1) }
override HTTP::RouteHandler getRouteHandler() { result = h }
}
class MicroRequestExpr extends NodeJSLib::RequestExpr {
override MicroRequestSource src;
}
class MicroReseponseExpr extends NodeJSLib::ResponseExpr {
override MicroResponseSource src;
}
private HTTP::RouteHandler getRouteHandlerFromReqRes(DataFlow::Node node) {
exists(HTTP::Servers::RequestSource src |
src.ref().flowsTo(node) and
result = src.getRouteHandler()
)
or
exists(HTTP::Servers::ResponseSource src |
src.ref().flowsTo(node) and
result = src.getRouteHandler()
)
}
class MicroBodyParserCall extends HTTP::RequestInputAccess, DataFlow::CallNode {
string name;
MicroBodyParserCall() {
name = ["buffer", "text", "json"] and
this = moduleMember("micro", name).getACall()
}
override string getKind() { result = "body" }
override HTTP::RouteHandler getRouteHandler() {
result = getRouteHandlerFromReqRes(getArgument(0))
}
override predicate isUserControlledObject() { name = "json" }
}
class MicroSendArgument extends HTTP::ResponseSendArgument {
CallNode send;
MicroSendArgument() {
send = moduleMember("micro", ["send", "sendError"]).getACall() and
this = send.getLastArgument().asExpr()
}
override HTTP::RouteHandler getRouteHandler() {
result = getRouteHandlerFromReqRes(send.getArgument([0, 1]))
}
}
}

View File

@@ -17,32 +17,12 @@ private import semmle.javascript.security.dataflow.RegExpInjectionCustomizations
private import semmle.javascript.security.dataflow.ClientSideUrlRedirectCustomizations
private import semmle.javascript.security.dataflow.ServerSideUrlRedirectCustomizations
private import semmle.javascript.security.dataflow.InsecureRandomnessCustomizations
private import HeuristicSinks as Sinks
/**
* A heuristic sink for data flow in a security query.
*/
abstract class HeuristicSink extends DataFlow::Node { }
class HeuristicSink = Sinks::HeuristicSink;
private class HeuristicCodeInjectionSink extends HeuristicSink, CodeInjection::Sink {
HeuristicCodeInjectionSink() {
isAssignedTo(this, "$where")
or
isAssignedToOrConcatenatedWith(this, "(?i)(command|cmd|exec|code|script|program)")
or
isArgTo(this, "(?i)(eval|run)") // "exec" clashes too often with `RegExp.prototype.exec`
or
exists(string srcPattern |
// function/lambda syntax anywhere
srcPattern = "(?s).*function\\s*\\(.*\\).*" or
srcPattern = "(?s).*(\\(.*\\)|[A-Za-z_]+)\\s?=>.*"
|
isConcatenatedWithString(this, srcPattern)
)
or
// dynamic property name
isConcatenatedWithStrings("(?is)[a-z]+\\[", this, "(?s)\\].*")
}
}
private class HeuristicCodeInjectionSink extends Sinks::HeuristicCodeInjectionSink,
CodeInjection::Sink { }
private class HeuristicCommandInjectionSink extends HeuristicSink, CommandInjection::Sink {
HeuristicCommandInjectionSink() {

View File

@@ -0,0 +1,35 @@
/**
* Provides heuristically recognized sinks for security queries.
*/
import javascript
private import SyntacticHeuristics
/**
* A heuristic sink for data flow in a security query.
*/
abstract class HeuristicSink extends DataFlow::Node { }
/**
* A heuristically recognized sink for `js/code-injection` vulnerabilities.
*/
class HeuristicCodeInjectionSink extends HeuristicSink {
HeuristicCodeInjectionSink() {
isAssignedTo(this, "$where")
or
isAssignedToOrConcatenatedWith(this, "(?i)(command|cmd|exec|code|script|program)")
or
isArgTo(this, "(?i)(eval|run)") // "exec" clashes too often with `RegExp.prototype.exec`
or
exists(string srcPattern |
// function/lambda syntax anywhere
srcPattern = "(?s).*function\\s*\\(.*\\).*" or
srcPattern = "(?s).*(\\(.*\\)|[A-Za-z_]+)\\s?=>.*"
|
isConcatenatedWithString(this, srcPattern)
)
or
// dynamic property name
isConcatenatedWithStrings("(?is)[a-z]+\\[", this, "(?s)\\].*")
}
}

View File

@@ -0,0 +1,45 @@
/**
* Provides a dataflow tracking configuration for reasoning about
* storage of sensitive information in build artifact.
*
* Note, for performance reasons: only import this file if
* `CleartextLogging::Configuration` is needed, otherwise
* `CleartextLoggingCustomizations` should be imported instead.
*/
import javascript
/**
* Classes and predicates for storage of sensitive information in build artifact query.
*/
module BuildArtifactLeak {
import BuildArtifactLeakCustomizations::BuildArtifactLeak
import CleartextLoggingCustomizations::CleartextLogging as CleartextLogging
/**
* A taint tracking configuration for storage of sensitive information in build artifact.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "BuildArtifactLeak" }
override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel lbl) {
source.(CleartextLogging::Source).getLabel() = lbl
}
override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel lbl) {
sink.(Sink).getLabel() = lbl
}
override predicate isSanitizer(DataFlow::Node node) {
node instanceof CleartextLogging::Barrier
}
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
CleartextLogging::isSanitizerEdge(pred, succ)
}
override predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node trg) {
CleartextLogging::isAdditionalTaintStep(src, trg)
}
}
}

View File

@@ -0,0 +1,32 @@
/**
* Provides default sinks for reasoning about storage of sensitive information
* in build artifact, as well as extension points for adding your own.
*/
import javascript
private import semmle.javascript.dataflow.InferredTypes
private import semmle.javascript.security.SensitiveActions::HeuristicNames
/**
* Sinks for storage of sensitive information in build artifact.
*/
module BuildArtifactLeak {
/**
* A data flow sink for storage of sensitive information in a build artifact.
*/
abstract class Sink extends DataFlow::Node {
/**
* Gets a data-flow label that leaks information for this sink.
*/
DataFlow::FlowLabel getLabel() { result.isTaint() }
}
/**
* An instantiation of `webpack.DefintePlugin` that stores information in a compiled JavaScript file.
*/
class WebpackDefinePluginSink extends Sink {
WebpackDefinePluginSink() {
this = DataFlow::moduleMember("webpack", "DefinePlugin").getAnInstantiation().getAnArgument()
}
}
}

View File

@@ -35,23 +35,11 @@ module CleartextLogging {
override predicate isSanitizer(DataFlow::Node node) { node instanceof Barrier }
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
succ.(DataFlow::PropRead).getBase() = pred
CleartextLogging::isSanitizerEdge(pred, succ)
}
override predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node trg) {
// A taint propagating data flow edge through objects: a tainted write taints the entire object.
exists(DataFlow::PropWrite write |
write.getRhs() = src and
trg.(DataFlow::SourceNode).flowsTo(write.getBase())
)
or
// Taint through the arguments object.
exists(DataFlow::CallNode call, Function f |
src = call.getAnArgument() and
f = call.getACallee() and
not call.isImprecise() and
trg.asExpr() = f.getArgumentsVariable().getAnAccess()
)
CleartextLogging::isAdditionalTaintStep(src, trg)
}
}
}

View File

@@ -176,17 +176,50 @@ module CleartextLogging {
override string describe() { result = "process environment" }
override DataFlow::FlowLabel getLabel() {
result.isTaint() or
result instanceof PartiallySensitiveMap
}
override DataFlow::FlowLabel getLabel() { result.isTaint() }
}
/**
* A flow label describing a map that might contain sensitive information in some properties.
* Property reads on such maps where the property name is fixed is unlikely to leak sensitive information.
* Holds if the edge `pred` -> `succ` should be sanitized for clear-text logging of sensitive information.
*/
class PartiallySensitiveMap extends DataFlow::FlowLabel {
PartiallySensitiveMap() { this = "PartiallySensitiveMap" }
predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
succ.(DataFlow::PropRead).getBase() = pred
}
/**
* Holds if the edge `src` -> `trg` is an additional taint-step for clear-text logging of sensitive information.
*/
predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node trg) {
// A taint propagating data flow edge through objects: a tainted write taints the entire object.
exists(DataFlow::PropWrite write |
write.getRhs() = src and
trg.(DataFlow::SourceNode).flowsTo(write.getBase())
)
or
// A property-copy step,
// dst[x] = src[x]
// dst[x] = JSON.stringify(src[x])
exists(DataFlow::PropWrite write, DataFlow::PropRead read |
read = write.getRhs()
or
exists(DataFlow::MethodCallNode stringify |
stringify = write.getRhs() and
stringify = DataFlow::globalVarRef("JSON").getAMethodCall("stringify") and
stringify.getArgument(0) = read
)
|
not exists(write.getPropertyName()) and
not exists(read.getPropertyName()) and
src = read.getBase() and
trg = write.getBase().getALocalSource()
)
or
// Taint through the arguments object.
exists(DataFlow::CallNode call, Function f |
src = call.getAnArgument() and
f = call.getACallee() and
not call.isImprecise() and
trg.asExpr() = f.getArgumentsVariable().getAnAccess()
)
}
}

View File

@@ -0,0 +1,30 @@
/**
* Provides a taint-tracking configuration for reasoning about improper code
* sanitization.
*
* Note, for performance reasons: only import this file if
* `ImproperCodeSanitization::Configuration` is needed, otherwise
* `ImproperCodeSanitizationCustomizations` should be imported instead.
*/
import javascript
/**
* Classes and predicates for reasoning about improper code sanitization.
*/
module ImproperCodeSanitization {
import ImproperCodeSanitizationCustomizations::ImproperCodeSanitization
/**
* A taint-tracking configuration for reasoning about improper code sanitization vulnerabilities.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "ImproperCodeSanitization" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isSanitizer(DataFlow::Node sanitizer) { sanitizer instanceof Sanitizer }
}
}

View File

@@ -0,0 +1,68 @@
/**
* Provides default sources, sinks and sanitizers for reasoning about
* improper code sanitization, as well as extension points for
* adding your own.
*/
import javascript
/**
* Classes and predicates for reasoning about improper code sanitization.
*/
module ImproperCodeSanitization {
/**
* A data flow source for improper code sanitization.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for improper code sanitization.
*/
abstract class Sink extends DataFlow::Node { }
/**
* A sanitizer for improper code sanitization.
*/
abstract class Sanitizer extends DataFlow::Node { }
/**
* A call to a HTML sanitizer seen as a source for improper code sanitization
*/
class HtmlSanitizerCallAsSource extends Source {
HtmlSanitizerCallAsSource() { this instanceof HtmlSanitizerCall }
}
/**
* A call to `JSON.stringify()` seen as a source for improper code sanitization
*/
class JSONStringifyAsSource extends Source {
JSONStringifyAsSource() { this = DataFlow::globalVarRef("JSON").getAMemberCall("stringify") }
}
/**
* A leaf in a string-concatenation, where the string-concatenation constructs code that looks like a function.
*/
class FunctionStringConstruction extends Sink, StringOps::ConcatenationLeaf {
FunctionStringConstruction() {
exists(StringOps::ConcatenationRoot root, int i |
root.getOperand(i) = this and
not exists(this.getStringValue())
|
exists(StringOps::ConcatenationLeaf functionLeaf |
functionLeaf = root.getOperand(any(int j | j < i))
|
functionLeaf
.getStringValue()
.regexpMatch([".*function( )?([a-zA-Z0-9]+)?( )?\\(.*", ".*eval\\(.*",
".*new Function\\(.*", "(^|.*[^a-zA-Z0-9])\\(.*\\)( )?=>.*"])
)
)
}
}
/**
* A call to `String.prototype.replace` seen as a sanitizer for improper code sanitization.
* All calls to replace that happens after the initial improper sanitization is seen as a sanitizer.
*/
class StringReplaceCallAsSanitizer extends Sanitizer, StringReplaceCall { }
}

View File

@@ -50,14 +50,42 @@ module IndirectCommandInjection {
// `require('minimist')(...)` => `{ _: [], a: ... b: ... }`
this = DataFlow::moduleImport("minimist").getACall()
or
// `require('yargs').argv` => `{ _: [], a: ... b: ... }`
this = DataFlow::moduleMember("yargs", "argv")
or
// `require('optimist').argv` => `{ _: [], a: ... b: ... }`
this = DataFlow::moduleMember("optimist", "argv")
}
}
/**
* Gets an instance of `yargs`.
* Either directly imported as a module, or through some chained method call.
*/
private DataFlow::SourceNode yargs() {
result = DataFlow::moduleImport("yargs")
or
// script used to generate list of chained methods: https://gist.github.com/erik-krogh/f8afe952c0577f4b563a993e613269ba
exists(string method |
not method =
// the methods that does not return a chained `yargs` object.
["getContext", "getDemandedOptions", "getDemandedCommands", "getDeprecatedOptions",
"_getParseContext", "getOptions", "getGroups", "getStrict", "getStrictCommands",
"getExitProcess", "locale", "getUsageInstance", "getCommandInstance"]
|
result = yargs().getAMethodCall(method)
)
}
/**
* An array of command line arguments (`argv`) parsed by the `yargs` libary.
*/
class YargsArgv extends Source {
YargsArgv() {
this = yargs().getAPropertyRead("argv")
or
this = yargs().getAMethodCall("parse") and
this.(DataFlow::MethodCallNode).getNumArgument() = 0
}
}
/**
* A command-line argument that effectively is system-controlled, and therefore not likely to be exploitable when used in the execution of another command.
*/

View File

@@ -0,0 +1,27 @@
/**
* Provides a taint tracking configuration for reasoning about download of sensitive file through insecure connection.
*
* Note, for performance reasons: only import this file if
* `InsecureDownload::Configuration` is needed, otherwise
* `InsecureDownloadCustomizations` should be imported instead.
*/
import javascript
/**
* Classes and predicates for reasoning about download of sensitive file through insecure connection vulnerabilities.
*/
module InsecureDownload {
import InsecureDownloadCustomizations::InsecureDownload
/**
* A taint tracking configuration for download of sensitive file through insecure connection.
*/
class Configuration extends DataFlow::Configuration {
Configuration() { this = "InsecureDownload" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
}
}

View File

@@ -0,0 +1,70 @@
/**
* Provides default sources, sinks and sanitizers for reasoning about
* download of sensitive file through insecure connection, as well as
* extension points for adding your own.
*/
import javascript
/**
* Classes and predicates for reasoning about download of sensitive file through insecure connection vulnerabilities.
*/
module InsecureDownload {
/**
* A data flow source for download of sensitive file through insecure connection.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for download of sensitive file through insecure connection.
*/
abstract class Sink extends DataFlow::Node {
/**
* Gets the call that downloads the sensitive file.
*/
abstract DataFlow::Node getDownloadCall();
}
/**
* A sanitizer for download of sensitive file through insecure connection.
*/
abstract class Sanitizer extends DataFlow::Node { }
/**
* A HTTP or FTP URL that refers to a file with a sensitive file extension,
* seen as a source for download of sensitive file through insecure connection.
*/
class SensitiveFileUrl extends Source {
SensitiveFileUrl() {
exists(string str | str = this.getStringValue() |
str.regexpMatch("http://.*|ftp://.*") and
exists(string suffix | suffix = unsafeExtension() |
str.suffix(str.length() - suffix.length() - 1).toLowerCase() = "." + suffix
)
)
}
}
/**
* Gets a file-extension that can potentially be dangerous.
*
* Archives are included, because they often contain source-code.
*/
string unsafeExtension() {
result =
["exe", "dmg", "pkg", "tar.gz", "zip", "sh", "bat", "cmd", "app", "apk", "msi", "dmg",
"tar.gz", "zip", "js", "py", "jar", "war"]
}
/**
* A url downloaded by a client-request, seen as a sink for download of
* sensitive file through insecure connection.a
*/
class ClientRequestURL extends Sink {
ClientRequest request;
ClientRequestURL() { this = request.getUrl() }
override DataFlow::Node getDownloadCall() { result = request }
}
}

View File

@@ -36,16 +36,7 @@ module InsecureRandomness {
}
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
// Assume that all operations on tainted values preserve taint: crypto is hard
succ.asExpr().(BinaryExpr).getAnOperand() = pred.asExpr()
or
succ.asExpr().(UnaryExpr).getOperand() = pred.asExpr()
or
exists(DataFlow::MethodCallNode mc |
mc = DataFlow::globalVarRef("Math").getAMemberCall(_) and
pred = mc.getAnArgument() and
succ = mc
)
InsecureRandomness::isAdditionalTaintStep(pred, succ)
}
}
}

View File

@@ -30,39 +30,50 @@ module InsecureRandomness {
override InvokeExpr astNode;
DefaultSource() {
exists(DataFlow::ModuleImportNode mod, string name | mod.getPath() = name |
// require("random-number")();
name = "random-number" and
this = mod.getACall()
not this.getContainer() = getASecureRandomGeneratingFunction() and
(
exists(DataFlow::ModuleImportNode mod, string name | mod.getPath() = name |
// require("random-number")();
name = "random-number" and
this = mod.getACall()
or
// require("random-int")();
name = "random-int" and
this = mod.getACall()
or
// require("random-float")();
name = "random-float" and
this = mod.getACall()
or
// require('random-seed').create()();
name = "random-seed" and
this = mod.getAMemberCall("create").getACall()
or
// require('unique-random')()();
name = "unique-random" and
this = mod.getACall().getACall()
)
or
// require("random-int")();
name = "random-int" and
this = mod.getACall()
// Math.random()
this = DataFlow::globalVarRef("Math").getAMemberCall("random")
or
// require("random-float")();
name = "random-float" and
this = mod.getACall()
// (new require('chance')).<name>()
this = DataFlow::moduleImport("chance").getAnInstantiation().getAMemberInvocation(_)
or
// require('random-seed').create()();
name = "random-seed" and
this = mod.getAMemberCall("create").getACall()
or
// require('unique-random')()();
name = "unique-random" and
this = mod.getACall().getACall()
// require('crypto').pseudoRandomBytes()
this = DataFlow::moduleMember("crypto", "pseudoRandomBytes").getAnInvocation()
)
or
// Math.random()
this = DataFlow::globalVarRef("Math").getAMemberCall("random")
or
// (new require('chance')).<name>()
this = DataFlow::moduleImport("chance").getAnInstantiation().getAMemberInvocation(_)
or
// require('crypto').pseudoRandomBytes()
this = DataFlow::moduleMember("crypto", "pseudoRandomBytes").getAnInvocation()
}
}
/**
* Gets a container that at some point generates a secure random value.
*/
pragma[noinline]
private StmtContainer getASecureRandomGeneratingFunction() {
result = randomBufferSource().getContainer()
}
/**
* A sensitive write, considered as a sink for random values that are not cryptographically
* secure.
@@ -78,4 +89,40 @@ module InsecureRandomness {
class CryptoKeySink extends Sink {
CryptoKeySink() { this instanceof CryptographicKey }
}
/**
* Holds if the step `pred` -> `succ` is an additional taint-step for random values that are not cryptographically secure.
*/
predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
// Assume that all operations on tainted values preserve taint: crypto is hard
succ.asExpr().(BinaryExpr).getAnOperand() = pred.asExpr()
or
succ.asExpr().(UnaryExpr).getOperand() = pred.asExpr()
or
exists(DataFlow::MethodCallNode mc |
mc = DataFlow::globalVarRef("Math").getAMemberCall(_) and
pred = mc.getAnArgument() and
succ = mc
)
}
/**
* Gets a Buffer/TypedArray containing cryptographically secure random numbers.
*/
DataFlow::SourceNode randomBufferSource() {
result = DataFlow::moduleMember("crypto", ["randomBytes", "randomFillSync"]).getACall()
or
exists(DataFlow::CallNode call |
call = DataFlow::moduleMember("crypto", ["randomFill", "randomFillSync"]) and
result = call.getArgument(0).getALocalSource()
)
or
result = DataFlow::globalVarRef("crypto").getAMethodCall("getRandomValues")
or
result = DataFlow::moduleImport("secure-random").getACall()
or
result =
DataFlow::moduleImport("secure-random")
.getAMethodCall(["randomArray", "randomUint8Array", "randomBuffer"])
}
}

View File

@@ -212,11 +212,10 @@ module TaintedPath {
DataFlow::Node output;
PreservingPathCall() {
exists(string name | name = "dirname" or name = "toNamespacedPath" |
this = NodeJSLib::Path::moduleMember(name).getACall() and
input = getAnArgument() and
output = this
)
this =
NodeJSLib::Path::moduleMember(["dirname", "toNamespacedPath", "parse", "format"]).getACall() and
input = getAnArgument() and
output = this
or
// non-global replace or replace of something other than /\.\./g, /[/]/g, or /[\.]/g.
this.getCalleeName() = "replace" and

View File

@@ -4,3 +4,5 @@ test_locationRef
| customization.js:3:3:3:14 | doc.location |
test_domValueRef
| customization.js:4:3:4:28 | doc.get ... 'test') |
| tst.js:49:3:49:8 | window |
| tst.js:50:3:50:8 | window |

View File

@@ -0,0 +1,10 @@
/** @externs */
/**
* @constructor
* @name EventTarget
*/
function EventTarget() {}
/** @type {EventTarget} */
var window;

View File

@@ -39,3 +39,13 @@
factory2();
})();
(function pollute() {
class C {
foo() {
this.x; // Should not be a domValueRef
}
}
window.myApp = new C();
window.myApp.foo();
})();

View File

@@ -0,0 +1 @@
module.exports.foo = function() {};

View File

@@ -0,0 +1,3 @@
{
"main": "dist/does-not-exist.js"
}

View File

@@ -0,0 +1,3 @@
export function exported() {}
function notExported() {}

View File

@@ -0,0 +1,3 @@
{
"main": "main.js"
}

View File

@@ -1,6 +1,14 @@
getTopmostPackageJSON
| absent_main/package.json:1:1:3:1 | {\\n " ... t.js"\\n} |
| esmodules/package.json:1:1:3:1 | {\\n " ... n.js"\\n} |
| lib1/package.json:1:1:3:1 | {\\n " ... n.js"\\n} |
getAValueExportedBy
| absent_main/package.json:1:1:3:1 | {\\n " ... t.js"\\n} | absent_main/index.js:1:1:1:0 | this |
| absent_main/package.json:1:1:3:1 | {\\n " ... t.js"\\n} | absent_main/index.js:1:1:1:14 | module.exports |
| absent_main/package.json:1:1:3:1 | {\\n " ... t.js"\\n} | absent_main/index.js:1:1:1:18 | module.exports.foo |
| absent_main/package.json:1:1:3:1 | {\\n " ... t.js"\\n} | absent_main/index.js:1:22:1:21 | this |
| absent_main/package.json:1:1:3:1 | {\\n " ... t.js"\\n} | absent_main/index.js:1:22:1:34 | function() {} |
| esmodules/package.json:1:1:3:1 | {\\n " ... n.js"\\n} | esmodules/main.js:1:8:1:29 | functio ... ed() {} |
| lib1/package.json:1:1:3:1 | {\\n " ... n.js"\\n} | lib1/foo.js:1:1:1:0 | this |
| lib1/package.json:1:1:3:1 | {\\n " ... n.js"\\n} | lib1/foo.js:1:1:1:53 | module. ... in() {} |
| lib1/package.json:1:1:3:1 | {\\n " ... n.js"\\n} | lib1/foo.js:1:18:1:53 | functio ... in() {} |

View File

@@ -711,12 +711,14 @@
| (parameter 0 (member default (root https://www.npmjs.com/package/m2))) | src/m3/tst3.js:5:7:5:10 | "me" | false |
| (parameter 0 (member foo (root https://www.npmjs.com/package/m2))) | src/m3/tst2.js:5:5:5:12 | { x: o } | false |
| (parameter 0 (member m (instance (member default (root https://www.npmjs.com/package/m2))))) | src/m3/tst3.js:4:15:4:18 | "hi" | false |
| (parameter 0 (member m (instance (member default (root https://www.npmjs.com/package/m2))))) | src/m3/tst3.js:4:15:4:18 | "hi" | true |
| (parameter 0 (member m (instance (root https://www.npmjs.com/package/m2)))) | src/m3/tst3.js:4:15:4:18 | "hi" | false |
| (parameter 0 (member m (member default (root https://www.npmjs.com/package/m2)))) | src/m3/tst3.js:2:5:2:8 | "hi" | false |
| (parameter 0 (member m (return (member default (root https://www.npmjs.com/package/m2))))) | src/m3/tst3.js:4:15:4:18 | "hi" | false |
| (parameter 0 (member m (return (root https://www.npmjs.com/package/m2)))) | src/m3/tst3.js:4:15:4:18 | "hi" | false |
| (parameter 0 (member m (root https://www.npmjs.com/package/m2))) | src/m3/tst3.js:2:5:2:8 | "hi" | false |
| (parameter 0 (member s (instance (member default (root https://www.npmjs.com/package/m2))))) | src/m3/tst3.js:5:15:5:21 | "there" | false |
| (parameter 0 (member s (instance (member default (root https://www.npmjs.com/package/m2))))) | src/m3/tst3.js:5:15:5:21 | "there" | true |
| (parameter 0 (member s (instance (root https://www.npmjs.com/package/m2)))) | src/m3/tst3.js:5:15:5:21 | "there" | false |
| (parameter 0 (member s (member default (root https://www.npmjs.com/package/m2)))) | src/m3/tst3.js:3:5:3:11 | "there" | false |
| (parameter 0 (member s (return (member default (root https://www.npmjs.com/package/m2))))) | src/m3/tst3.js:5:15:5:21 | "there" | false |

View File

@@ -6,12 +6,15 @@
| (instance (member default (root https://www.npmjs.com/package/m2))) | src/m2/main.js:11:4:11:3 | this | true |
| (instance (member default (root https://www.npmjs.com/package/m2))) | src/m2/main.js:15:11:15:10 | this | true |
| (instance (member default (root https://www.npmjs.com/package/m2))) | src/m3/tst3.js:4:1:4:11 | new A("me") | false |
| (instance (member default (root https://www.npmjs.com/package/m2))) | src/m3/tst3.js:4:1:4:11 | new A("me") | true |
| (instance (member default (root https://www.npmjs.com/package/m2))) | src/m3/tst3.js:5:1:5:11 | new A("me") | false |
| (instance (member default (root https://www.npmjs.com/package/m2))) | src/m3/tst3.js:5:1:5:11 | new A("me") | true |
| (instance (root https://www.npmjs.com/package/m2)) | src/m3/tst3.js:4:1:4:11 | new A("me") | false |
| (instance (root https://www.npmjs.com/package/m2)) | src/m3/tst3.js:5:1:5:11 | new A("me") | false |
| (member default (root https://www.npmjs.com/package/m2)) | src/m3/tst3.js:1:8:1:8 | A | false |
| (member foo (root https://www.npmjs.com/package/m2)) | src/m3/tst2.js:1:10:1:12 | foo | false |
| (member m (instance (member default (root https://www.npmjs.com/package/m2)))) | src/m3/tst3.js:4:1:4:13 | new A("me").m | false |
| (member m (instance (member default (root https://www.npmjs.com/package/m2)))) | src/m3/tst3.js:4:1:4:13 | new A("me").m | true |
| (member m (instance (root https://www.npmjs.com/package/m2))) | src/m3/tst3.js:4:1:4:13 | new A("me").m | false |
| (member m (member default (root https://www.npmjs.com/package/m2))) | src/m3/tst3.js:2:1:2:3 | A.m | false |
| (member m (return (member default (root https://www.npmjs.com/package/m2)))) | src/m3/tst3.js:4:1:4:13 | new A("me").m | false |
@@ -19,6 +22,7 @@
| (member m (root https://www.npmjs.com/package/m2)) | src/m3/tst3.js:2:1:2:3 | A.m | false |
| (member name (instance (member default (root https://www.npmjs.com/package/m2)))) | src/m2/main.js:12:27:12:35 | this.name | true |
| (member s (instance (member default (root https://www.npmjs.com/package/m2)))) | src/m3/tst3.js:5:1:5:13 | new A("me").s | false |
| (member s (instance (member default (root https://www.npmjs.com/package/m2)))) | src/m3/tst3.js:5:1:5:13 | new A("me").s | true |
| (member s (instance (root https://www.npmjs.com/package/m2))) | src/m3/tst3.js:5:1:5:13 | new A("me").s | false |
| (member s (member default (root https://www.npmjs.com/package/m2))) | src/m3/tst3.js:3:1:3:3 | A.s | false |
| (member s (return (member default (root https://www.npmjs.com/package/m2)))) | src/m3/tst3.js:5:1:5:13 | new A("me").s | false |
@@ -734,12 +738,14 @@
| (return (member default (root https://www.npmjs.com/package/m2))) | src/m3/tst3.js:5:1:5:11 | new A("me") | false |
| (return (member foo (root https://www.npmjs.com/package/m2))) | src/m3/tst2.js:5:1:5:13 | foo({ x: o }) | false |
| (return (member m (instance (member default (root https://www.npmjs.com/package/m2))))) | src/m3/tst3.js:4:1:4:19 | new A("me").m("hi") | false |
| (return (member m (instance (member default (root https://www.npmjs.com/package/m2))))) | src/m3/tst3.js:4:1:4:19 | new A("me").m("hi") | true |
| (return (member m (instance (root https://www.npmjs.com/package/m2)))) | src/m3/tst3.js:4:1:4:19 | new A("me").m("hi") | false |
| (return (member m (member default (root https://www.npmjs.com/package/m2)))) | src/m3/tst3.js:2:1:2:9 | A.m("hi") | false |
| (return (member m (return (member default (root https://www.npmjs.com/package/m2))))) | src/m3/tst3.js:4:1:4:19 | new A("me").m("hi") | false |
| (return (member m (return (root https://www.npmjs.com/package/m2)))) | src/m3/tst3.js:4:1:4:19 | new A("me").m("hi") | false |
| (return (member m (root https://www.npmjs.com/package/m2))) | src/m3/tst3.js:2:1:2:9 | A.m("hi") | false |
| (return (member s (instance (member default (root https://www.npmjs.com/package/m2))))) | src/m3/tst3.js:5:1:5:22 | new A(" ... there") | false |
| (return (member s (instance (member default (root https://www.npmjs.com/package/m2))))) | src/m3/tst3.js:5:1:5:22 | new A(" ... there") | true |
| (return (member s (instance (root https://www.npmjs.com/package/m2)))) | src/m3/tst3.js:5:1:5:22 | new A(" ... there") | false |
| (return (member s (member default (root https://www.npmjs.com/package/m2)))) | src/m3/tst3.js:3:1:3:12 | A.s("there") | false |
| (return (member s (return (member default (root https://www.npmjs.com/package/m2))))) | src/m3/tst3.js:5:1:5:22 | new A(" ... there") | false |

View File

@@ -2,12 +2,12 @@ regexpTest
| tst.js:6:9:6:28 | /^[a-z]+$/.test(str) |
| tst.js:7:9:7:36 | /^[a-z] ... != null |
| tst.js:8:9:8:28 | /^[a-z]+$/.exec(str) |
| tst.js:9:9:9:31 | str.mat ... -z]+$/) |
| tst.js:10:9:10:31 | str.mat ... -z]+$") |
| tst.js:9:9:9:29 | str.mat ... -z]+$/) |
| tst.js:10:9:10:29 | str.mat ... -z]+$") |
| tst.js:12:9:12:24 | regexp.test(str) |
| tst.js:13:9:13:32 | regexp. ... != null |
| tst.js:14:9:14:24 | regexp.exec(str) |
| tst.js:15:9:15:27 | str.matches(regexp) |
| tst.js:15:9:15:25 | str.match(regexp) |
| tst.js:18:9:18:13 | match |
| tst.js:19:10:19:14 | match |
| tst.js:20:9:20:21 | match == null |
@@ -15,18 +15,20 @@ regexpTest
| tst.js:22:9:22:13 | match |
| tst.js:25:23:25:27 | match |
| tst.js:29:21:29:36 | regexp.test(str) |
| tst.js:33:21:33:39 | str.matches(regexp) |
| tst.js:33:23:33:39 | str.match(regexp) |
| tst.js:40:9:40:37 | regexp. ... defined |
| tst.js:44:9:44:14 | match2 |
| tst.js:45:10:45:15 | match2 |
#select
| tst.js:6:9:6:28 | /^[a-z]+$/.test(str) | tst.js:6:10:6:17 | ^[a-z]+$ | tst.js:6:9:6:18 | /^[a-z]+$/ | tst.js:6:25:6:27 | str | true |
| tst.js:7:9:7:36 | /^[a-z] ... != null | tst.js:7:10:7:17 | ^[a-z]+$ | tst.js:7:9:7:18 | /^[a-z]+$/ | tst.js:7:25:7:27 | str | true |
| tst.js:8:9:8:28 | /^[a-z]+$/.exec(str) | tst.js:8:10:8:17 | ^[a-z]+$ | tst.js:8:9:8:18 | /^[a-z]+$/ | tst.js:8:25:8:27 | str | true |
| tst.js:9:9:9:31 | str.mat ... -z]+$/) | tst.js:9:22:9:29 | ^[a-z]+$ | tst.js:9:21:9:30 | /^[a-z]+$/ | tst.js:9:9:9:11 | str | true |
| tst.js:10:9:10:31 | str.mat ... -z]+$") | tst.js:10:22:10:29 | ^[a-z]+$ | tst.js:10:21:10:30 | "^[a-z]+$" | tst.js:10:9:10:11 | str | true |
| tst.js:9:9:9:29 | str.mat ... -z]+$/) | tst.js:9:20:9:27 | ^[a-z]+$ | tst.js:9:19:9:28 | /^[a-z]+$/ | tst.js:9:9:9:11 | str | true |
| tst.js:10:9:10:29 | str.mat ... -z]+$") | tst.js:10:20:10:27 | ^[a-z]+$ | tst.js:10:19:10:28 | "^[a-z]+$" | tst.js:10:9:10:11 | str | true |
| tst.js:12:9:12:24 | regexp.test(str) | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:12:9:12:14 | regexp | tst.js:12:21:12:23 | str | true |
| tst.js:13:9:13:32 | regexp. ... != null | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:13:9:13:14 | regexp | tst.js:13:21:13:23 | str | true |
| tst.js:14:9:14:24 | regexp.exec(str) | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:14:9:14:14 | regexp | tst.js:14:21:14:23 | str | true |
| tst.js:15:9:15:27 | str.matches(regexp) | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:15:21:15:26 | regexp | tst.js:15:9:15:11 | str | true |
| tst.js:15:9:15:25 | str.match(regexp) | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:15:19:15:24 | regexp | tst.js:15:9:15:11 | str | true |
| tst.js:18:9:18:13 | match | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:17:17:17:22 | regexp | tst.js:17:29:17:31 | str | true |
| tst.js:19:10:19:14 | match | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:17:17:17:22 | regexp | tst.js:17:29:17:31 | str | true |
| tst.js:20:9:20:21 | match == null | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:17:17:17:22 | regexp | tst.js:17:29:17:31 | str | false |
@@ -34,5 +36,7 @@ regexpTest
| tst.js:22:9:22:13 | match | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:17:17:17:22 | regexp | tst.js:17:29:17:31 | str | true |
| tst.js:25:23:25:27 | match | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:17:17:17:22 | regexp | tst.js:17:29:17:31 | str | true |
| tst.js:29:21:29:36 | regexp.test(str) | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:29:21:29:26 | regexp | tst.js:29:33:29:35 | str | true |
| tst.js:33:21:33:39 | str.matches(regexp) | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:33:33:33:38 | regexp | tst.js:33:21:33:23 | str | true |
| tst.js:33:23:33:39 | str.match(regexp) | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:33:33:33:38 | regexp | tst.js:33:23:33:25 | str | true |
| tst.js:40:9:40:37 | regexp. ... defined | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:40:9:40:14 | regexp | tst.js:40:21:40:23 | str | false |
| tst.js:44:9:44:14 | match2 | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:43:28:43:33 | regexp | tst.js:43:18:43:20 | str | true |
| tst.js:45:10:45:15 | match2 | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:43:28:43:33 | regexp | tst.js:43:18:43:20 | str | true |

View File

@@ -6,13 +6,13 @@ function f(str) {
if (/^[a-z]+$/.test(str)) {}
if (/^[a-z]+$/.exec(str) != null) {}
if (/^[a-z]+$/.exec(str)) {}
if (str.matches(/^[a-z]+$/)) {}
if (str.matches("^[a-z]+$")) {}
if (str.match(/^[a-z]+$/)) {}
if (str.match("^[a-z]+$")) {}
if (regexp.test(str)) {}
if (regexp.exec(str) != null) {}
if (regexp.exec(str)) {}
if (str.matches(regexp)) {}
if (str.match(regexp)) {}
let match = regexp.exec(str);
if (match) {}
@@ -30,7 +30,7 @@ function f(str) {
});
something({
someOption: str.matches(regexp)
someOption: !!str.match(regexp)
});
something({
@@ -39,4 +39,13 @@ function f(str) {
if (regexp.exec(str) == undefined) {}
if (regexp.exec(str) === undefined) {} // not recognized as RegExpTest
let match2 = str.match(regexp);
if (match2) {}
if (!match2) {}
}
function something() {}
f("some string");
f("someotherstring");

View File

@@ -15,3 +15,5 @@
| tst.js:14:1:14:48 | require ... ", arg) | tst.js:14:45:14:47 | arg |
| tst.js:16:1:16:35 | console ... ", arg) | tst.js:16:22:16:29 | "msg %s" |
| tst.js:16:1:16:35 | console ... ", arg) | tst.js:16:32:16:34 | arg |
| tst.js:19:1:19:18 | log("msg %s", arg) | tst.js:19:5:19:12 | "msg %s" |
| tst.js:19:1:19:18 | log("msg %s", arg) | tst.js:19:15:19:17 | arg |

View File

@@ -14,3 +14,6 @@ require("winston").createLogger().info("msg %s", arg);
require("log4js").getLogger().log("msg %s", arg);
console.assert(true, "msg %s", arg);
let log = console.log;
log("msg %s", arg);

View File

@@ -0,0 +1,19 @@
routeHandler
| tst.js:5:7:10:1 | async ( ... llo";\\n} |
| tst.js:12:1:15:1 | functio ... nse";\\n} |
requestSource
| tst.js:5:14:5:16 | req |
| tst.js:12:26:12:28 | req |
responseSource
| tst.js:5:19:5:21 | res |
| tst.js:12:31:12:33 | res |
requestInputAccess
| body | tst.js:7:5:7:19 | micro.json(req) |
| header | tst.js:6:5:6:31 | req.hea ... -type'] |
| header | tst.js:13:5:13:31 | req.hea ... -type'] |
userControlledObject
| tst.js:7:5:7:19 | micro.json(req) |
responseSendArgument
| tst.js:8:31:8:36 | "data" |
responseSendArgumentHandler
| tst.js:5:7:10:1 | async ( ... llo";\\n} | tst.js:8:31:8:36 | "data" |

View File

@@ -0,0 +1,17 @@
import javascript
query HTTP::RouteHandler routeHandler() { any() }
query HTTP::Servers::RequestSource requestSource() { any() }
query HTTP::Servers::ResponseSource responseSource() { any() }
query HTTP::RequestInputAccess requestInputAccess(string kind) { kind = result.getKind() }
query HTTP::RequestInputAccess userControlledObject() { result.isUserControlledObject() }
query HTTP::ResponseSendArgument responseSendArgument() { any() }
query HTTP::ResponseSendArgument responseSendArgumentHandler(HTTP::RouteHandler h) {
h = result.getRouteHandler()
}

View File

@@ -0,0 +1,19 @@
const micro = require('micro')
const bluebird = require('bluebird');
const compress = require('micro-compress');
micro(async (req, res) => {
req.headers['content-type'];
micro.json(req);
micro.sendError(req, res, "data");
return "Hello";
})
function* wrappedHandler(req, res) {
req.headers['content-type'];
yield "Response";
}
let handler = bluebird.coroutine(wrappedHandler);
micro(compress(handler));

View File

@@ -48,6 +48,11 @@ nodes
| child_process-test.js:70:25:70:31 | req.url |
| child_process-test.js:72:29:72:31 | cmd |
| child_process-test.js:72:29:72:31 | cmd |
| child_process-test.js:80:19:80:36 | req.query.fileName |
| child_process-test.js:80:19:80:36 | req.query.fileName |
| child_process-test.js:80:19:80:36 | req.query.fileName |
| child_process-test.js:82:37:82:54 | req.query.fileName |
| child_process-test.js:82:37:82:54 | req.query.fileName |
| execSeries.js:3:20:3:22 | arr |
| execSeries.js:6:14:6:16 | arr |
| execSeries.js:6:14:6:21 | arr[i++] |
@@ -64,6 +69,10 @@ nodes
| execSeries.js:18:34:18:40 | req.url |
| execSeries.js:19:12:19:16 | [cmd] |
| execSeries.js:19:13:19:15 | cmd |
| lib/subLib/index.js:7:32:7:35 | name |
| lib/subLib/index.js:8:10:8:25 | "rm -rf " + name |
| lib/subLib/index.js:8:10:8:25 | "rm -rf " + name |
| lib/subLib/index.js:8:22:8:25 | name |
| other.js:5:9:5:49 | cmd |
| other.js:5:15:5:38 | url.par ... , true) |
| other.js:5:15:5:44 | url.par ... ).query |
@@ -152,6 +161,9 @@ edges
| child_process-test.js:70:15:70:49 | url.par ... ry.path | child_process-test.js:70:9:70:49 | cmd |
| child_process-test.js:70:25:70:31 | req.url | child_process-test.js:70:15:70:38 | url.par ... , true) |
| child_process-test.js:70:25:70:31 | req.url | child_process-test.js:70:15:70:38 | url.par ... , true) |
| child_process-test.js:80:19:80:36 | req.query.fileName | child_process-test.js:80:19:80:36 | req.query.fileName |
| child_process-test.js:82:37:82:54 | req.query.fileName | lib/subLib/index.js:7:32:7:35 | name |
| child_process-test.js:82:37:82:54 | req.query.fileName | lib/subLib/index.js:7:32:7:35 | name |
| execSeries.js:3:20:3:22 | arr | execSeries.js:6:14:6:16 | arr |
| execSeries.js:6:14:6:16 | arr | execSeries.js:6:14:6:21 | arr[i++] |
| execSeries.js:6:14:6:21 | arr[i++] | execSeries.js:14:24:14:30 | command |
@@ -168,6 +180,9 @@ edges
| execSeries.js:18:34:18:40 | req.url | execSeries.js:18:13:18:47 | require ... , true) |
| execSeries.js:19:12:19:16 | [cmd] | execSeries.js:13:19:13:26 | commands |
| execSeries.js:19:13:19:15 | cmd | execSeries.js:19:12:19:16 | [cmd] |
| lib/subLib/index.js:7:32:7:35 | name | lib/subLib/index.js:8:22:8:25 | name |
| lib/subLib/index.js:8:22:8:25 | name | lib/subLib/index.js:8:10:8:25 | "rm -rf " + name |
| lib/subLib/index.js:8:22:8:25 | name | lib/subLib/index.js:8:10:8:25 | "rm -rf " + name |
| other.js:5:9:5:49 | cmd | other.js:7:33:7:35 | cmd |
| other.js:5:9:5:49 | cmd | other.js:7:33:7:35 | cmd |
| other.js:5:9:5:49 | cmd | other.js:8:28:8:30 | cmd |
@@ -228,7 +243,9 @@ edges
| child_process-test.js:59:5:59:39 | cp.exec ... , args) | child_process-test.js:6:25:6:31 | req.url | child_process-test.js:50:15:50:17 | cmd | This command depends on $@. | child_process-test.js:6:25:6:31 | req.url | a user-provided value |
| child_process-test.js:64:3:64:21 | cp.spawn(cmd, args) | child_process-test.js:6:25:6:31 | req.url | child_process-test.js:43:15:43:17 | cmd | This command depends on $@. | child_process-test.js:6:25:6:31 | req.url | a user-provided value |
| child_process-test.js:72:29:72:31 | cmd | child_process-test.js:70:25:70:31 | req.url | child_process-test.js:72:29:72:31 | cmd | This command depends on $@. | child_process-test.js:70:25:70:31 | req.url | a user-provided value |
| child_process-test.js:80:19:80:36 | req.query.fileName | child_process-test.js:80:19:80:36 | req.query.fileName | child_process-test.js:80:19:80:36 | req.query.fileName | This command depends on $@. | child_process-test.js:80:19:80:36 | req.query.fileName | a user-provided value |
| execSeries.js:14:41:14:47 | command | execSeries.js:18:34:18:40 | req.url | execSeries.js:14:41:14:47 | command | This command depends on $@. | execSeries.js:18:34:18:40 | req.url | a user-provided value |
| lib/subLib/index.js:8:10:8:25 | "rm -rf " + name | child_process-test.js:82:37:82:54 | req.query.fileName | lib/subLib/index.js:8:10:8:25 | "rm -rf " + name | This command depends on $@. | child_process-test.js:82:37:82:54 | req.query.fileName | a user-provided value |
| other.js:7:33:7:35 | cmd | other.js:5:25:5:31 | req.url | other.js:7:33:7:35 | cmd | This command depends on $@. | other.js:5:25:5:31 | req.url | a user-provided value |
| other.js:8:28:8:30 | cmd | other.js:5:25:5:31 | req.url | other.js:8:28:8:30 | cmd | This command depends on $@. | other.js:5:25:5:31 | req.url | a user-provided value |
| other.js:9:32:9:34 | cmd | other.js:5:25:5:31 | req.url | other.js:9:32:9:34 | cmd | This command depends on $@. | other.js:5:25:5:31 | req.url | a user-provided value |

View File

@@ -68,6 +68,58 @@ nodes
| command-line-parameter-command-injection.js:33:21:33:44 | require ... ").argv |
| command-line-parameter-command-injection.js:33:21:33:44 | require ... ").argv |
| command-line-parameter-command-injection.js:33:21:33:48 | require ... rgv.foo |
| command-line-parameter-command-injection.js:36:6:39:7 | args |
| command-line-parameter-command-injection.js:36:13:39:7 | require ... \\t\\t.argv |
| command-line-parameter-command-injection.js:36:13:39:7 | require ... \\t\\t.argv |
| command-line-parameter-command-injection.js:41:10:41:25 | "cmd.sh " + args |
| command-line-parameter-command-injection.js:41:10:41:25 | "cmd.sh " + args |
| command-line-parameter-command-injection.js:41:22:41:25 | args |
| command-line-parameter-command-injection.js:43:10:43:62 | "cmd.sh ... e().foo |
| command-line-parameter-command-injection.js:43:10:43:62 | "cmd.sh ... e().foo |
| command-line-parameter-command-injection.js:43:22:43:58 | require ... parse() |
| command-line-parameter-command-injection.js:43:22:43:58 | require ... parse() |
| command-line-parameter-command-injection.js:43:22:43:62 | require ... e().foo |
| command-line-parameter-command-injection.js:47:8:53:12 | args |
| command-line-parameter-command-injection.js:48:3:50:3 | argv: { ... rgs\\n\\t\\t} |
| command-line-parameter-command-injection.js:48:3:50:3 | argv: { ... rgs\\n\\t\\t} |
| command-line-parameter-command-injection.js:48:9:50:3 | {\\n\\t\\t\\t...args\\n\\t\\t} |
| command-line-parameter-command-injection.js:55:10:55:25 | "cmd.sh " + args |
| command-line-parameter-command-injection.js:55:10:55:25 | "cmd.sh " + args |
| command-line-parameter-command-injection.js:55:22:55:25 | args |
| command-line-parameter-command-injection.js:57:6:57:37 | tainted1 |
| command-line-parameter-command-injection.js:57:17:57:37 | require ... ').argv |
| command-line-parameter-command-injection.js:57:17:57:37 | require ... ').argv |
| command-line-parameter-command-injection.js:58:6:58:40 | tainted2 |
| command-line-parameter-command-injection.js:58:17:58:40 | require ... parse() |
| command-line-parameter-command-injection.js:58:17:58:40 | require ... parse() |
| command-line-parameter-command-injection.js:60:8:63:2 | taint1rest |
| command-line-parameter-command-injection.js:60:8:63:2 | taint2rest |
| command-line-parameter-command-injection.js:60:9:60:31 | taint1: ... t1rest} |
| command-line-parameter-command-injection.js:60:17:60:31 | {...taint1rest} |
| command-line-parameter-command-injection.js:60:33:60:55 | taint2: ... t2rest} |
| command-line-parameter-command-injection.js:60:41:60:55 | {...taint2rest} |
| command-line-parameter-command-injection.js:61:11:61:18 | tainted1 |
| command-line-parameter-command-injection.js:62:11:62:18 | tainted2 |
| command-line-parameter-command-injection.js:65:10:65:31 | "cmd.sh ... nt1rest |
| command-line-parameter-command-injection.js:65:10:65:31 | "cmd.sh ... nt1rest |
| command-line-parameter-command-injection.js:65:22:65:31 | taint1rest |
| command-line-parameter-command-injection.js:66:10:66:31 | "cmd.sh ... nt2rest |
| command-line-parameter-command-injection.js:66:10:66:31 | "cmd.sh ... nt2rest |
| command-line-parameter-command-injection.js:66:22:66:31 | taint2rest |
| command-line-parameter-command-injection.js:68:6:68:16 | {...taint3} |
| command-line-parameter-command-injection.js:68:6:68:40 | taint3 |
| command-line-parameter-command-injection.js:68:20:68:40 | require ... ').argv |
| command-line-parameter-command-injection.js:68:20:68:40 | require ... ').argv |
| command-line-parameter-command-injection.js:69:10:69:27 | "cmd.sh " + taint3 |
| command-line-parameter-command-injection.js:69:10:69:27 | "cmd.sh " + taint3 |
| command-line-parameter-command-injection.js:69:22:69:27 | taint3 |
| command-line-parameter-command-injection.js:71:6:71:16 | [...taint4] |
| command-line-parameter-command-injection.js:71:6:71:40 | taint4 |
| command-line-parameter-command-injection.js:71:20:71:40 | require ... ').argv |
| command-line-parameter-command-injection.js:71:20:71:40 | require ... ').argv |
| command-line-parameter-command-injection.js:72:10:72:27 | "cmd.sh " + taint4 |
| command-line-parameter-command-injection.js:72:10:72:27 | "cmd.sh " + taint4 |
| command-line-parameter-command-injection.js:72:22:72:27 | taint4 |
edges
| command-line-parameter-command-injection.js:4:10:4:21 | process.argv | command-line-parameter-command-injection.js:4:10:4:21 | process.argv |
| command-line-parameter-command-injection.js:8:22:8:33 | process.argv | command-line-parameter-command-injection.js:8:22:8:36 | process.argv[2] |
@@ -129,6 +181,51 @@ edges
| command-line-parameter-command-injection.js:33:21:33:44 | require ... ").argv | command-line-parameter-command-injection.js:33:21:33:48 | require ... rgv.foo |
| command-line-parameter-command-injection.js:33:21:33:48 | require ... rgv.foo | command-line-parameter-command-injection.js:33:9:33:48 | "cmd.sh ... rgv.foo |
| command-line-parameter-command-injection.js:33:21:33:48 | require ... rgv.foo | command-line-parameter-command-injection.js:33:9:33:48 | "cmd.sh ... rgv.foo |
| command-line-parameter-command-injection.js:36:6:39:7 | args | command-line-parameter-command-injection.js:41:22:41:25 | args |
| command-line-parameter-command-injection.js:36:13:39:7 | require ... \\t\\t.argv | command-line-parameter-command-injection.js:36:6:39:7 | args |
| command-line-parameter-command-injection.js:36:13:39:7 | require ... \\t\\t.argv | command-line-parameter-command-injection.js:36:6:39:7 | args |
| command-line-parameter-command-injection.js:41:22:41:25 | args | command-line-parameter-command-injection.js:41:10:41:25 | "cmd.sh " + args |
| command-line-parameter-command-injection.js:41:22:41:25 | args | command-line-parameter-command-injection.js:41:10:41:25 | "cmd.sh " + args |
| command-line-parameter-command-injection.js:43:22:43:58 | require ... parse() | command-line-parameter-command-injection.js:43:22:43:62 | require ... e().foo |
| command-line-parameter-command-injection.js:43:22:43:58 | require ... parse() | command-line-parameter-command-injection.js:43:22:43:62 | require ... e().foo |
| command-line-parameter-command-injection.js:43:22:43:62 | require ... e().foo | command-line-parameter-command-injection.js:43:10:43:62 | "cmd.sh ... e().foo |
| command-line-parameter-command-injection.js:43:22:43:62 | require ... e().foo | command-line-parameter-command-injection.js:43:10:43:62 | "cmd.sh ... e().foo |
| command-line-parameter-command-injection.js:47:8:53:12 | args | command-line-parameter-command-injection.js:55:22:55:25 | args |
| command-line-parameter-command-injection.js:48:3:50:3 | argv: { ... rgs\\n\\t\\t} | command-line-parameter-command-injection.js:48:9:50:3 | {\\n\\t\\t\\t...args\\n\\t\\t} |
| command-line-parameter-command-injection.js:48:3:50:3 | argv: { ... rgs\\n\\t\\t} | command-line-parameter-command-injection.js:48:9:50:3 | {\\n\\t\\t\\t...args\\n\\t\\t} |
| command-line-parameter-command-injection.js:48:9:50:3 | {\\n\\t\\t\\t...args\\n\\t\\t} | command-line-parameter-command-injection.js:47:8:53:12 | args |
| command-line-parameter-command-injection.js:55:22:55:25 | args | command-line-parameter-command-injection.js:55:10:55:25 | "cmd.sh " + args |
| command-line-parameter-command-injection.js:55:22:55:25 | args | command-line-parameter-command-injection.js:55:10:55:25 | "cmd.sh " + args |
| command-line-parameter-command-injection.js:57:6:57:37 | tainted1 | command-line-parameter-command-injection.js:61:11:61:18 | tainted1 |
| command-line-parameter-command-injection.js:57:17:57:37 | require ... ').argv | command-line-parameter-command-injection.js:57:6:57:37 | tainted1 |
| command-line-parameter-command-injection.js:57:17:57:37 | require ... ').argv | command-line-parameter-command-injection.js:57:6:57:37 | tainted1 |
| command-line-parameter-command-injection.js:58:6:58:40 | tainted2 | command-line-parameter-command-injection.js:62:11:62:18 | tainted2 |
| command-line-parameter-command-injection.js:58:17:58:40 | require ... parse() | command-line-parameter-command-injection.js:58:6:58:40 | tainted2 |
| command-line-parameter-command-injection.js:58:17:58:40 | require ... parse() | command-line-parameter-command-injection.js:58:6:58:40 | tainted2 |
| command-line-parameter-command-injection.js:60:8:63:2 | taint1rest | command-line-parameter-command-injection.js:65:22:65:31 | taint1rest |
| command-line-parameter-command-injection.js:60:8:63:2 | taint2rest | command-line-parameter-command-injection.js:66:22:66:31 | taint2rest |
| command-line-parameter-command-injection.js:60:9:60:31 | taint1: ... t1rest} | command-line-parameter-command-injection.js:60:17:60:31 | {...taint1rest} |
| command-line-parameter-command-injection.js:60:17:60:31 | {...taint1rest} | command-line-parameter-command-injection.js:60:8:63:2 | taint1rest |
| command-line-parameter-command-injection.js:60:33:60:55 | taint2: ... t2rest} | command-line-parameter-command-injection.js:60:41:60:55 | {...taint2rest} |
| command-line-parameter-command-injection.js:60:41:60:55 | {...taint2rest} | command-line-parameter-command-injection.js:60:8:63:2 | taint2rest |
| command-line-parameter-command-injection.js:61:11:61:18 | tainted1 | command-line-parameter-command-injection.js:60:9:60:31 | taint1: ... t1rest} |
| command-line-parameter-command-injection.js:62:11:62:18 | tainted2 | command-line-parameter-command-injection.js:60:33:60:55 | taint2: ... t2rest} |
| command-line-parameter-command-injection.js:65:22:65:31 | taint1rest | command-line-parameter-command-injection.js:65:10:65:31 | "cmd.sh ... nt1rest |
| command-line-parameter-command-injection.js:65:22:65:31 | taint1rest | command-line-parameter-command-injection.js:65:10:65:31 | "cmd.sh ... nt1rest |
| command-line-parameter-command-injection.js:66:22:66:31 | taint2rest | command-line-parameter-command-injection.js:66:10:66:31 | "cmd.sh ... nt2rest |
| command-line-parameter-command-injection.js:66:22:66:31 | taint2rest | command-line-parameter-command-injection.js:66:10:66:31 | "cmd.sh ... nt2rest |
| command-line-parameter-command-injection.js:68:6:68:16 | {...taint3} | command-line-parameter-command-injection.js:68:6:68:40 | taint3 |
| command-line-parameter-command-injection.js:68:6:68:40 | taint3 | command-line-parameter-command-injection.js:69:22:69:27 | taint3 |
| command-line-parameter-command-injection.js:68:20:68:40 | require ... ').argv | command-line-parameter-command-injection.js:68:6:68:16 | {...taint3} |
| command-line-parameter-command-injection.js:68:20:68:40 | require ... ').argv | command-line-parameter-command-injection.js:68:6:68:16 | {...taint3} |
| command-line-parameter-command-injection.js:69:22:69:27 | taint3 | command-line-parameter-command-injection.js:69:10:69:27 | "cmd.sh " + taint3 |
| command-line-parameter-command-injection.js:69:22:69:27 | taint3 | command-line-parameter-command-injection.js:69:10:69:27 | "cmd.sh " + taint3 |
| command-line-parameter-command-injection.js:71:6:71:16 | [...taint4] | command-line-parameter-command-injection.js:71:6:71:40 | taint4 |
| command-line-parameter-command-injection.js:71:6:71:40 | taint4 | command-line-parameter-command-injection.js:72:22:72:27 | taint4 |
| command-line-parameter-command-injection.js:71:20:71:40 | require ... ').argv | command-line-parameter-command-injection.js:71:6:71:16 | [...taint4] |
| command-line-parameter-command-injection.js:71:20:71:40 | require ... ').argv | command-line-parameter-command-injection.js:71:6:71:16 | [...taint4] |
| command-line-parameter-command-injection.js:72:22:72:27 | taint4 | command-line-parameter-command-injection.js:72:10:72:27 | "cmd.sh " + taint4 |
| command-line-parameter-command-injection.js:72:22:72:27 | taint4 | command-line-parameter-command-injection.js:72:10:72:27 | "cmd.sh " + taint4 |
#select
| command-line-parameter-command-injection.js:4:10:4:21 | process.argv | command-line-parameter-command-injection.js:4:10:4:21 | process.argv | command-line-parameter-command-injection.js:4:10:4:21 | process.argv | This command depends on an unsanitized $@. | command-line-parameter-command-injection.js:4:10:4:21 | process.argv | command-line argument |
| command-line-parameter-command-injection.js:8:10:8:36 | "cmd.sh ... argv[2] | command-line-parameter-command-injection.js:8:22:8:33 | process.argv | command-line-parameter-command-injection.js:8:10:8:36 | "cmd.sh ... argv[2] | This command depends on an unsanitized $@. | command-line-parameter-command-injection.js:8:22:8:33 | process.argv | command-line argument |
@@ -144,3 +241,10 @@ edges
| command-line-parameter-command-injection.js:31:9:31:45 | "cmd.sh ... )().foo | command-line-parameter-command-injection.js:31:21:31:41 | require ... ist")() | command-line-parameter-command-injection.js:31:9:31:45 | "cmd.sh ... )().foo | This command depends on an unsanitized $@. | command-line-parameter-command-injection.js:31:21:31:41 | require ... ist")() | command-line argument |
| command-line-parameter-command-injection.js:32:9:32:45 | "cmd.sh ... rgv.foo | command-line-parameter-command-injection.js:32:21:32:41 | require ... ").argv | command-line-parameter-command-injection.js:32:9:32:45 | "cmd.sh ... rgv.foo | This command depends on an unsanitized $@. | command-line-parameter-command-injection.js:32:21:32:41 | require ... ").argv | command-line argument |
| command-line-parameter-command-injection.js:33:9:33:48 | "cmd.sh ... rgv.foo | command-line-parameter-command-injection.js:33:21:33:44 | require ... ").argv | command-line-parameter-command-injection.js:33:9:33:48 | "cmd.sh ... rgv.foo | This command depends on an unsanitized $@. | command-line-parameter-command-injection.js:33:21:33:44 | require ... ").argv | command-line argument |
| command-line-parameter-command-injection.js:41:10:41:25 | "cmd.sh " + args | command-line-parameter-command-injection.js:36:13:39:7 | require ... \\t\\t.argv | command-line-parameter-command-injection.js:41:10:41:25 | "cmd.sh " + args | This command depends on an unsanitized $@. | command-line-parameter-command-injection.js:36:13:39:7 | require ... \\t\\t.argv | command-line argument |
| command-line-parameter-command-injection.js:43:10:43:62 | "cmd.sh ... e().foo | command-line-parameter-command-injection.js:43:22:43:58 | require ... parse() | command-line-parameter-command-injection.js:43:10:43:62 | "cmd.sh ... e().foo | This command depends on an unsanitized $@. | command-line-parameter-command-injection.js:43:22:43:58 | require ... parse() | command-line argument |
| command-line-parameter-command-injection.js:55:10:55:25 | "cmd.sh " + args | command-line-parameter-command-injection.js:48:3:50:3 | argv: { ... rgs\\n\\t\\t} | command-line-parameter-command-injection.js:55:10:55:25 | "cmd.sh " + args | This command depends on an unsanitized $@. | command-line-parameter-command-injection.js:48:3:50:3 | argv: { ... rgs\\n\\t\\t} | command-line argument |
| command-line-parameter-command-injection.js:65:10:65:31 | "cmd.sh ... nt1rest | command-line-parameter-command-injection.js:57:17:57:37 | require ... ').argv | command-line-parameter-command-injection.js:65:10:65:31 | "cmd.sh ... nt1rest | This command depends on an unsanitized $@. | command-line-parameter-command-injection.js:57:17:57:37 | require ... ').argv | command-line argument |
| command-line-parameter-command-injection.js:66:10:66:31 | "cmd.sh ... nt2rest | command-line-parameter-command-injection.js:58:17:58:40 | require ... parse() | command-line-parameter-command-injection.js:66:10:66:31 | "cmd.sh ... nt2rest | This command depends on an unsanitized $@. | command-line-parameter-command-injection.js:58:17:58:40 | require ... parse() | command-line argument |
| command-line-parameter-command-injection.js:69:10:69:27 | "cmd.sh " + taint3 | command-line-parameter-command-injection.js:68:20:68:40 | require ... ').argv | command-line-parameter-command-injection.js:69:10:69:27 | "cmd.sh " + taint3 | This command depends on an unsanitized $@. | command-line-parameter-command-injection.js:68:20:68:40 | require ... ').argv | command-line argument |
| command-line-parameter-command-injection.js:72:10:72:27 | "cmd.sh " + taint4 | command-line-parameter-command-injection.js:71:20:71:40 | require ... ').argv | command-line-parameter-command-injection.js:72:10:72:27 | "cmd.sh " + taint4 | This command depends on an unsanitized $@. | command-line-parameter-command-injection.js:71:20:71:40 | require ... ').argv | command-line argument |

View File

@@ -72,3 +72,14 @@ http.createServer(function(req, res) {
util.promisify(cp.exec)(cmd); // NOT OK
});
const webpackDevServer = require('webpack-dev-server');
new webpackDevServer(compiler, {
before: function (app) {
app.use(function (req, res, next) {
cp.exec(req.query.fileName); // NOT OK
require("my-sub-lib").foo(req.query.fileName); // calls lib/subLib/index.js#foo
});
}
});

View File

@@ -31,3 +31,44 @@ cp.exec("cmd.sh " + require("get-them-args")().foo); // NOT OK
cp.exec("cmd.sh " + require("minimist")().foo); // NOT OK
cp.exec("cmd.sh " + require("yargs").argv.foo); // NOT OK
cp.exec("cmd.sh " + require("optimist").argv.foo); // NOT OK
(function () {
var args = require('yargs') // eslint-disable-line
.command('serve [port]', 'start the server', (yargs) => { })
.option('verbose', { foo: "bar" })
.argv
cp.exec("cmd.sh " + args); // NOT OK
cp.exec("cmd.sh " + require("yargs").array("foo").parse().foo); // NOT OK
});
(function () {
const {
argv: {
...args
},
} = require('yargs')
.usage('Usage: foo bar')
.command();
cp.exec("cmd.sh " + args); // NOT OK
var tainted1 = require('yargs').argv;
var tainted2 = require('yargs').parse()
const {taint1: {...taint1rest},taint2: {...taint2rest}} = {
taint1: tainted1,
taint2: tainted2
}
cp.exec("cmd.sh " + taint1rest); // NOT OK - has flow from tainted1
cp.exec("cmd.sh " + taint2rest); // NOT OK - has flow from tianted2
var {...taint3} = require('yargs').argv;
cp.exec("cmd.sh " + taint3); // NOT OK
var [...taint4] = require('yargs').argv;
cp.exec("cmd.sh " + taint4); // NOT OK
});

View File

@@ -2,4 +2,8 @@ var cp = require("child_process")
module.exports = function (name) {
cp.exec("rm -rf " + name); // OK - this file belongs in a sub-"module", and is not the primary exported module.
};
module.exports.foo = function (name) {
cp.exec("rm -rf " + name); // NOT OK - this is being called explicitly from child_process-test.js
};

View File

@@ -1,5 +1,5 @@
{
"name": "mySubLib",
"name": "my-sub-lib",
"version": "0.0.7",
"main": "./index.js"
}

View File

@@ -187,21 +187,21 @@ edges
| tst.js:313:10:313:10 | e | tst.js:314:20:314:20 | e |
| tst.js:313:10:313:10 | e | tst.js:314:20:314:20 | e |
#select
| exception-xss.js:11:18:11:18 | e | exception-xss.js:2:12:2:28 | document.location | exception-xss.js:11:18:11:18 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:2:12:2:28 | document.location | user-provided value |
| exception-xss.js:17:18:17:18 | e | exception-xss.js:2:12:2:28 | document.location | exception-xss.js:17:18:17:18 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:2:12:2:28 | document.location | user-provided value |
| exception-xss.js:23:18:23:18 | e | exception-xss.js:2:12:2:28 | document.location | exception-xss.js:23:18:23:18 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:2:12:2:28 | document.location | user-provided value |
| exception-xss.js:35:18:35:18 | e | exception-xss.js:2:12:2:28 | document.location | exception-xss.js:35:18:35:18 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:2:12:2:28 | document.location | user-provided value |
| exception-xss.js:48:18:48:18 | e | exception-xss.js:2:12:2:28 | document.location | exception-xss.js:48:18:48:18 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:2:12:2:28 | document.location | user-provided value |
| exception-xss.js:83:18:83:18 | e | exception-xss.js:2:12:2:28 | document.location | exception-xss.js:83:18:83:18 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:2:12:2:28 | document.location | user-provided value |
| exception-xss.js:91:18:91:18 | e | exception-xss.js:2:12:2:28 | document.location | exception-xss.js:91:18:91:18 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:2:12:2:28 | document.location | user-provided value |
| exception-xss.js:97:18:97:18 | e | exception-xss.js:2:12:2:28 | document.location | exception-xss.js:97:18:97:18 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:2:12:2:28 | document.location | user-provided value |
| exception-xss.js:107:18:107:18 | e | exception-xss.js:2:12:2:28 | document.location | exception-xss.js:107:18:107:18 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:2:12:2:28 | document.location | user-provided value |
| exception-xss.js:119:12:119:28 | "Exception: " + e | exception-xss.js:117:11:117:23 | req.params.id | exception-xss.js:119:12:119:28 | "Exception: " + e | Cross-site scripting vulnerability due to $@. | exception-xss.js:117:11:117:23 | req.params.id | user-provided value |
| exception-xss.js:130:18:130:18 | e | exception-xss.js:125:45:125:61 | document.location | exception-xss.js:130:18:130:18 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:125:45:125:61 | document.location | user-provided value |
| exception-xss.js:138:19:138:23 | error | exception-xss.js:136:10:136:22 | req.params.id | exception-xss.js:138:19:138:23 | error | Cross-site scripting vulnerability due to $@. | exception-xss.js:136:10:136:22 | req.params.id | user-provided value |
| exception-xss.js:149:18:149:18 | e | exception-xss.js:146:12:146:28 | document.location | exception-xss.js:149:18:149:18 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:146:12:146:28 | document.location | user-provided value |
| exception-xss.js:155:18:155:18 | e | exception-xss.js:146:12:146:28 | document.location | exception-xss.js:155:18:155:18 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:146:12:146:28 | document.location | user-provided value |
| exception-xss.js:175:18:175:18 | e | exception-xss.js:146:12:146:28 | document.location | exception-xss.js:175:18:175:18 | e | Cross-site scripting vulnerability due to $@. | exception-xss.js:146:12:146:28 | document.location | user-provided value |
| exception-xss.js:182:19:182:23 | error | exception-xss.js:180:10:180:22 | req.params.id | exception-xss.js:182:19:182:23 | error | Cross-site scripting vulnerability due to $@. | exception-xss.js:180:10:180:22 | req.params.id | user-provided value |
| tst.js:306:20:306:20 | e | tst.js:304:9:304:16 | location | tst.js:306:20:306:20 | e | Cross-site scripting vulnerability due to $@. | tst.js:304:9:304:16 | location | user-provided value |
| tst.js:314:20:314:20 | e | tst.js:311:10:311:17 | location | tst.js:314:20:314:20 | e | Cross-site scripting vulnerability due to $@. | tst.js:311:10:311:17 | location | user-provided value |
| exception-xss.js:11:18:11:18 | e | exception-xss.js:2:12:2:28 | document.location | exception-xss.js:11:18:11:18 | e | $@ is reinterpreted as HTML without escaping meta-characters. | exception-xss.js:2:12:2:28 | document.location | Exception text |
| exception-xss.js:17:18:17:18 | e | exception-xss.js:2:12:2:28 | document.location | exception-xss.js:17:18:17:18 | e | $@ is reinterpreted as HTML without escaping meta-characters. | exception-xss.js:2:12:2:28 | document.location | Exception text |
| exception-xss.js:23:18:23:18 | e | exception-xss.js:2:12:2:28 | document.location | exception-xss.js:23:18:23:18 | e | $@ is reinterpreted as HTML without escaping meta-characters. | exception-xss.js:2:12:2:28 | document.location | Exception text |
| exception-xss.js:35:18:35:18 | e | exception-xss.js:2:12:2:28 | document.location | exception-xss.js:35:18:35:18 | e | $@ is reinterpreted as HTML without escaping meta-characters. | exception-xss.js:2:12:2:28 | document.location | Exception text |
| exception-xss.js:48:18:48:18 | e | exception-xss.js:2:12:2:28 | document.location | exception-xss.js:48:18:48:18 | e | $@ is reinterpreted as HTML without escaping meta-characters. | exception-xss.js:2:12:2:28 | document.location | Exception text |
| exception-xss.js:83:18:83:18 | e | exception-xss.js:2:12:2:28 | document.location | exception-xss.js:83:18:83:18 | e | $@ is reinterpreted as HTML without escaping meta-characters. | exception-xss.js:2:12:2:28 | document.location | Exception text |
| exception-xss.js:91:18:91:18 | e | exception-xss.js:2:12:2:28 | document.location | exception-xss.js:91:18:91:18 | e | $@ is reinterpreted as HTML without escaping meta-characters. | exception-xss.js:2:12:2:28 | document.location | Exception text |
| exception-xss.js:97:18:97:18 | e | exception-xss.js:2:12:2:28 | document.location | exception-xss.js:97:18:97:18 | e | $@ is reinterpreted as HTML without escaping meta-characters. | exception-xss.js:2:12:2:28 | document.location | Exception text |
| exception-xss.js:107:18:107:18 | e | exception-xss.js:2:12:2:28 | document.location | exception-xss.js:107:18:107:18 | e | $@ is reinterpreted as HTML without escaping meta-characters. | exception-xss.js:2:12:2:28 | document.location | Exception text |
| exception-xss.js:119:12:119:28 | "Exception: " + e | exception-xss.js:117:11:117:23 | req.params.id | exception-xss.js:119:12:119:28 | "Exception: " + e | $@ is reinterpreted as HTML without escaping meta-characters. | exception-xss.js:117:11:117:23 | req.params.id | Exception text |
| exception-xss.js:130:18:130:18 | e | exception-xss.js:125:45:125:61 | document.location | exception-xss.js:130:18:130:18 | e | $@ is reinterpreted as HTML without escaping meta-characters. | exception-xss.js:125:45:125:61 | document.location | Exception text |
| exception-xss.js:138:19:138:23 | error | exception-xss.js:136:10:136:22 | req.params.id | exception-xss.js:138:19:138:23 | error | $@ is reinterpreted as HTML without escaping meta-characters. | exception-xss.js:136:10:136:22 | req.params.id | Exception text |
| exception-xss.js:149:18:149:18 | e | exception-xss.js:146:12:146:28 | document.location | exception-xss.js:149:18:149:18 | e | $@ is reinterpreted as HTML without escaping meta-characters. | exception-xss.js:146:12:146:28 | document.location | Exception text |
| exception-xss.js:155:18:155:18 | e | exception-xss.js:146:12:146:28 | document.location | exception-xss.js:155:18:155:18 | e | $@ is reinterpreted as HTML without escaping meta-characters. | exception-xss.js:146:12:146:28 | document.location | Exception text |
| exception-xss.js:175:18:175:18 | e | exception-xss.js:146:12:146:28 | document.location | exception-xss.js:175:18:175:18 | e | $@ is reinterpreted as HTML without escaping meta-characters. | exception-xss.js:146:12:146:28 | document.location | Exception text |
| exception-xss.js:182:19:182:23 | error | exception-xss.js:180:10:180:22 | req.params.id | exception-xss.js:182:19:182:23 | error | $@ is reinterpreted as HTML without escaping meta-characters. | exception-xss.js:180:10:180:22 | req.params.id | Exception text |
| tst.js:306:20:306:20 | e | tst.js:304:9:304:16 | location | tst.js:306:20:306:20 | e | $@ is reinterpreted as HTML without escaping meta-characters. | tst.js:304:9:304:16 | location | Exception text |
| tst.js:314:20:314:20 | e | tst.js:311:10:311:17 | location | tst.js:314:20:314:20 | e | $@ is reinterpreted as HTML without escaping meta-characters. | tst.js:311:10:311:17 | location | Exception text |

View File

@@ -66,18 +66,18 @@ edges
| xss-through-dom.js:73:20:73:41 | $("inpu ... 0).name | xss-through-dom.js:73:9:73:41 | selector |
| xss-through-dom.js:73:20:73:41 | $("inpu ... 0).name | xss-through-dom.js:73:9:73:41 | selector |
#select
| xss-through-dom.js:2:16:2:34 | $("textarea").val() | xss-through-dom.js:2:16:2:34 | $("textarea").val() | xss-through-dom.js:2:16:2:34 | $("textarea").val() | Cross-site scripting vulnerability due to $@. | xss-through-dom.js:2:16:2:34 | $("textarea").val() | DOM text |
| xss-through-dom.js:4:16:4:40 | $(".som ... .text() | xss-through-dom.js:4:16:4:40 | $(".som ... .text() | xss-through-dom.js:4:16:4:40 | $(".som ... .text() | Cross-site scripting vulnerability due to $@. | xss-through-dom.js:4:16:4:40 | $(".som ... .text() | DOM text |
| xss-through-dom.js:8:16:8:53 | $(".som ... arget") | xss-through-dom.js:8:16:8:53 | $(".som ... arget") | xss-through-dom.js:8:16:8:53 | $(".som ... arget") | Cross-site scripting vulnerability due to $@. | xss-through-dom.js:8:16:8:53 | $(".som ... arget") | DOM text |
| xss-through-dom.js:11:3:11:42 | documen ... nerText | xss-through-dom.js:11:3:11:42 | documen ... nerText | xss-through-dom.js:11:3:11:42 | documen ... nerText | Cross-site scripting vulnerability due to $@. | xss-through-dom.js:11:3:11:42 | documen ... nerText | DOM text |
| xss-through-dom.js:19:3:19:44 | documen ... Content | xss-through-dom.js:19:3:19:44 | documen ... Content | xss-through-dom.js:19:3:19:44 | documen ... Content | Cross-site scripting vulnerability due to $@. | xss-through-dom.js:19:3:19:44 | documen ... Content | DOM text |
| xss-through-dom.js:23:3:23:48 | documen ... ].value | xss-through-dom.js:23:3:23:48 | documen ... ].value | xss-through-dom.js:23:3:23:48 | documen ... ].value | Cross-site scripting vulnerability due to $@. | xss-through-dom.js:23:3:23:48 | documen ... ].value | DOM text |
| xss-through-dom.js:27:3:27:61 | documen ... arget') | xss-through-dom.js:27:3:27:61 | documen ... arget') | xss-through-dom.js:27:3:27:61 | documen ... arget') | Cross-site scripting vulnerability due to $@. | xss-through-dom.js:27:3:27:61 | documen ... arget') | DOM text |
| xss-through-dom.js:51:30:51:48 | $("textarea").val() | xss-through-dom.js:51:30:51:48 | $("textarea").val() | xss-through-dom.js:51:30:51:48 | $("textarea").val() | Cross-site scripting vulnerability due to $@. | xss-through-dom.js:51:30:51:48 | $("textarea").val() | DOM text |
| xss-through-dom.js:54:31:54:49 | $("textarea").val() | xss-through-dom.js:54:31:54:49 | $("textarea").val() | xss-through-dom.js:54:31:54:49 | $("textarea").val() | Cross-site scripting vulnerability due to $@. | xss-through-dom.js:54:31:54:49 | $("textarea").val() | DOM text |
| xss-through-dom.js:56:30:56:51 | $("inpu ... 0).name | xss-through-dom.js:56:30:56:51 | $("inpu ... 0).name | xss-through-dom.js:56:30:56:51 | $("inpu ... 0).name | Cross-site scripting vulnerability due to $@. | xss-through-dom.js:56:30:56:51 | $("inpu ... 0).name | DOM text |
| xss-through-dom.js:57:30:57:67 | $("inpu ... "name") | xss-through-dom.js:57:30:57:67 | $("inpu ... "name") | xss-through-dom.js:57:30:57:67 | $("inpu ... "name") | Cross-site scripting vulnerability due to $@. | xss-through-dom.js:57:30:57:67 | $("inpu ... "name") | DOM text |
| xss-through-dom.js:61:30:61:69 | $(docum ... value") | xss-through-dom.js:61:30:61:69 | $(docum ... value") | xss-through-dom.js:61:30:61:69 | $(docum ... value") | Cross-site scripting vulnerability due to $@. | xss-through-dom.js:61:30:61:69 | $(docum ... value") | DOM text |
| xss-through-dom.js:64:30:64:40 | valMethod() | xss-through-dom.js:64:30:64:40 | valMethod() | xss-through-dom.js:64:30:64:40 | valMethod() | Cross-site scripting vulnerability due to $@. | xss-through-dom.js:64:30:64:40 | valMethod() | DOM text |
| xss-through-dom.js:71:11:71:32 | $("inpu ... 0).name | xss-through-dom.js:71:11:71:32 | $("inpu ... 0).name | xss-through-dom.js:71:11:71:32 | $("inpu ... 0).name | Cross-site scripting vulnerability due to $@. | xss-through-dom.js:71:11:71:32 | $("inpu ... 0).name | DOM text |
| xss-through-dom.js:77:7:77:14 | selector | xss-through-dom.js:73:20:73:41 | $("inpu ... 0).name | xss-through-dom.js:77:7:77:14 | selector | Cross-site scripting vulnerability due to $@. | xss-through-dom.js:73:20:73:41 | $("inpu ... 0).name | DOM text |
| xss-through-dom.js:2:16:2:34 | $("textarea").val() | xss-through-dom.js:2:16:2:34 | $("textarea").val() | xss-through-dom.js:2:16:2:34 | $("textarea").val() | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:2:16:2:34 | $("textarea").val() | DOM text |
| xss-through-dom.js:4:16:4:40 | $(".som ... .text() | xss-through-dom.js:4:16:4:40 | $(".som ... .text() | xss-through-dom.js:4:16:4:40 | $(".som ... .text() | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:4:16:4:40 | $(".som ... .text() | DOM text |
| xss-through-dom.js:8:16:8:53 | $(".som ... arget") | xss-through-dom.js:8:16:8:53 | $(".som ... arget") | xss-through-dom.js:8:16:8:53 | $(".som ... arget") | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:8:16:8:53 | $(".som ... arget") | DOM text |
| xss-through-dom.js:11:3:11:42 | documen ... nerText | xss-through-dom.js:11:3:11:42 | documen ... nerText | xss-through-dom.js:11:3:11:42 | documen ... nerText | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:11:3:11:42 | documen ... nerText | DOM text |
| xss-through-dom.js:19:3:19:44 | documen ... Content | xss-through-dom.js:19:3:19:44 | documen ... Content | xss-through-dom.js:19:3:19:44 | documen ... Content | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:19:3:19:44 | documen ... Content | DOM text |
| xss-through-dom.js:23:3:23:48 | documen ... ].value | xss-through-dom.js:23:3:23:48 | documen ... ].value | xss-through-dom.js:23:3:23:48 | documen ... ].value | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:23:3:23:48 | documen ... ].value | DOM text |
| xss-through-dom.js:27:3:27:61 | documen ... arget') | xss-through-dom.js:27:3:27:61 | documen ... arget') | xss-through-dom.js:27:3:27:61 | documen ... arget') | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:27:3:27:61 | documen ... arget') | DOM text |
| xss-through-dom.js:51:30:51:48 | $("textarea").val() | xss-through-dom.js:51:30:51:48 | $("textarea").val() | xss-through-dom.js:51:30:51:48 | $("textarea").val() | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:51:30:51:48 | $("textarea").val() | DOM text |
| xss-through-dom.js:54:31:54:49 | $("textarea").val() | xss-through-dom.js:54:31:54:49 | $("textarea").val() | xss-through-dom.js:54:31:54:49 | $("textarea").val() | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:54:31:54:49 | $("textarea").val() | DOM text |
| xss-through-dom.js:56:30:56:51 | $("inpu ... 0).name | xss-through-dom.js:56:30:56:51 | $("inpu ... 0).name | xss-through-dom.js:56:30:56:51 | $("inpu ... 0).name | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:56:30:56:51 | $("inpu ... 0).name | DOM text |
| xss-through-dom.js:57:30:57:67 | $("inpu ... "name") | xss-through-dom.js:57:30:57:67 | $("inpu ... "name") | xss-through-dom.js:57:30:57:67 | $("inpu ... "name") | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:57:30:57:67 | $("inpu ... "name") | DOM text |
| xss-through-dom.js:61:30:61:69 | $(docum ... value") | xss-through-dom.js:61:30:61:69 | $(docum ... value") | xss-through-dom.js:61:30:61:69 | $(docum ... value") | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:61:30:61:69 | $(docum ... value") | DOM text |
| xss-through-dom.js:64:30:64:40 | valMethod() | xss-through-dom.js:64:30:64:40 | valMethod() | xss-through-dom.js:64:30:64:40 | valMethod() | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:64:30:64:40 | valMethod() | DOM text |
| xss-through-dom.js:71:11:71:32 | $("inpu ... 0).name | xss-through-dom.js:71:11:71:32 | $("inpu ... 0).name | xss-through-dom.js:71:11:71:32 | $("inpu ... 0).name | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:71:11:71:32 | $("inpu ... 0).name | DOM text |
| xss-through-dom.js:77:7:77:14 | selector | xss-through-dom.js:73:20:73:41 | $("inpu ... 0).name | xss-through-dom.js:77:7:77:14 | selector | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:73:20:73:41 | $("inpu ... 0).name | DOM text |

View File

@@ -25,6 +25,24 @@
function EventTarget() {}
/**
* @type {!EventTarget}
* Stub for the DOM hierarchy.
*
* @constructor
* @extends {EventTarget}
*/
function DomObjectStub() {}
/**
* @type {!DomObjectStub}
*/
DomObjectStub.prototype.body;
/**
* @type {!DomObjectStub}
*/
DomObjectStub.prototype.value;
/**
* @type {!DomObjectStub}
*/
var document;

Some files were not shown because too many files have changed in this diff Show More