Merge pull request #3765 from asger-semmle/js-team-sprint-merge2

JS: Merge js-team-sprint
This commit is contained in:
Asger F
2020-06-23 12:58:27 +01:00
committed by GitHub
94 changed files with 2814 additions and 104 deletions

View File

@@ -11,6 +11,7 @@
- [jGrowl](https://github.com/stanlemon/jGrowl)
- [jQuery](https://jquery.com/)
- [marsdb](https://www.npmjs.com/package/marsdb)
- [micro](https://www.npmjs.com/package/micro/)
- [minimongo](https://www.npmjs.com/package/minimongo/)
- [mssql](https://www.npmjs.com/package/mssql)
- [mysql](https://www.npmjs.com/package/mysql)
@@ -20,6 +21,7 @@
- [sqlite](https://www.npmjs.com/package/sqlite)
- [ssh2-streams](https://www.npmjs.com/package/ssh2-streams)
- [ssh2](https://www.npmjs.com/package/ssh2)
- [yargs](https://www.npmjs.com/package/yargs)
- [webpack-dev-server](https://www.npmjs.com/package/webpack-dev-server)
* TypeScript 3.9 is now supported.
@@ -35,6 +37,13 @@
| Incomplete HTML attribute sanitization (`js/incomplete-html-attribute-sanitization`) | security, external/cwe/cwe-20, external/cwe/cwe-079, external/cwe/cwe-116 | Highlights potential XSS vulnerabilities due to incomplete sanitization of HTML meta-characters. Results are shown on LGTM by default. |
| Unsafe expansion of self-closing HTML tag (`js/unsafe-html-expansion`) | security, external/cwe/cwe-079, external/cwe/cwe-116 | Highlights potential XSS vulnerabilities caused by unsafe expansion of self-closing HTML tags. |
| Unsafe shell command constructed from library input (`js/shell-command-constructed-from-input`) | correctness, security, external/cwe/cwe-078, external/cwe/cwe-088 | Highlights potential command injections due to a shell command being constructed from library inputs. Results are shown on LGTM by default. |
| Download of sensitive file through insecure connection (`js/insecure-download`) | security, external/cwe/cwe-829 | Highlights downloads of sensitive files through an unencrypted protocol. Results are shown on LGTM by default. |
| Exposure of private files (`js/exposure-of-private-files`) | security, external/cwe/cwe-200 | Highlights servers that serve private files. Results are shown on LGTM by default. |
| Creating biased random numbers from a cryptographically secure source (`js/biased-cryptographic-random`) | security, external/cwe/cwe-327 | Highlights mathematical operations on cryptographically secure numbers that can create biased results. Results are shown on LGTM by default. |
| Storage of sensitive information in build artifact (`js/build-artifact-leak`) | security, external/cwe/cwe-312 | Highlights storage of sensitive information in build artifacts. Results are shown on LGTM by default. |
| Improper code sanitization (`js/bad-code-sanitization`) | security, external/cwe/cwe-094, external/cwe/cwe-079, external/cwe/cwe-116 | Highlights string concatenation where code is constructed without proper sanitization. Results are shown on LGTM by default. |
| Disabling certificate validation (`js/disabling-certificate-validation`) | security, external/cwe-295 | Highlights locations where SSL certificate validation is disabled. Results are shown on LGTM by default. |
| Incomplete multi-character sanitization (`js/incomplete-multi-character-sanitization`) | correctness, security, external/cwe/cwe-20, external/cwe/cwe-116 | Highlights sanitizers that fail to remove dangerous substrings completely. Results are shown on LGTM by default. |
## Changes to existing queries

View File

@@ -13,27 +13,33 @@
+ semmlecode-javascript-queries/Security/CWE-078/ShellCommandInjectionFromEnvironment.ql: /Security/CWE/CWE-078
+ semmlecode-javascript-queries/Security/CWE-079/ReflectedXss.ql: /Security/CWE/CWE-079
+ semmlecode-javascript-queries/Security/CWE-079/StoredXss.ql: /Security/CWE/CWE-079
+ semmlecode-javascript-queries/Security/CWE-079/Xss.ql: /Security/CWE/CWE-079
+ semmlecode-javascript-queries/Security/CWE-079/UnsafeJQueryPlugin.ql: /Security/CWE/CWE-079
+ semmlecode-javascript-queries/Security/CWE-079/Xss.ql: /Security/CWE/CWE-079
+ semmlecode-javascript-queries/Security/CWE-089/SqlInjection.ql: /Security/CWE/CWE-089
+ semmlecode-javascript-queries/Security/CWE-094/CodeInjection.ql: /Security/CWE/CWE-094
+ semmlecode-javascript-queries/Security/CWE-094/ImproperCodeSanitization.ql: /Security/CWE/CWE-094
+ semmlecode-javascript-queries/Security/CWE-094/UnsafeDynamicMethodAccess.ql: /Security/CWE/CWE-094
+ semmlecode-javascript-queries/Security/CWE-116/IncompleteSanitization.ql: /Security/CWE/CWE-116
+ semmlecode-javascript-queries/Security/CWE-116/IncompleteHtmlAttributeSanitization.ql: /Security/CWE/CWE-116
+ semmlecode-javascript-queries/Security/CWE-116/DoubleEscaping.ql: /Security/CWE/CWE-116
+ semmlecode-javascript-queries/Security/CWE-116/IncompleteHtmlAttributeSanitization.ql: /Security/CWE/CWE-116
+ semmlecode-javascript-queries/Security/CWE-116/IncompleteMultiCharacterSanitization.ql: /Security/CWE/CWE-116
+ semmlecode-javascript-queries/Security/CWE-116/IncompleteSanitization.ql: /Security/CWE/CWE-116
+ semmlecode-javascript-queries/Security/CWE-134/TaintedFormatString.ql: /Security/CWE/CWE-134
+ semmlecode-javascript-queries/Security/CWE-200/PrivateFileExposure.ql: /Security/CWE/CWE-200
+ semmlecode-javascript-queries/Security/CWE-201/PostMessageStar.ql: /Security/CWE/CWE-201
+ semmlecode-javascript-queries/Security/CWE-209/StackTraceExposure.ql: /Security/CWE/CWE-209
+ semmlecode-javascript-queries/Security/CWE-312/CleartextStorage.ql: /Security/CWE/CWE-312
+ semmlecode-javascript-queries/Security/CWE-295/DisablingCertificateValidation.ql: /Security/CWE/CWE-295
+ semmlecode-javascript-queries/Security/CWE-312/BuildArtifactLeak.ql: /Security/CWE/CWE-312
+ semmlecode-javascript-queries/Security/CWE-312/CleartextLogging.ql: /Security/CWE/CWE-312
+ semmlecode-javascript-queries/Security/CWE-312/CleartextStorage.ql: /Security/CWE/CWE-312
+ semmlecode-javascript-queries/Security/CWE-313/PasswordInConfigurationFile.ql: /Security/CWE/CWE-313
+ semmlecode-javascript-queries/Security/CWE-327/BadRandomness.ql: /Security/CWE/CWE-327
+ semmlecode-javascript-queries/Security/CWE-327/BrokenCryptoAlgorithm.ql: /Security/CWE/CWE-327
+ semmlecode-javascript-queries/Security/CWE-338/InsecureRandomness.ql: /Security/CWE/CWE-338
+ semmlecode-javascript-queries/Security/CWE-346/CorsMisconfigurationForCredentials.ql: /Security/CWE/CWE-346
+ semmlecode-javascript-queries/Security/CWE-352/MissingCsrfMiddleware.ql: /Security/CWE/CWE-352
+ semmlecode-javascript-queries/Security/CWE-400/RemotePropertyInjection.ql: /Security/CWE/CWE-400
+ semmlecode-javascript-queries/Security/CWE-400/PrototypePollution.ql: /Security/CWE/CWE-400
+ semmlecode-javascript-queries/Security/CWE-400/PrototypePollutionUtility.ql: /Security/CWE/CWE-400
+ semmlecode-javascript-queries/Security/CWE-400/RemotePropertyInjection.ql: /Security/CWE/CWE-400
+ semmlecode-javascript-queries/Security/CWE-502/UnsafeDeserialization.ql: /Security/CWE/CWE-502
+ semmlecode-javascript-queries/Security/CWE-506/HardcodedDataInterpretedAsCode.ql: /Security/CWE/CWE-506
+ semmlecode-javascript-queries/Security/CWE-601/ClientSideUrlRedirect.ql: /Security/CWE/CWE-601
@@ -48,6 +54,7 @@
+ semmlecode-javascript-queries/Security/CWE-798/HardcodedCredentials.ql: /Security/CWE/CWE-798
+ semmlecode-javascript-queries/Security/CWE-807/ConditionalBypass.ql: /Security/CWE/CWE-807
+ semmlecode-javascript-queries/Security/CWE-807/DifferentKindsComparisonBypass.ql: /Security/CWE/CWE-807
+ semmlecode-javascript-queries/Security/CWE-829/InsecureDownload.ql: /Security/CWE/CWE-829
+ semmlecode-javascript-queries/Security/CWE-834/LoopBoundInjection.ql: /Security/CWE/CWE-834
+ semmlecode-javascript-queries/Security/CWE-843/TypeConfusionThroughParameterTampering.ql: /Security/CWE/CWE-834
+ semmlecode-javascript-queries/Security/CWE-916/InsufficientPasswordHash.ql: /Security/CWE/CWE-916

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,204 @@
/**
* @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 a Buffer/TypedArray containing cryptographically secure random numbers.
*/
private 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"])
}
/**
* 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 = randomBufferSource() and
result = source
or
// Loading a number from a `Buffer`.
exists(DataFlow::TypeTracker t2 | t = t2.append(LoadStep(prop())) |
// the random generators return arrays/Buffers of random numbers, we therefore track through an indexed read.
exists(DataFlow::PropRead read | result = read |
read.getBase() = goodRandom(t2, source) and
not read.getPropertyNameExpr() instanceof Label
)
or
// reading a number from a Buffer.
exists(DataFlow::MethodCallNode call | result = call |
call.getReceiver() = goodRandom(t2, source) and
call
.getMethodName()
.regexpMatch("read(BigInt|BigUInt|Double|Float|Int|UInt)(8|16|32|64)?(BE|LE)?")
)
)
or
exists(DataFlow::TypeTracker t2 | t = t2.smallstep(goodRandom(t2, source), result))
or
// re-using the collection steps for `Set`.
exists(DataFlow::TypeTracker t2 |
result = CollectionsTypeTracking::collectionStep(goodRandom(t2, source), t, t2)
)
or
InsecureRandomness::isAdditionalTaintStep(goodRandom(t.continue(), source), result) and
// bit shifts and multiplication by powers of two are generally used for constructing larger numbers from smaller numbers.
not exists(BinaryExpr binop | binop = result.asExpr() |
binop.getOperator().regexpMatch(".*(<|>).*")
or
binop.getOperator() = "*" and isPowerOfTwo().asExpr() = binop.getAnOperand()
or
// string concat does not produce a number
unique(InferredType type | type = binop.flow().analyze().getAType()) = TTString()
)
}
/**
* Gets a reference to a cryptographically secure random number produced by `source`.
*/
DataFlow::Node goodRandom(DataFlow::SourceNode source) {
result = goodRandom(DataFlow::TypeTracker::end(), source)
}
/**
* Gets a node that is passed to a rounding function from `Math`, using type-backtracker `t`.
*/
DataFlow::Node isRounded(DataFlow::TypeBackTracker t) {
t.start() and
result = DataFlow::globalVarRef("Math").getAMemberCall(["round", "floor", "ceil"]).getArgument(0)
or
exists(DataFlow::TypeBackTracker t2 | t2 = t.smallstep(result, isRounded(t2)))
or
InsecureRandomness::isAdditionalTaintStep(result, isRounded(t.continue()))
}
/**
* Gets a node that that produces a biased result from otherwise cryptographically secure random numbers produced by `source`.
*/
DataFlow::Node badCrypto(string description, DataFlow::SourceNode source) {
// addition and multiplication - always bad when both the lhs and rhs are random.
exists(BinaryExpr binop | result.asExpr() = binop |
goodRandom(_).asExpr() = binop.getLeftOperand() and
goodRandom(_).asExpr() = binop.getRightOperand() and
goodRandom(source).asExpr() = binop.getAnOperand() and
(
binop.getOperator() = "+" and description = "addition"
or
binop.getOperator() = "*" and description = "multiplication"
)
)
or
// division - bad if result is rounded.
exists(DivExpr div | result.asExpr() = div |
goodRandom(source).asExpr() = div.getLeftOperand() and
description = "division and rounding the result" and
not div.getRightOperand() = isPowerOfTwoMinusOne().asExpr() and // division by (2^n)-1 most of the time produces a uniformly random number between 0 and 1.
div = isRounded(DataFlow::TypeBackTracker::end()).asExpr()
)
or
// modulo - only bad if not by a power of 2 - and the result is not checked for bias
exists(ModExpr mod, DataFlow::Node random | result.asExpr() = mod and mod.getOperator() = "%" |
description = "modulo" and
goodRandom(source) = random and
random.asExpr() = mod.getLeftOperand() and
// division by a power of 2 is OK. E.g. if `x` is uniformly random is in the range [0..255] then `x % 32` is uniformly random in the range [0..31].
not mod.getRightOperand() = isPowerOfTwo().asExpr() and
// not exists a comparison that checks if the result is potentially biased.
not exists(BinaryExpr comparison | comparison.getOperator() = [">", "<", "<=", ">="] |
AccessPath::getAnAliasedSourceNode(random.getALocalSource())
.flowsToExpr(comparison.getAnOperand())
or
exists(DataFlow::PropRead otherRead |
otherRead = random.(DataFlow::PropRead).getBase().getALocalSource().getAPropertyRead() and
not exists(otherRead.getPropertyName()) and
otherRead.flowsToExpr(comparison.getAnOperand())
)
)
)
or
// create a number from a string - always a bad idea.
exists(DataFlow::CallNode number, StringOps::ConcatenationRoot root | result = number |
number = DataFlow::globalVarRef(["Number", "parseInt", "parseFloat"]).getACall() and
root = number.getArgument(0) and
goodRandom(source) = root.getALeaf() and
exists(root.getALeaf().getStringValue()) and
description = "string concatenation"
)
}
from DataFlow::Node node, string description, DataFlow::SourceNode source
where node = badCrypto(description, source)
select node, "Using " + description + " on a $@ produces biased results.", source,
"cryptographically secure random number"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -78,4 +78,20 @@ 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
)
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -64,6 +64,21 @@ nodes
| angularjs.js:53:32:53:39 | location |
| angularjs.js:53:32:53:46 | location.search |
| angularjs.js:53:32:53:46 | location.search |
| bad-code-sanitization.js:54:14:54:67 | `(funct ... "))}))` |
| bad-code-sanitization.js:54:14:54:67 | `(funct ... "))}))` |
| bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) |
| bad-code-sanitization.js:54:44:54:62 | req.param("wobble") |
| bad-code-sanitization.js:54:44:54:62 | req.param("wobble") |
| bad-code-sanitization.js:56:7:56:47 | taint |
| bad-code-sanitization.js:56:15:56:36 | [req.bo ... "foo"] |
| bad-code-sanitization.js:56:15:56:47 | [req.bo ... n("\\n") |
| bad-code-sanitization.js:56:16:56:23 | req.body |
| bad-code-sanitization.js:56:16:56:23 | req.body |
| bad-code-sanitization.js:56:16:56:28 | req.body.name |
| bad-code-sanitization.js:58:14:58:53 | `(funct ... nt)}))` |
| bad-code-sanitization.js:58:14:58:53 | `(funct ... nt)}))` |
| bad-code-sanitization.js:58:29:58:49 | JSON.st ... (taint) |
| bad-code-sanitization.js:58:44:58:48 | taint |
| express.js:7:24:7:69 | "return ... + "];" |
| express.js:7:24:7:69 | "return ... + "];" |
| express.js:7:44:7:62 | req.param("wobble") |
@@ -193,6 +208,19 @@ edges
| angularjs.js:53:32:53:39 | location | angularjs.js:53:32:53:46 | location.search |
| angularjs.js:53:32:53:39 | location | angularjs.js:53:32:53:46 | location.search |
| angularjs.js:53:32:53:39 | location | angularjs.js:53:32:53:46 | location.search |
| bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) | bad-code-sanitization.js:54:14:54:67 | `(funct ... "))}))` |
| bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) | bad-code-sanitization.js:54:14:54:67 | `(funct ... "))}))` |
| bad-code-sanitization.js:54:44:54:62 | req.param("wobble") | bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) |
| bad-code-sanitization.js:54:44:54:62 | req.param("wobble") | bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) |
| bad-code-sanitization.js:56:7:56:47 | taint | bad-code-sanitization.js:58:44:58:48 | taint |
| bad-code-sanitization.js:56:15:56:36 | [req.bo ... "foo"] | bad-code-sanitization.js:56:15:56:47 | [req.bo ... n("\\n") |
| bad-code-sanitization.js:56:15:56:47 | [req.bo ... n("\\n") | bad-code-sanitization.js:56:7:56:47 | taint |
| bad-code-sanitization.js:56:16:56:23 | req.body | bad-code-sanitization.js:56:16:56:28 | req.body.name |
| bad-code-sanitization.js:56:16:56:23 | req.body | bad-code-sanitization.js:56:16:56:28 | req.body.name |
| bad-code-sanitization.js:56:16:56:28 | req.body.name | bad-code-sanitization.js:56:15:56:36 | [req.bo ... "foo"] |
| bad-code-sanitization.js:58:29:58:49 | JSON.st ... (taint) | bad-code-sanitization.js:58:14:58:53 | `(funct ... nt)}))` |
| bad-code-sanitization.js:58:29:58:49 | JSON.st ... (taint) | bad-code-sanitization.js:58:14:58:53 | `(funct ... nt)}))` |
| bad-code-sanitization.js:58:44:58:48 | taint | bad-code-sanitization.js:58:29:58:49 | JSON.st ... (taint) |
| express.js:7:44:7:62 | req.param("wobble") | express.js:7:24:7:69 | "return ... + "];" |
| express.js:7:44:7:62 | req.param("wobble") | express.js:7:24:7:69 | "return ... + "];" |
| express.js:7:44:7:62 | req.param("wobble") | express.js:7:24:7:69 | "return ... + "];" |
@@ -261,6 +289,8 @@ edges
| angularjs.js:47:16:47:30 | location.search | angularjs.js:47:16:47:23 | location | angularjs.js:47:16:47:30 | location.search | $@ flows to here and is interpreted as code. | angularjs.js:47:16:47:23 | location | User-provided value |
| angularjs.js:50:22:50:36 | location.search | angularjs.js:50:22:50:29 | location | angularjs.js:50:22:50:36 | location.search | $@ flows to here and is interpreted as code. | angularjs.js:50:22:50:29 | location | User-provided value |
| angularjs.js:53:32:53:46 | location.search | angularjs.js:53:32:53:39 | location | angularjs.js:53:32:53:46 | location.search | $@ flows to here and is interpreted as code. | angularjs.js:53:32:53:39 | location | User-provided value |
| bad-code-sanitization.js:54:14:54:67 | `(funct ... "))}))` | bad-code-sanitization.js:54:44:54:62 | req.param("wobble") | bad-code-sanitization.js:54:14:54:67 | `(funct ... "))}))` | $@ flows to here and is interpreted as code. | bad-code-sanitization.js:54:44:54:62 | req.param("wobble") | User-provided value |
| bad-code-sanitization.js:58:14:58:53 | `(funct ... nt)}))` | bad-code-sanitization.js:56:16:56:23 | req.body | bad-code-sanitization.js:58:14:58:53 | `(funct ... nt)}))` | $@ flows to here and is interpreted as code. | bad-code-sanitization.js:56:16:56:23 | req.body | User-provided value |
| express.js:7:24:7:69 | "return ... + "];" | express.js:7:44:7:62 | req.param("wobble") | express.js:7:24:7:69 | "return ... + "];" | $@ flows to here and is interpreted as code. | express.js:7:44:7:62 | req.param("wobble") | User-provided value |
| express.js:9:34:9:79 | "return ... + "];" | express.js:9:54:9:72 | req.param("wobble") | express.js:9:34:9:79 | "return ... + "];" | $@ flows to here and is interpreted as code. | express.js:9:54:9:72 | req.param("wobble") | User-provided value |
| express.js:12:8:12:53 | "return ... + "];" | express.js:12:28:12:46 | req.param("wobble") | express.js:12:8:12:53 | "return ... + "];" | $@ flows to here and is interpreted as code. | express.js:12:28:12:46 | req.param("wobble") | User-provided value |

