mirror of
https://github.com/github/codeql.git
synced 2026-04-30 19:26:02 +02:00
Merge remote-tracking branch 'upstream/master' into loginjection
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
* @id js/node/unused-npm-dependency
|
||||
* @tags maintainability
|
||||
* frameworks/node.js
|
||||
* @precision medium
|
||||
* @precision low
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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></script></code>, then
|
||||
the generated code will break out of a <code></script></code> if inserted into a
|
||||
<code></script></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>
|
||||
@@ -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"
|
||||
@@ -0,0 +1,4 @@
|
||||
function createObjectWrite() {
|
||||
const assignment = `obj[${JSON.stringify(key)}]=42`;
|
||||
return `(function(){${assignment}})` // NOT OK
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<include src="IncompleteSanitization.qhelp" />
|
||||
|
||||
</qhelp>
|
||||
@@ -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()
|
||||
40
javascript/ql/src/Security/CWE-200/PrivateFileExposure.qhelp
Normal file
40
javascript/ql/src/Security/CWE-200/PrivateFileExposure.qhelp
Normal 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>
|
||||
129
javascript/ql/src/Security/CWE-200/PrivateFileExposure.ql
Normal file
129
javascript/ql/src/Security/CWE-200/PrivateFileExposure.ql
Normal 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."
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
var express = require('express');
|
||||
|
||||
var app = express();
|
||||
|
||||
app.use('/node_modules', express.static(path.resolve(__dirname, '../node_modules')));
|
||||
@@ -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'));
|
||||
@@ -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>
|
||||
@@ -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."
|
||||
@@ -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
|
||||
}
|
||||
);
|
||||
36
javascript/ql/src/Security/CWE-312/BuildArtifactLeak.qhelp
Normal file
36
javascript/ql/src/Security/CWE-312/BuildArtifactLeak.qhelp
Normal 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>
|
||||
23
javascript/ql/src/Security/CWE-312/BuildArtifactLeak.ql
Normal file
23
javascript/ql/src/Security/CWE-312/BuildArtifactLeak.ql
Normal 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()
|
||||
@@ -0,0 +1,9 @@
|
||||
const webpack = require("webpack");
|
||||
|
||||
module.exports = [{
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': JSON.stringify({ DEBUG: process.env.DEBUG })
|
||||
})
|
||||
]
|
||||
}];
|
||||
@@ -0,0 +1,9 @@
|
||||
const webpack = require("webpack");
|
||||
|
||||
module.exports = [{
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
"process.env": JSON.stringify(process.env)
|
||||
})
|
||||
]
|
||||
}];
|
||||
65
javascript/ql/src/Security/CWE-327/BadRandomness.qhelp
Normal file
65
javascript/ql/src/Security/CWE-327/BadRandomness.qhelp
Normal 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>
|
||||
184
javascript/ql/src/Security/CWE-327/BadRandomness.ql
Normal file
184
javascript/ql/src/Security/CWE-327/BadRandomness.ql
Normal 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"
|
||||
@@ -0,0 +1,3 @@
|
||||
const cryptoRandomString = require('crypto-random-string');
|
||||
|
||||
const digits = cryptoRandomString({length: 10, type: 'numeric'});
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
38
javascript/ql/src/Security/CWE-829/InsecureDownload.qhelp
Normal file
38
javascript/ql/src/Security/CWE-829/InsecureDownload.qhelp
Normal 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>
|
||||
20
javascript/ql/src/Security/CWE-829/InsecureDownload.ql
Normal file
20
javascript/ql/src/Security/CWE-829/InsecureDownload.ql
Normal 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"
|
||||
@@ -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));
|
||||
@@ -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));
|
||||
@@ -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>
|
||||
@@ -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."
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
32
javascript/ql/src/meta/MetaMetrics.qll
Normal file
32
javascript/ql/src/meta/MetaMetrics.qll
Normal 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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
17
javascript/ql/src/meta/analysis-quality/ResolvableImports.ql
Normal file
17
javascript/ql/src/meta/analysis-quality/ResolvableImports.ql
Normal 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()))
|
||||
16
javascript/ql/src/meta/analysis-quality/RouteHandlers.ql
Normal file
16
javascript/ql/src/meta/analysis-quality/RouteHandlers.ql
Normal 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())
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
89
javascript/ql/src/meta/analysis-quality/TaintMetrics.qll
Normal file
89
javascript/ql/src/meta/analysis-quality/TaintMetrics.qll
Normal 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
|
||||
}
|
||||
14
javascript/ql/src/meta/analysis-quality/TaintSinks.ql
Normal file
14
javascript/ql/src/meta/analysis-quality/TaintSinks.ql
Normal 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())
|
||||
14
javascript/ql/src/meta/analysis-quality/TaintSources.ql
Normal file
14
javascript/ql/src/meta/analysis-quality/TaintSources.ql
Normal 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())
|
||||
24
javascript/ql/src/meta/analysis-quality/TaintSteps.ql
Normal file
24
javascript/ql/src/meta/analysis-quality/TaintSteps.ql
Normal 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))
|
||||
27
javascript/ql/src/meta/analysis-quality/TaintedNodes.ql
Normal file
27
javascript/ql/src/meta/analysis-quality/TaintedNodes.ql
Normal 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))
|
||||
17
javascript/ql/src/meta/types/TypedExprs.ql
Normal file
17
javascript/ql/src/meta/types/TypedExprs.ql
Normal 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()))
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
114
javascript/ql/src/semmle/javascript/frameworks/Micro.qll
Normal file
114
javascript/ql/src/semmle/javascript/frameworks/Micro.qll
Normal 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]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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)\\].*")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -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 { }
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
10
javascript/ql/test/library-tests/DOM/externs/externs.js
Normal file
10
javascript/ql/test/library-tests/DOM/externs/externs.js
Normal file
@@ -0,0 +1,10 @@
|
||||
/** @externs */
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @name EventTarget
|
||||
*/
|
||||
function EventTarget() {}
|
||||
|
||||
/** @type {EventTarget} */
|
||||
var window;
|
||||
@@ -39,3 +39,13 @@
|
||||
factory2();
|
||||
|
||||
})();
|
||||
|
||||
(function pollute() {
|
||||
class C {
|
||||
foo() {
|
||||
this.x; // Should not be a domValueRef
|
||||
}
|
||||
}
|
||||
window.myApp = new C();
|
||||
window.myApp.foo();
|
||||
})();
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
module.exports.foo = function() {};
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"main": "dist/does-not-exist.js"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export function exported() {}
|
||||
|
||||
function notExported() {}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"main": "main.js"
|
||||
}
|
||||
@@ -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() {} |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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" |
|
||||
@@ -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()
|
||||
}
|
||||
19
javascript/ql/test/library-tests/frameworks/Micro/tst.js
Normal file
19
javascript/ql/test/library-tests/frameworks/Micro/tst.js
Normal 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));
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "mySubLib",
|
||||
"name": "my-sub-lib",
|
||||
"version": "0.0.7",
|
||||
"main": "./index.js"
|
||||
}
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user