View File

@@ -64,6 +64,21 @@ nodes
| angularjs.js:53:32:53:39 | location |
| angularjs.js:53:32:53:46 | location.search |
| angularjs.js:53:32:53:46 | location.search |
| bad-code-sanitization.js:54:14:54:67 | `(funct ... "))}))` |
| bad-code-sanitization.js:54:14:54:67 | `(funct ... "))}))` |
| bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) |
| bad-code-sanitization.js:54:44:54:62 | req.param("wobble") |
| bad-code-sanitization.js:54:44:54:62 | req.param("wobble") |
| bad-code-sanitization.js:56:7:56:47 | taint |
| bad-code-sanitization.js:56:15:56:36 | [req.bo ... "foo"] |
| bad-code-sanitization.js:56:15:56:47 | [req.bo ... n("\\n") |
| bad-code-sanitization.js:56:16:56:23 | req.body |
| bad-code-sanitization.js:56:16:56:23 | req.body |
| bad-code-sanitization.js:56:16:56:28 | req.body.name |
| bad-code-sanitization.js:58:14:58:53 | `(funct ... nt)}))` |
| bad-code-sanitization.js:58:14:58:53 | `(funct ... nt)}))` |
| bad-code-sanitization.js:58:29:58:49 | JSON.st ... (taint) |
| bad-code-sanitization.js:58:44:58:48 | taint |
| eslint-escope-build.js:20:22:20:22 | c |
| eslint-escope-build.js:20:22:20:22 | c |
| eslint-escope-build.js:21:16:21:16 | c |
@@ -197,6 +212,19 @@ edges
| angularjs.js:53:32:53:39 | location | angularjs.js:53:32:53:46 | location.search |
| angularjs.js:53:32:53:39 | location | angularjs.js:53:32:53:46 | location.search |
| angularjs.js:53:32:53:39 | location | angularjs.js:53:32:53:46 | location.search |
| bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) | bad-code-sanitization.js:54:14:54:67 | `(funct ... "))}))` |
| bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) | bad-code-sanitization.js:54:14:54:67 | `(funct ... "))}))` |
| bad-code-sanitization.js:54:44:54:62 | req.param("wobble") | bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) |
| bad-code-sanitization.js:54:44:54:62 | req.param("wobble") | bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) |
| bad-code-sanitization.js:56:7:56:47 | taint | bad-code-sanitization.js:58:44:58:48 | taint |
| bad-code-sanitization.js:56:15:56:36 | [req.bo ... "foo"] | bad-code-sanitization.js:56:15:56:47 | [req.bo ... n("\\n") |
| bad-code-sanitization.js:56:15:56:47 | [req.bo ... n("\\n") | bad-code-sanitization.js:56:7:56:47 | taint |
| bad-code-sanitization.js:56:16:56:23 | req.body | bad-code-sanitization.js:56:16:56:28 | req.body.name |
| bad-code-sanitization.js:56:16:56:23 | req.body | bad-code-sanitization.js:56:16:56:28 | req.body.name |
| bad-code-sanitization.js:56:16:56:28 | req.body.name | bad-code-sanitization.js:56:15:56:36 | [req.bo ... "foo"] |
| bad-code-sanitization.js:58:29:58:49 | JSON.st ... (taint) | bad-code-sanitization.js:58:14:58:53 | `(funct ... nt)}))` |
| bad-code-sanitization.js:58:29:58:49 | JSON.st ... (taint) | bad-code-sanitization.js:58:14:58:53 | `(funct ... nt)}))` |
| bad-code-sanitization.js:58:44:58:48 | taint | bad-code-sanitization.js:58:29:58:49 | JSON.st ... (taint) |
| eslint-escope-build.js:20:22:20:22 | c | eslint-escope-build.js:21:16:21:16 | c |
| eslint-escope-build.js:20:22:20:22 | c | eslint-escope-build.js:21:16:21:16 | c |
| eslint-escope-build.js:20:22:20:22 | c | eslint-escope-build.js:21:16:21:16 | c |

View File

@@ -0,0 +1,75 @@
nodes
| bad-code-sanitization.js:2:12:2:90 | /^[_$a- ... key)}]` |
| bad-code-sanitization.js:2:65:2:90 | `[${JSO ... key)}]` |
| bad-code-sanitization.js:2:69:2:87 | JSON.stringify(key) |
| bad-code-sanitization.js:2:69:2:87 | JSON.stringify(key) |
| bad-code-sanitization.js:6:11:6:25 | statements |
| bad-code-sanitization.js:6:24:6:25 | [] |
| bad-code-sanitization.js:7:21:7:70 | `${name ... key])}` |
| bad-code-sanitization.js:7:31:7:43 | safeProp(key) |
| bad-code-sanitization.js:8:27:8:36 | statements |
| bad-code-sanitization.js:8:27:8:46 | statements.join(';') |
| bad-code-sanitization.js:8:27:8:46 | statements.join(';') |
| bad-code-sanitization.js:15:44:15:63 | htmlescape(pathname) |
| bad-code-sanitization.js:15:44:15:63 | htmlescape(pathname) |
| bad-code-sanitization.js:15:44:15:63 | htmlescape(pathname) |
| bad-code-sanitization.js:19:27:19:47 | JSON.st ... (input) |
| bad-code-sanitization.js:19:27:19:47 | JSON.st ... (input) |
| bad-code-sanitization.js:19:27:19:47 | JSON.st ... (input) |
| bad-code-sanitization.js:31:30:31:50 | JSON.st ... (input) |
| bad-code-sanitization.js:31:30:31:50 | JSON.st ... (input) |
| bad-code-sanitization.js:31:30:31:50 | JSON.st ... (input) |
| bad-code-sanitization.js:40:23:40:43 | JSON.st ... (input) |
| bad-code-sanitization.js:40:23:40:43 | JSON.st ... (input) |
| bad-code-sanitization.js:40:23:40:43 | JSON.st ... (input) |
| bad-code-sanitization.js:44:22:44:42 | JSON.st ... (input) |
| bad-code-sanitization.js:44:22:44:42 | JSON.st ... (input) |
| bad-code-sanitization.js:44:22:44:42 | JSON.st ... (input) |
| bad-code-sanitization.js:52:28:52:62 | JSON.st ... bble")) |
| bad-code-sanitization.js:52:28:52:62 | JSON.st ... bble")) |
| bad-code-sanitization.js:52:28:52:62 | JSON.st ... bble")) |
| bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) |
| bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) |
| bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) |
| bad-code-sanitization.js:58:29:58:49 | JSON.st ... (taint) |
| bad-code-sanitization.js:58:29:58:49 | JSON.st ... (taint) |
| bad-code-sanitization.js:58:29:58:49 | JSON.st ... (taint) |
| bad-code-sanitization.js:63:11:63:55 | assignment |
| bad-code-sanitization.js:63:24:63:55 | `obj[${ ... )}]=42` |
| bad-code-sanitization.js:63:31:63:49 | JSON.stringify(key) |
| bad-code-sanitization.js:63:31:63:49 | JSON.stringify(key) |
| bad-code-sanitization.js:64:27:64:36 | assignment |
| bad-code-sanitization.js:64:27:64:36 | assignment |
edges
| bad-code-sanitization.js:2:12:2:90 | /^[_$a- ... key)}]` | bad-code-sanitization.js:7:31:7:43 | safeProp(key) |
| bad-code-sanitization.js:2:65:2:90 | `[${JSO ... key)}]` | bad-code-sanitization.js:2:12:2:90 | /^[_$a- ... key)}]` |
| bad-code-sanitization.js:2:69:2:87 | JSON.stringify(key) | bad-code-sanitization.js:2:65:2:90 | `[${JSO ... key)}]` |
| bad-code-sanitization.js:2:69:2:87 | JSON.stringify(key) | bad-code-sanitization.js:2:65:2:90 | `[${JSO ... key)}]` |
| bad-code-sanitization.js:6:11:6:25 | statements | bad-code-sanitization.js:8:27:8:36 | statements |
| bad-code-sanitization.js:6:24:6:25 | [] | bad-code-sanitization.js:6:11:6:25 | statements |
| bad-code-sanitization.js:7:21:7:70 | `${name ... key])}` | bad-code-sanitization.js:6:24:6:25 | [] |
| bad-code-sanitization.js:7:31:7:43 | safeProp(key) | bad-code-sanitization.js:7:21:7:70 | `${name ... key])}` |
| bad-code-sanitization.js:8:27:8:36 | statements | bad-code-sanitization.js:8:27:8:46 | statements.join(';') |
| bad-code-sanitization.js:8:27:8:36 | statements | bad-code-sanitization.js:8:27:8:46 | statements.join(';') |
| bad-code-sanitization.js:15:44:15:63 | htmlescape(pathname) | bad-code-sanitization.js:15:44:15:63 | htmlescape(pathname) |
| bad-code-sanitization.js:19:27:19:47 | JSON.st ... (input) | bad-code-sanitization.js:19:27:19:47 | JSON.st ... (input) |
| bad-code-sanitization.js:31:30:31:50 | JSON.st ... (input) | bad-code-sanitization.js:31:30:31:50 | JSON.st ... (input) |
| bad-code-sanitization.js:40:23:40:43 | JSON.st ... (input) | bad-code-sanitization.js:40:23:40:43 | JSON.st ... (input) |
| bad-code-sanitization.js:44:22:44:42 | JSON.st ... (input) | bad-code-sanitization.js:44:22:44:42 | JSON.st ... (input) |
| bad-code-sanitization.js:52:28:52:62 | JSON.st ... bble")) | bad-code-sanitization.js:52:28:52:62 | JSON.st ... bble")) |
| bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) | bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) |
| bad-code-sanitization.js:58:29:58:49 | JSON.st ... (taint) | bad-code-sanitization.js:58:29:58:49 | JSON.st ... (taint) |
| bad-code-sanitization.js:63:11:63:55 | assignment | bad-code-sanitization.js:64:27:64:36 | assignment |
| bad-code-sanitization.js:63:11:63:55 | assignment | bad-code-sanitization.js:64:27:64:36 | assignment |
| bad-code-sanitization.js:63:24:63:55 | `obj[${ ... )}]=42` | bad-code-sanitization.js:63:11:63:55 | assignment |
| bad-code-sanitization.js:63:31:63:49 | JSON.stringify(key) | bad-code-sanitization.js:63:24:63:55 | `obj[${ ... )}]=42` |
| bad-code-sanitization.js:63:31:63:49 | JSON.stringify(key) | bad-code-sanitization.js:63:24:63:55 | `obj[${ ... )}]=42` |
#select
| bad-code-sanitization.js:8:27:8:46 | statements.join(';') | bad-code-sanitization.js:2:69:2:87 | JSON.stringify(key) | bad-code-sanitization.js:8:27:8:46 | statements.join(';') | $@ flows to here and is used to construct code. | bad-code-sanitization.js:2:69:2:87 | JSON.stringify(key) | Improperly sanitized value |
| bad-code-sanitization.js:15:44:15:63 | htmlescape(pathname) | bad-code-sanitization.js:15:44:15:63 | htmlescape(pathname) | bad-code-sanitization.js:15:44:15:63 | htmlescape(pathname) | $@ flows to here and is used to construct code. | bad-code-sanitization.js:15:44:15:63 | htmlescape(pathname) | Improperly sanitized value |
| bad-code-sanitization.js:19:27:19:47 | JSON.st ... (input) | bad-code-sanitization.js:19:27:19:47 | JSON.st ... (input) | bad-code-sanitization.js:19:27:19:47 | JSON.st ... (input) | $@ flows to here and is used to construct code. | bad-code-sanitization.js:19:27:19:47 | JSON.st ... (input) | Improperly sanitized value |
| bad-code-sanitization.js:31:30:31:50 | JSON.st ... (input) | bad-code-sanitization.js:31:30:31:50 | JSON.st ... (input) | bad-code-sanitization.js:31:30:31:50 | JSON.st ... (input) | $@ flows to here and is used to construct code. | bad-code-sanitization.js:31:30:31:50 | JSON.st ... (input) | Improperly sanitized value |
| bad-code-sanitization.js:40:23:40:43 | JSON.st ... (input) | bad-code-sanitization.js:40:23:40:43 | JSON.st ... (input) | bad-code-sanitization.js:40:23:40:43 | JSON.st ... (input) | $@ flows to here and is used to construct code. | bad-code-sanitization.js:40:23:40:43 | JSON.st ... (input) | Improperly sanitized value |
| bad-code-sanitization.js:44:22:44:42 | JSON.st ... (input) | bad-code-sanitization.js:44:22:44:42 | JSON.st ... (input) | bad-code-sanitization.js:44:22:44:42 | JSON.st ... (input) | $@ flows to here and is used to construct code. | bad-code-sanitization.js:44:22:44:42 | JSON.st ... (input) | Improperly sanitized value |
| bad-code-sanitization.js:52:28:52:62 | JSON.st ... bble")) | bad-code-sanitization.js:52:28:52:62 | JSON.st ... bble")) | bad-code-sanitization.js:52:28:52:62 | JSON.st ... bble")) | $@ flows to here and is used to construct code. | bad-code-sanitization.js:52:28:52:62 | JSON.st ... bble")) | Improperly sanitized value |
| bad-code-sanitization.js:64:27:64:36 | assignment | bad-code-sanitization.js:63:31:63:49 | JSON.stringify(key) | bad-code-sanitization.js:64:27:64:36 | assignment | $@ flows to here and is used to construct code. | bad-code-sanitization.js:63:31:63:49 | JSON.stringify(key) | Improperly sanitized value |

View File

@@ -0,0 +1 @@
Security/CWE-094/ImproperCodeSanitization.ql

View File

@@ -0,0 +1,92 @@
function safeProp(key) {
return /^[_$a-zA-Z][_$a-zA-Z0-9]*$/.test(key) ? `.${key}` : `[${JSON.stringify(key)}]`;
}
function test1() {
const statements = [];
statements.push(`${name}${safeProp(key)}=${stringify(thing[key])}`);
return `(function(){${statements.join(';')}})` // NOT OK
}
import htmlescape from 'htmlescape'
function test2(props) {
const pathname = props.data.pathname;
return `function(){return new Error('${htmlescape(pathname)}')}`; // NOT OK
}
function test3(input) {
return `(function(){${JSON.stringify(input)}))` // NOT OK
}
function evenSaferProp(key) {
return /^[_$a-zA-Z][_$a-zA-Z0-9]*$/.test(key) ? `.${key}` : `[${JSON.stringify(key)}]`.replace(/[<>\b\f\n\r\t\0\u2028\u2029]/g, '');
}
function test4(input) {
return `(function(){${evenSaferProp(input)}))` // OK
}
function test4(input) {
var foo = `(function(){${JSON.stringify(input)}))` // NOT OK - we can type-track to a code-injection sink, the source is not remote flow.
setTimeout(foo);
}
function test5(input) {
console.log('methodName() => ' + JSON.stringify(input)); // OK
}
function test6(input) {
return `(() => {${JSON.stringify(input)})` // NOT OK
}
function test7(input) {
return `() => {${JSON.stringify(input)}` // NOT OK
}
var express = require('express');
var app = express();
app.get('/some/path', function(req, res) {
var foo = `(function(){${JSON.stringify(req.param("wobble"))}))` // NOT - the source is remote-flow, but we know of no sink.
setTimeout(`(function(){${JSON.stringify(req.param("wobble"))}))`); // OK - the source is remote-flow, and the sink is code-injection.
var taint = [req.body.name, "foo"].join("\n");
setTimeout(`(function(){${JSON.stringify(taint)}))`); // OK - the source is remote-flow, and the sink is code-injection.
});
// Bad documentation example:
function createObjectWrite() {
const assignment = `obj[${JSON.stringify(key)}]=42`;
return `(function(){${assignment}})` // NOT OK
}
// Good documentation example:
function good() {
const charMap = {
'<': '\\u003C',
'>' : '\\u003E',
'/': '\\u002F',
'\\': '\\\\',
'\b': '\\b',
'\f': '\\f',
'\n': '\\n',
'\r': '\\r',
'\t': '\\t',
'\0': '\\0',
'\u2028': '\\u2028',
'\u2029': '\\u2029'
};
function escapeUnsafeChars(str) {
return str.replace(/[<>\b\f\n\r\t\0\u2028\u2029]/g, x => charMap[x])
}
function createObjectWrite() {
const assignment = `obj[${escapeUnsafeChars(JSON.stringify(key))}]=42`;
return `(function(){${assignment}})` // OK
}
}

View File

@@ -0,0 +1,9 @@
<html>
<body>
<script>
var foo ="bla</script onload=\"\">";
</script>
</body>
</html>

View File

@@ -0,0 +1,33 @@
| tst-multi-character-sanitization.js:3:13:3:57 | content ... gi, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:3:30:3:49 | <.*cript.*\\/scrip.*> | <.*cript.*\\/scrip.*> |
| tst-multi-character-sanitization.js:4:13:4:47 | content ... /g, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:4:30:4:40 | on\\w+=".*" | on\\w+=".*" |
| tst-multi-character-sanitization.js:5:13:5:49 | content ... /g, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:5:30:5:42 | on\\w+=\\'.*\\' | on\\w+=\\'.*\\' |
| tst-multi-character-sanitization.js:9:13:9:47 | content ... gi, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:9:30:9:39 | <.*cript.* | <.*cript.* |
| tst-multi-character-sanitization.js:10:13:10:49 | content ... /g, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:10:30:10:42 | .on\\w+=.*".*" | .on\\w+=.*".*" |
| tst-multi-character-sanitization.js:11:13:11:51 | content ... /g, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:11:30:11:44 | .on\\w+=.*\\'.*\\' | .on\\w+=.*\\'.*\\' |
| tst-multi-character-sanitization.js:19:3:19:35 | respons ... pt, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:18:18:18:24 | <script | <script |
| tst-multi-character-sanitization.js:25:10:25:40 | text.re ... /g, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:25:24:25:27 | <!-- | <!-- |
| tst-multi-character-sanitization.js:38:8:38:30 | id.repl ... /g, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:38:20:38:23 | \\.\\. | \\.\\. |
| tst-multi-character-sanitization.js:49:13:49:43 | req.url ... EL, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:48:21:48:31 | (\\/)?\\.\\.\\/ | (\\/)?\\.\\.\\/ |
| tst-multi-character-sanitization.js:64:7:64:73 | x.repla ... /g, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:64:18:64:24 | <script | <script |
| tst-multi-character-sanitization.js:66:7:66:56 | x.repla ... /g, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:66:18:66:49 | (\\/\|\\s)on\\w+=(\\'\|")?[^"]*(\\'\|")? | (\\/\|\\s)on\\w+=(\\'\|")?[^"]*(\\'\|")? |
| tst-multi-character-sanitization.js:75:7:75:37 | x.repla ... gm, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:75:18:75:21 | <!-- | <!-- |
| tst-multi-character-sanitization.js:77:7:77:36 | x.repla ... /g, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:77:18:77:29 | \\sng-[a-z-]+ | \\sng-[a-z-]+ |
| tst-multi-character-sanitization.js:81:7:81:58 | x.repla ... /g, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:81:18:81:24 | <script | <script |
| tst-multi-character-sanitization.js:81:7:81:58 | x.repla ... /g, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:81:36:81:39 | only | only |
| tst-multi-character-sanitization.js:82:7:82:50 | x.repla ... /g, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:82:18:82:30 | <script async | <script async |
| tst-multi-character-sanitization.js:83:7:83:63 | x.repla ... gi, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:83:18:83:21 | <!-- | <!-- |
| tst-multi-character-sanitization.js:85:7:85:48 | x.repla ... /g, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:85:18:85:41 | \\x2E\\x2E\\x2F\\x2E\\x2E\\x2F | \\x2E\\x2E\\x2F\\x2E\\x2E\\x2F |
| tst-multi-character-sanitization.js:87:7:87:47 | x.repla ... gi, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:87:18:87:24 | <script | <script |
| tst-multi-character-sanitization.js:92:7:96:4 | x.repla ... ";\\n }) | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:92:18:92:24 | <script | <script |
| tst-multi-character-sanitization.js:100:7:100:28 | x.repla ... /g, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:100:18:100:21 | \\.\\. | \\.\\. |
| tst-multi-character-sanitization.js:101:7:101:30 | x.repla ... /g, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:101:18:101:23 | \\.\\.\\/ | \\.\\.\\/ |
| tst-multi-character-sanitization.js:102:7:102:30 | x.repla ... /g, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:102:18:102:23 | \\/\\.\\. | \\/\\.\\. |
| tst-multi-character-sanitization.js:104:7:104:58 | x.repla ... gi, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:104:18:104:24 | <script | <script |
| tst-multi-character-sanitization.js:106:7:106:64 | x.repla ... gi, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:106:18:106:56 | <(script\|del)(?=[\\s>])[\\w\\W]*?<\\/\\1\\s*> | <(script\|del)(?=[\\s>])[\\w\\W]*?<\\/\\1\\s*> |
| tst-multi-character-sanitization.js:107:7:107:62 | x.repla ... /g, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:107:18:107:55 | \\<script[\\s\\S]*?\\>[\\s\\S]*?\\<\\/script\\> | \\<script[\\s\\S]*?\\>[\\s\\S]*?\\<\\/script\\> |
| tst-multi-character-sanitization.js:108:7:108:75 | x.repla ... gm, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:108:18:108:67 | <(script\|style\|title)[^<]+<\\/(script\|style\|title)> | <(script\|style\|title)[^<]+<\\/(script\|style\|title)> |
| tst-multi-character-sanitization.js:109:7:109:58 | x.repla ... gi, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:109:18:109:24 | <script | <script |
| tst-multi-character-sanitization.js:110:7:110:50 | x.repla ... gi, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:110:18:110:24 | <script | <script |
| tst-multi-character-sanitization.js:111:7:111:32 | x.repla ... /g, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:111:20:111:23 | <!-- | <!-- |
| tst-multi-character-sanitization.js:112:7:112:50 | x.repla ... /g, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:112:18:112:43 | require\\('\\.\\.\\/common'\\); | require\\('\\.\\.\\/common'\\); |
| tst-multi-character-sanitization.js:113:7:113:41 | x.repla ... /g, "") | The replaced string may still contain a substring that starts matching at $@. | tst-multi-character-sanitization.js:113:18:113:34 | \\.\\.\\/\\.\\.\\/lib\\/ | \\.\\.\\/\\.\\.\\/lib\\/ |

View File

@@ -0,0 +1 @@
Security/CWE-116/IncompleteMultiCharacterSanitization.ql

View File

@@ -0,0 +1,116 @@
// CVE-2019-10756
(function(content) {
content = content.replace(/<.*cript.*\/scrip.*>/gi, ""); // NOT OK
content = content.replace(/ on\w+=".*"/g, ""); // NOT OK
content = content.replace(/ on\w+=\'.*\'/g, ""); // NOT OK
return content;
});
(function(content) {
content = content.replace(/<.*cript.*/gi, ""); // NOT OK
content = content.replace(/.on\w+=.*".*"/g, ""); // NOT OK
content = content.replace(/.on\w+=.*\'.*\'/g, ""); // NOT OK
return content;
});
// CVE-2020-7656
(function(responseText) {
var rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi;
responseText.replace(rscript, ""); // NOT OK
return responseText;
});
// CVE-2019-1010091
(function(text) {
text = text.replace(/<!--|--!?>/g, ""); // NOT OK
return text;
});
(function(text) {
while (/<!--|--!?>/g.test(text)) {
text = text.replace(/<!--|--!?>/g, ""); // OK
}
return text;
});
// CVE-2019-10767
(function(id) {
id = id.replace(/\.\./g, ""); // NOT OK
return id;
});
(function(id) {
id = id.replace(/[\]\[*,;'"`<>\\?\/]/g, ""); // OK (or is it?)
return id;
});
// CVE-2019-8903
(function(req) {
var REG_TRAVEL = /(\/)?\.\.\//g;
req.url = req.url.replace(REG_TRAVEL, ""); // NOT OK
});
(function(req) {
var beg;
for (var i = 0; i < req.url.length; i++) {
if (req.url[i] === "." && req.url[i + 1] === "/") beg = i + 1;
else if (req.url[i] === "?") break;
}
if (beg) req.url = req.url.substring(beg);
});
// New cases
(function(x) {
x = x.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/g, ""); // NOT OK
x = x.replace(/(\/|\s)on\w+=(\'|")?[^"]*(\'|")?/g, ""); // NOT OK
x = x.replace(/<\/script>/g, ""); // OK
x = x.replace(/<(.)?br(.)?>/g, ""); // OK
x = x.replace(/<\/?b>/g, ""); // OK
x = x.replace(/<(ul|ol)><\/(ul|ol)>/gi, ""); // OK
x = x.replace(/<li><\/li>/gi, ""); // OK
x = x.replace(/<!--(.*?)-->/gm, ""); // NOT OK
x = x.replace(/\sng-[a-z-]+/, ""); // OK (single ng-attribute, should be flagged by some other query!)
x = x.replace(/\sng-[a-z-]+/g, ""); // NOT OK (ng-attributes)
x = x.replace(/(<!--\[CDATA\[|\]\]-->)/g, "\n"); // OK: not a sanitizer
x = x.replace(/<script.+desktop\-only.+<\/script>/g, ""); // OK, but still flagged [INCONSISTENCY]
x = x.replace(/<script async.+?<\/script>/g, ""); // OK, but still flagged [INCONSISTENCY]
x = x.replace(/<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi, ""); // NOT OK
x = x.replace(/\x2E\x2E\x2F\x2E\x2E\x2F/g, ""); // NOT OK (matches "../../")
x = x.replace(/<script.*>.*<\/script>/gi, ""); // NOT OK
x = x.replace(/^(\.\.\/?)+/g, ""); // OK
// NOT OK
x = x.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/g, function(
$0
) {
return unknown ? $0 : "";
});
x = x.replace(/<\/?([a-z][a-z0-9]*)\b[^>]*>/gi, ""); // NOT OK [INCONSISTENCY]
x = x.replace(/\.\./g, ""); // NOT OK
x = x.replace(/\.\.\//g, ""); // NOT OK
x = x.replace(/\/\.\./g, ""); // NOT OK
x = x.replace(/<script(.*?)>([\s\S]*?)<\/script>/gi, ""); // NOT OK
x = x.replace(/<(script|del)(?=[\s>])[\w\W]*?<\/\1\s*>/gi, ""); // NOT OK
x = x.replace(/\<script[\s\S]*?\>[\s\S]*?\<\/script\>/g, ""); // NOT OK
x = x.replace(/<(script|style|title)[^<]+<\/(script|style|title)>/gm, ""); // NOT OK
x = x.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, ""); // NOT OK
x = x.replace(/<script[\s\S]*?<\/script>/gi, ""); // NOT OK
x = x.replace(/ ?<!-- ?/g, ""); // NOT OK
x = x.replace(/require\('\.\.\/common'\);/g, ""); // OK [INCONSISTENCY] permit alphanum-suffix after the dots?
x = x.replace(/\.\.\/\.\.\/lib\//g, ""); // OK [INCONSISTENCY] permit alphanum-suffix after the dots?
return x;
});

View File

@@ -0,0 +1,21 @@
| lib/tst.js:7:1:7:45 | app.use ... rname)) | Serves the folder query-tests/Security/CWE-200/lib, which can contain private information. |
| lib/tst.js:9:1:9:43 | app.use ... otDir)) | Serves the folder query-tests/Security/CWE-200/lib, which can contain private information. |
| lib/tst.js:11:1:11:52 | app.use ... + '/')) | Serves the folder query-tests/Security/CWE-200/lib, which can contain private information. |
| private-file-exposure.js:8:1:8:49 | app.use ... ular')) | Serves the folder "./node_modules/angular", which can contain private information. |
| private-file-exposure.js:9:1:9:59 | app.use ... ular')) | Serves the folder "node_modules/angular", which can contain private information. |
| private-file-exposure.js:10:1:10:67 | app.use ... mate')) | Serves the folder "node_modules/angular-animate", which can contain private information. |
| private-file-exposure.js:11:1:11:67 | app.use ... ular')) | Serves the folder "/node_modules/angular", which can contain private information. |
| private-file-exposure.js:12:1:12:78 | app.use ... ute/')) | Serves the folder "/node_modules/angular-route/", which can contain private information. |
| private-file-exposure.js:13:1:13:48 | app.use ... ular')) | Serves the folder "/node_modules/angular", which can contain private information. |
| private-file-exposure.js:14:1:14:84 | app.use ... les'))) | Serves the folder "../node_modules", which can contain private information. |
| private-file-exposure.js:15:1:15:35 | app.use ... ('./')) | Serves the current working folder, which can contain private information. |
| private-file-exposure.js:16:1:16:67 | app.use ... lar/')) | Serves the folder "./node_modules/angular/", which can contain private information. |
| private-file-exposure.js:17:1:17:78 | app.use ... ar/'))) | Serves the folder "./node_modules/angular/", which can contain private information. |
| private-file-exposure.js:18:1:18:74 | app.use ... les"))) | Serves the folder "/node_modules", which can contain private information. |
| private-file-exposure.js:19:1:19:88 | app.use ... lar/')) | Serves the folder "/node_modules/angular/", which can contain private information. |
| private-file-exposure.js:22:1:22:58 | app.use ... lar/')) | Serves the folder "/node_modules/angular/", which can contain private information. |
| private-file-exposure.js:40:1:40:88 | app.use ... lar/')) | Serves the folder "/node_modules/angular/", which can contain private information. |
| private-file-exposure.js:41:1:41:97 | app.use ... lar/')) | Serves the folder "/node_modules/angular/", which can contain private information. |
| private-file-exposure.js:42:1:42:66 | app.use ... dir())) | Serves the home folder , which can contain private information. |
| private-file-exposure.js:43:1:43:46 | app.use ... )("/")) | Serves the root folder, which can contain private information. |
| private-file-exposure.js:51:5:51:88 | app.use ... les'))) | Serves the folder "../node_modules", which can contain private information. |

View File

@@ -0,0 +1 @@
Security/CWE-200/PrivateFileExposure.ql

View File

@@ -0,0 +1,6 @@
{
"name": "foo",
"dependencies": {
"async": "3.2.0"
}
}

View File

@@ -0,0 +1,11 @@
var express = require('express');
var path = require("path");
var app = express();
app.use('basedir', express.static(__dirname)); // BAD
const rootDir = __dirname;
app.use('basedir', express.static(rootDir)); // BAD
app.use('/monthly', express.static(__dirname + '/')); // BAD

View File

@@ -0,0 +1,62 @@
var express = require('express');
var path = require("path");
var app = express();
// Not good.
app.use(express.static('./node_modules/angular'));
app.use('/angular', express.static('node_modules/angular'));
app.use('/animate', express.static('node_modules/angular-animate'));
app.use('/js', express.static(__dirname + '/node_modules/angular'));
app.use('/router', express.static(__dirname + '/node_modules/angular-route/'));
app.use(express.static('/node_modules/angular'));
app.use('/node_modules', express.static(path.resolve(__dirname, '../node_modules')));
app.use('/js',express.static('./'));
app.use('/angular', express.static("./node_modules" + '/angular/'));
app.use('/angular', express.static(path.join("./node_modules" + '/angular/')));
app.use('/angular', express.static(path.join(__dirname, "/node_modules")));
app.use('/angular', express.static(path.join(__dirname, "/node_modules") + '/angular/'));
const rootDir = __dirname;
const nodeDir = path.join(rootDir + "/node_modules");
app.use('/angular', express.static(nodeDir + '/angular/'));
// Good
app.use(express.static('./node_modules/jquery/dist'));
app.use(express.static('./node_modules/bootstrap/dist'));
app.use('/js', express.static(__dirname + '/node_modules/html5sortable/dist'));
app.use('/css', express.static(__dirname + '/css'));
app.use('/favicon.ico', express.static(__dirname + '/favicon.ico'));
app.use(express.static(__dirname + "/static"));
app.use(express.static(__dirname + "/static/js"));
app.use('/docs/api', express.static('docs/api'));
app.use('/js/', express.static('node_modules/bootstrap/dist/js'))
app.use('/css/', express.static('node_modules/font-awesome/css'));
app.use('basedir', express.static(__dirname)); // GOOD, because there is no package.json in the same folder.
app.use('/monthly', express.static(__dirname + '/')); // GOOD, because there is no package.json in the same folder.
const connect = require("connect");
app.use('/angular', connect.static(path.join(__dirname, "/node_modules") + '/angular/')); // NOT OK
app.use('/angular', require('serve-static')(path.join(__dirname, "/node_modules") + '/angular/')); // NOT OK
app.use('/home', require('serve-static')(require("os").homedir())); // NOT OK
app.use('/root', require('serve-static')("/")); // NOT OK
// Bad documentation example
function bad() {
var express = require('express');
var app = express();
app.use('/node_modules', express.static(path.resolve(__dirname, '../node_modules'))); // NOT OK
}
// Good documentation example
function good() {
var express = require('express');
var app = express();
app.use("jquery", express.static('./node_modules/jquery/dist')); // OK
app.use("bootstrap", express.static('./node_modules/bootstrap/dist')); // OK
}

View File

@@ -0,0 +1,9 @@
| tst.js:15:3:15:27 | rejectU ... : false | Disabling certificate validation is strongly discouraged. |
| tst.js:18:1:18:40 | process ... HORIZED | Disabling certificate validation is strongly discouraged. |
| tst.js:21:3:21:27 | rejectU ... : false | Disabling certificate validation is strongly discouraged. |
| tst.js:25:3:25:27 | rejectU ... : false | Disabling certificate validation is strongly discouraged. |
| tst.js:29:3:29:27 | rejectU ... : false | Disabling certificate validation is strongly discouraged. |
| tst.js:34:3:34:27 | rejectU ... : false | Disabling certificate validation is strongly discouraged. |
| tst.js:39:2:39:29 | rejectU ... ndirect | Disabling certificate validation is strongly discouraged. |
| tst.js:45:2:45:28 | rejectU ... !!false | Disabling certificate validation is strongly discouraged. |
| tst.js:48:2:48:26 | rejectU ... : !true | Disabling certificate validation is strongly discouraged. |

View File

@@ -0,0 +1 @@
Security/CWE-295/DisablingCertificateValidation.ql

View File

@@ -0,0 +1,70 @@
let https = require("https"),
tls = require("tls");
new https.Agent(); // OK
new https.Agent({
rejectUnauthorized: true // OK
});
unknownCall({
rejectUnauthorized: false // OK (but probably unsafe after all)
});
new https.Agent({
rejectUnauthorized: false // NOT OK
});
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; // NOT OK
https.get({
rejectUnauthorized: false // NOT OK
});
new tls.TLSSocket(socket, {
rejectUnauthorized: false // NOT OK
});
tls.connect({
rejectUnauthorized: false // NOT OK
});
let socket = new tls.TLSSocket();
socket.renegotiate({
rejectUnauthorized: false // NOT OK
});
let indirect = false;
new https.Agent({
rejectUnauthorized: indirect // NOT OK
});
new https.Agent({
rejectUnauthorized: !false // OK
});
new https.Agent({
rejectUnauthorized: !!false // NOT OK
});
new https.Agent({
rejectUnauthorized: !true // NOT OK
});
new https.Agent({
rejectUnauthorized: !!true // OK
});
new https.Agent({
rejectUnauthorized: unknown() // OK
});
new https.Agent({
rejectUnauthorized: !getOptions().selfSignedSSL // OK
});
new https.Agent({
rejectUnauthorized: getOptions().rejectUnauthorized // OK
});
new https.Agent({
rejectUnauthorized: !!getOptions().rejectUnauthorized // OK
});
new https.Agent({
rejectUnauthorized: getOptions() == null ? true : getOptions().verifySsl // OK
});
new https.Agent({
rejectUnauthorized: typeof getOptions().rejectUnauthorized === 'boolean' ? getOptions().rejectUnauthorized : undefined // OK
});

View File

@@ -0,0 +1,57 @@
nodes
| build-leaks.js:4:39:6:1 | { // NO ... .env)\\n} |
| build-leaks.js:4:39:6:1 | { // NO ... .env)\\n} |
| build-leaks.js:5:20:5:46 | JSON.st ... ss.env) |
| build-leaks.js:5:35:5:45 | process.env |
| build-leaks.js:5:35:5:45 | process.env |
| build-leaks.js:13:11:19:10 | raw |
| build-leaks.js:13:17:19:10 | Object. ... }) |
| build-leaks.js:14:18:14:20 | env |
| build-leaks.js:15:24:15:34 | process.env |
| build-leaks.js:15:24:15:34 | process.env |
| build-leaks.js:16:20:16:22 | env |
| build-leaks.js:21:11:26:5 | stringifed |
| build-leaks.js:21:24:26:5 | {\\n ... )\\n } |
| build-leaks.js:22:24:25:14 | Object. ... }, {}) |
| build-leaks.js:22:49:22:51 | env |
| build-leaks.js:23:39:23:41 | raw |
| build-leaks.js:24:20:24:22 | env |
| build-leaks.js:30:22:30:31 | stringifed |
| build-leaks.js:34:26:34:57 | getEnv( ... ngified |
| build-leaks.js:34:26:34:57 | getEnv( ... ngified |
| build-leaks.js:40:9:40:60 | pw |
| build-leaks.js:40:14:40:60 | url.par ... assword |
| build-leaks.js:40:14:40:60 | url.par ... assword |
| build-leaks.js:41:43:41:86 | { "proc ... y(pw) } |
| build-leaks.js:41:43:41:86 | { "proc ... y(pw) } |
| build-leaks.js:41:67:41:84 | JSON.stringify(pw) |
| build-leaks.js:41:82:41:83 | pw |
edges
| build-leaks.js:5:20:5:46 | JSON.st ... ss.env) | build-leaks.js:4:39:6:1 | { // NO ... .env)\\n} |
| build-leaks.js:5:20:5:46 | JSON.st ... ss.env) | build-leaks.js:4:39:6:1 | { // NO ... .env)\\n} |
| build-leaks.js:5:35:5:45 | process.env | build-leaks.js:5:20:5:46 | JSON.st ... ss.env) |
| build-leaks.js:5:35:5:45 | process.env | build-leaks.js:5:20:5:46 | JSON.st ... ss.env) |
| build-leaks.js:13:11:19:10 | raw | build-leaks.js:23:39:23:41 | raw |
| build-leaks.js:13:17:19:10 | Object. ... }) | build-leaks.js:13:11:19:10 | raw |
| build-leaks.js:14:18:14:20 | env | build-leaks.js:16:20:16:22 | env |
| build-leaks.js:15:24:15:34 | process.env | build-leaks.js:14:18:14:20 | env |
| build-leaks.js:15:24:15:34 | process.env | build-leaks.js:14:18:14:20 | env |
| build-leaks.js:16:20:16:22 | env | build-leaks.js:13:17:19:10 | Object. ... }) |
| build-leaks.js:21:11:26:5 | stringifed | build-leaks.js:30:22:30:31 | stringifed |
| build-leaks.js:21:24:26:5 | {\\n ... )\\n } | build-leaks.js:21:11:26:5 | stringifed |
| build-leaks.js:22:24:25:14 | Object. ... }, {}) | build-leaks.js:21:24:26:5 | {\\n ... )\\n } |
| build-leaks.js:22:49:22:51 | env | build-leaks.js:24:20:24:22 | env |
| build-leaks.js:23:39:23:41 | raw | build-leaks.js:22:49:22:51 | env |
| build-leaks.js:24:20:24:22 | env | build-leaks.js:22:24:25:14 | Object. ... }, {}) |
| build-leaks.js:30:22:30:31 | stringifed | build-leaks.js:34:26:34:57 | getEnv( ... ngified |
| build-leaks.js:30:22:30:31 | stringifed | build-leaks.js:34:26:34:57 | getEnv( ... ngified |
| build-leaks.js:40:9:40:60 | pw | build-leaks.js:41:82:41:83 | pw |
| build-leaks.js:40:14:40:60 | url.par ... assword | build-leaks.js:40:9:40:60 | pw |
| build-leaks.js:40:14:40:60 | url.par ... assword | build-leaks.js:40:9:40:60 | pw |
| build-leaks.js:41:67:41:84 | JSON.stringify(pw) | build-leaks.js:41:43:41:86 | { "proc ... y(pw) } |
| build-leaks.js:41:67:41:84 | JSON.stringify(pw) | build-leaks.js:41:43:41:86 | { "proc ... y(pw) } |
| build-leaks.js:41:82:41:83 | pw | build-leaks.js:41:67:41:84 | JSON.stringify(pw) |
#select
| build-leaks.js:4:39:6:1 | { // NO ... .env)\\n} | build-leaks.js:5:35:5:45 | process.env | build-leaks.js:4:39:6:1 | { // NO ... .env)\\n} | Sensitive data returned by $@ is stored in a build artifact here. | build-leaks.js:5:35:5:45 | process.env | process environment |
| build-leaks.js:34:26:34:57 | getEnv( ... ngified | build-leaks.js:15:24:15:34 | process.env | build-leaks.js:34:26:34:57 | getEnv( ... ngified | Sensitive data returned by $@ is stored in a build artifact here. | build-leaks.js:15:24:15:34 | process.env | process environment |
| build-leaks.js:41:43:41:86 | { "proc ... y(pw) } | build-leaks.js:40:14:40:60 | url.par ... assword | build-leaks.js:41:43:41:86 | { "proc ... y(pw) } | Sensitive data returned by $@ is stored in a build artifact here. | build-leaks.js:40:14:40:60 | url.par ... assword | an access to current_password |

View File

@@ -0,0 +1 @@
Security/CWE-312/BuildArtifactLeak.ql

View File

@@ -0,0 +1,42 @@
const webpack = require("webpack");
var plugin = new webpack.DefinePlugin({ // NOT OK
"process.env": JSON.stringify(process.env)
});
// OK
new webpack.DefinePlugin({ 'process.env': JSON.stringify({ DEBUG: process.env.DEBUG }) })
function getEnv(env) {
const raw = Object.keys(process.env)
.reduce((env, key) => {
env[key] = process.env[key]
return env
}, {
NODE_ENV: process.env.NODE_ENV || env || 'development'
})
const stringifed = {
'process.env': Object.keys(raw).reduce((env, key) => {
env[key] = JSON.stringify(raw[key])
return env
}, {})
}
return {
raw: raw,
stringified: stringifed
}
}
new webpack.DefinePlugin(getEnv('production').stringified); // NOT OK
var https = require('https');
var url = require('url');
var server = https.createServer(function (req, res) {
let pw = url.parse(req.url, true).query.current_password;
var plugin = new webpack.DefinePlugin({ "process.env.secret": JSON.stringify(pw) }); // NOT OK
});

View File

@@ -0,0 +1,18 @@
| bad-random.js:3:11:3:61 | crypto. ... s(1)[0] | Using addition on a $@ produces biased results. | bad-random.js:3:11:3:31 | crypto. ... ytes(1) | cryptographically secure random number |
| bad-random.js:3:11:3:61 | crypto. ... s(1)[0] | Using addition on a $@ produces biased results. | bad-random.js:3:38:3:58 | crypto. ... ytes(1) | cryptographically secure random number |
| bad-random.js:4:11:4:61 | crypto. ... s(1)[0] | Using multiplication on a $@ produces biased results. | bad-random.js:4:11:4:31 | crypto. ... ytes(1) | cryptographically secure random number |
| bad-random.js:4:11:4:61 | crypto. ... s(1)[0] | Using multiplication on a $@ produces biased results. | bad-random.js:4:38:4:58 | crypto. ... ytes(1) | cryptographically secure random number |
| bad-random.js:9:28:9:43 | buffer[i] / 25.6 | Using division and rounding the result on a $@ produces biased results. | bad-random.js:6:16:6:40 | crypto. ... (bytes) | cryptographically secure random number |
| bad-random.js:11:17:11:31 | buffer[i] % 100 | Using modulo on a $@ produces biased results. | bad-random.js:6:16:6:40 | crypto. ... (bytes) | cryptographically secure random number |
| bad-random.js:14:11:14:63 | Number( ... (0, 3)) | Using string concatenation on a $@ produces biased results. | bad-random.js:14:25:14:45 | crypto. ... ytes(3) | cryptographically secure random number |
| bad-random.js:73:32:73:42 | byte / 25.6 | Using division and rounding the result on a $@ produces biased results. | bad-random.js:70:20:70:44 | crypto. ... (bytes) | cryptographically secure random number |
| bad-random.js:75:21:75:30 | byte % 100 | Using modulo on a $@ produces biased results. | bad-random.js:70:20:70:44 | crypto. ... (bytes) | cryptographically secure random number |
| bad-random.js:81:11:81:51 | secureR ... (10)[0] | Using addition on a $@ produces biased results. | bad-random.js:81:11:81:26 | secureRandom(10) | cryptographically secure random number |
| bad-random.js:81:11:81:51 | secureR ... (10)[0] | Using addition on a $@ produces biased results. | bad-random.js:81:33:81:48 | secureRandom(10) | cryptographically secure random number |
| bad-random.js:85:11:85:35 | goodRan ... Random2 | Using addition on a $@ produces biased results. | bad-random.js:83:23:83:38 | secureRandom(10) | cryptographically secure random number |
| bad-random.js:85:11:85:35 | goodRan ... Random2 | Using addition on a $@ produces biased results. | bad-random.js:84:23:84:38 | secureRandom(10) | cryptographically secure random number |
| bad-random.js:87:16:87:24 | bad + bad | Using addition on a $@ produces biased results. | bad-random.js:83:23:83:38 | secureRandom(10) | cryptographically secure random number |
| bad-random.js:87:16:87:24 | bad + bad | Using addition on a $@ produces biased results. | bad-random.js:84:23:84:38 | secureRandom(10) | cryptographically secure random number |
| bad-random.js:90:29:90:54 | secureR ... / 25.6 | Using division and rounding the result on a $@ produces biased results. | bad-random.js:90:29:90:44 | secureRandom(10) | cryptographically secure random number |
| bad-random.js:96:29:96:58 | crypto. ... ] / 100 | Using division and rounding the result on a $@ produces biased results. | bad-random.js:96:29:96:49 | crypto. ... ytes(1) | cryptographically secure random number |
| bad-random.js:118:17:118:45 | crypto. ... 0] % 10 | Using modulo on a $@ produces biased results. | bad-random.js:118:17:118:37 | crypto. ... ytes(1) | cryptographically secure random number |

View File

@@ -0,0 +1 @@
Security/CWE-327/BadRandomness.ql

View File

@@ -0,0 +1,129 @@
const crypto = require('crypto');
var bad = crypto.randomBytes(1)[0] + crypto.randomBytes(1)[0]; // NOT OK
var bad = crypto.randomBytes(1)[0] * crypto.randomBytes(1)[0]; // NOT OK
const buffer = crypto.randomBytes(bytes);
const digits = [];
for (let i = 0; i < buffer.length; ++i) {
digits.push(Math.floor(buffer[i] / 25.6)); // NOT OK
digits.push(buffer[i] % 8); // OK - input is a random byte, so the output is a uniformly random number between 0 and 7.
digits.push(buffer[i] % 100); // NOT OK
}
var bad = Number('0.' + crypto.randomBytes(3).readUIntBE(0, 3)); // NOT OK
var good = Number(10 + crypto.randomBytes(3).readUIntBE(0, 3)); // OK
const internals = {};
exports.randomDigits = function (size) {
const digits = [];
let buffer = internals.random(size * 2);
let pos = 0;
while (digits.length < size) {
if (pos >= buffer.length) {
buffer = internals.random(size * 2);
pos = 0;
}
if (buffer[pos] < 250) {
digits.push(buffer[pos] % 10); // GOOD - protected by a bias-checking comparison.
}
++pos;
}
return digits.join('');
};
internals.random = function (bytes) {
try {
return crypto.randomBytes(bytes);
}
catch (err) {
throw new Error("Failed to make bits.");
}
};
exports.randomDigits2 = function (size) {
const digits = [];
let buffer = crypto.randomBytes(size * 2);
let pos = 0;
while (digits.length < size) {
if (pos >= buffer.length) {
buffer = internals.random(size * 2);
pos = 0;
}
var num = buffer[pos];
if (num < 250) {
digits.push(num % 10); // GOOD - protected by a bias-checking comparison.
}
++pos;
}
return digits.join('');
};
function setSteps() {
const buffer = crypto.randomBytes(bytes);
const digits = [];
for (const byte of buffer.values()) {
digits.push(Math.floor(byte / 25.6)); // NOT OK
digits.push(byte % 8); // OK - 8 is a power of 2, so the result is unbiased.
digits.push(byte % 100); // NOT OK
}
}
const secureRandom = require("secure-random");
var bad = secureRandom(10)[0] + secureRandom(10)[0]; // NOT OK
var goodRandom1 = 5 + secureRandom(10)[0];
var goodRandom2 = 5 + secureRandom(10)[0];
var bad = goodRandom1 + goodRandom2; // NOT OK
var dontFlag = bad + bad; // OK - the operands have already been flagged - but flagged anyway due to us not detecting that [INCONSISTENCY].
var good = secureRandom(10)[0] / 0xff; // OK - result is not rounded.
var good = Math.ceil(0.5 - (secureRandom(10)[0] / 25.6)); // NOT OK - division generally introduces bias - but not flagged due to not looking through nested arithmetic.
var good = (crypto.randomBytes(1)[0] << 8) + crypto.randomBytes(3)[0]; // OK - bit shifts are usually used to construct larger/smaller numbers,
var good = Math.floor(max * (crypto.randomBytes(1)[0] / 0xff)); // OK - division by 0xff (255) gives a uniformly random number between 0 and 1.
var bad = Math.floor(max * (crypto.randomBytes(1)[0] / 100)); // NOT OK - division by 100 gives bias - but not flagged due to not looking through nested arithmetic.
var crb = crypto.randomBytes(4);
var cryptoRand = 0x01000000 * crb[0] + 0x00010000 * crb[1] + 0x00000100 * crb[2] + 0x00000001 * crb[3]; // OK - producing a larger number from smaller numbers.
var good = (secureRandom(10)[0] + "foo") + (secureRandom(10)[0] + "bar"); // OK - string concat
var eight = 8;
var good = crypto.randomBytes(4)[0] % eight; // OK - modulo by power of 2.
var twoHundredAndFiftyFive = 0xff;
var good = Math.floor(max * (crypto.randomBytes(1)[0] / twoHundredAndFiftyFive)); // OK - division by 0xff (255) gives a uniformly random number between 0 and 1.
var a = crypto.randomBytes(10);
var good = ((a[i] & 31) * 0x1000000000000) + (a[i + 1] * 0x10000000000) + (a[i + 2] * 0x100000000) + (a[i + 3] * 0x1000000) + (a[i + 4] << 16) + (a[i + 5] << 8) + a[i + 6]; // OK - generating a large number from smaller bytes.
var good = (a[i] * 0x100000000) + a[i + 6]; // OK - generating a large number from smaller bytes.
var good = (a[i + 2] * 0x10000000) + a[i + 6]; // OK - generating a large number from smaller bytes.
var foo = 0xffffffffffff + 0xfffffffffff + 0xffffffffff + 0xfffffffff + 0xffffffff + 0xfffffff + 0xffffff
// Bad documentation example:
const digits = [];
for (let i = 0; i < 10; i++) {
digits.push(crypto.randomBytes(1)[0] % 10); // NOT OK
}
// Good documentation example:
const digits = [];
while (digits.length < 10) {
const byte = crypto.randomBytes(1)[0];
if (byte >= 250) {
continue;
}
digits.push(byte % 10); // OK
}

View File

@@ -0,0 +1,38 @@
nodes
| insecure-download.js:5:16:5:28 | installer.url |
| insecure-download.js:5:16:5:28 | installer.url |
| insecure-download.js:9:27:9:138 | 'http:/ ... ll.exe' |
| insecure-download.js:9:27:9:138 | 'http:/ ... ll.exe' |
| insecure-download.js:15:18:15:40 | buildTo ... llerUrl |
| insecure-download.js:30:12:30:42 | "http:/ ... fe.APK" |
| insecure-download.js:30:12:30:42 | "http:/ ... fe.APK" |
| insecure-download.js:30:12:30:42 | "http:/ ... fe.APK" |
| insecure-download.js:36:9:36:45 | url |
| insecure-download.js:36:15:36:45 | "http:/ ... fe.APK" |
| insecure-download.js:36:15:36:45 | "http:/ ... fe.APK" |
| insecure-download.js:37:23:37:25 | url |
| insecure-download.js:37:23:37:25 | url |
| insecure-download.js:39:26:39:28 | url |
| insecure-download.js:39:26:39:28 | url |
| insecure-download.js:41:12:41:41 | "ftp:// ... fe.APK" |
| insecure-download.js:41:12:41:41 | "ftp:// ... fe.APK" |
| insecure-download.js:41:12:41:41 | "ftp:// ... fe.APK" |
edges
| insecure-download.js:9:27:9:138 | 'http:/ ... ll.exe' | insecure-download.js:15:18:15:40 | buildTo ... llerUrl |
| insecure-download.js:9:27:9:138 | 'http:/ ... ll.exe' | insecure-download.js:15:18:15:40 | buildTo ... llerUrl |
| insecure-download.js:15:18:15:40 | buildTo ... llerUrl | insecure-download.js:5:16:5:28 | installer.url |
| insecure-download.js:15:18:15:40 | buildTo ... llerUrl | insecure-download.js:5:16:5:28 | installer.url |
| insecure-download.js:30:12:30:42 | "http:/ ... fe.APK" | insecure-download.js:30:12:30:42 | "http:/ ... fe.APK" |
| insecure-download.js:36:9:36:45 | url | insecure-download.js:37:23:37:25 | url |
| insecure-download.js:36:9:36:45 | url | insecure-download.js:37:23:37:25 | url |
| insecure-download.js:36:9:36:45 | url | insecure-download.js:39:26:39:28 | url |
| insecure-download.js:36:9:36:45 | url | insecure-download.js:39:26:39:28 | url |
| insecure-download.js:36:15:36:45 | "http:/ ... fe.APK" | insecure-download.js:36:9:36:45 | url |
| insecure-download.js:36:15:36:45 | "http:/ ... fe.APK" | insecure-download.js:36:9:36:45 | url |
| insecure-download.js:41:12:41:41 | "ftp:// ... fe.APK" | insecure-download.js:41:12:41:41 | "ftp:// ... fe.APK" |
#select
| insecure-download.js:5:16:5:28 | installer.url | insecure-download.js:9:27:9:138 | 'http:/ ... ll.exe' | insecure-download.js:5:16:5:28 | installer.url | $@ of sensitive file from $@. | insecure-download.js:5:9:5:44 | nugget( ... => { }) | Download | insecure-download.js:9:27:9:138 | 'http:/ ... ll.exe' | HTTP source |
| insecure-download.js:30:12:30:42 | "http:/ ... fe.APK" | insecure-download.js:30:12:30:42 | "http:/ ... fe.APK" | insecure-download.js:30:12:30:42 | "http:/ ... fe.APK" | $@ of sensitive file from $@. | insecure-download.js:30:5:30:43 | nugget( ... e.APK") | Download | insecure-download.js:30:12:30:42 | "http:/ ... fe.APK" | HTTP source |
| insecure-download.js:37:23:37:25 | url | insecure-download.js:36:15:36:45 | "http:/ ... fe.APK" | insecure-download.js:37:23:37:25 | url | $@ of sensitive file from $@. | insecure-download.js:37:5:37:42 | cp.exec ... () {}) | Download | insecure-download.js:36:15:36:45 | "http:/ ... fe.APK" | HTTP source |
| insecure-download.js:39:26:39:28 | url | insecure-download.js:36:15:36:45 | "http:/ ... fe.APK" | insecure-download.js:39:26:39:28 | url | $@ of sensitive file from $@. | insecure-download.js:39:5:39:46 | cp.exec ... () {}) | Download | insecure-download.js:36:15:36:45 | "http:/ ... fe.APK" | HTTP source |
| insecure-download.js:41:12:41:41 | "ftp:// ... fe.APK" | insecure-download.js:41:12:41:41 | "ftp:// ... fe.APK" | insecure-download.js:41:12:41:41 | "ftp:// ... fe.APK" | $@ of sensitive file from $@. | insecure-download.js:41:5:41:42 | nugget( ... e.APK") | Download | insecure-download.js:41:12:41:41 | "ftp:// ... fe.APK" | HTTP source |

View File

@@ -0,0 +1 @@
Security/CWE-829/InsecureDownload.ql

View File

@@ -0,0 +1,42 @@
const nugget = require('nugget');
function foo() {
function downloadTools(installer) {
nugget(installer.url, {}, () => { }) // NOT OK
}
var constants = {
buildTools: {
installerUrl: 'http://download.microsoft.com/download/5/f/7/5f7acaeb-8363-451f-9425-68a90f98b238/visualcppbuildtools_full.exe'
}
}
function getBuildToolsInstallerPath() {
const buildTools = constants.buildTools
return {
url: buildTools.installerUrl
}
}
downloadTools(getBuildToolsInstallerPath())
}
const request = require('request');
function bar() {
request('http://www.google.com', function () { }); // OK
nugget("https://download.microsoft.com/download/5/f/7/5f7acaeb-8363-451f-9425-68a90f98b238/visualcppbuildtools_full.exe") // OK
nugget("http://example.org/unsafe.APK") // NOT OK
}
var cp = require("child_process")
function baz() {
var url = "http://example.org/unsafe.APK";
cp.exec("curl " + url, function () {}); // NOT OK
cp.execFile("curl", [url], function () {}); // NOT OK
nugget("ftp://example.org/unsafe.APK") // NOT OK
}