mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
Merge pull request #3765 from asger-semmle/js-team-sprint-merge2
JS: Merge js-team-sprint
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Using string concatenation to construct JavaScript code can be error-prone, or in the worst
|
||||
case, enable code injection if an input is constructed by an attacker.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
If using <code>JSON.stringify</code> or a HTML sanitizer to sanitize a string inserted into
|
||||
JavaScript code, then make sure to perform additional sanitization or remove potentially
|
||||
dangerous characters.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The example below constructs a function that assigns the number 42 to the property <code>key</code>
|
||||
on an object <code>obj</code>. However, if <code>key</code> contains <code></script></code>, then
|
||||
the generated code will break out of a <code></script></code> if inserted into a
|
||||
<code></script></code> tag.
|
||||
</p>
|
||||
<sample src="examples/ImproperCodeSanitization.js" />
|
||||
<p>
|
||||
The issue has been fixed by escaping potentially dangerous characters, as shown below.
|
||||
</p>
|
||||
<sample src="examples/ImproperCodeSanitizationFixed.js" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>OWASP: <a href="https://www.owasp.org/index.php/Code_Injection">Code Injection</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* @name Improper code sanitization
|
||||
* @description Escaping code as HTML does not provide protection against code injection.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id js/bad-code-sanitization
|
||||
* @tags security
|
||||
* external/cwe/cwe-094
|
||||
* external/cwe/cwe-079
|
||||
* external/cwe/cwe-116
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.ImproperCodeSanitization::ImproperCodeSanitization
|
||||
import DataFlow::PathGraph
|
||||
private import semmle.javascript.heuristics.HeuristicSinks
|
||||
private import semmle.javascript.security.dataflow.CodeInjectionCustomizations
|
||||
|
||||
/**
|
||||
* Gets a type-tracked instance of `RemoteFlowSource` using type-tracker `t`.
|
||||
*/
|
||||
private DataFlow::Node remoteFlow(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result instanceof RemoteFlowSource
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2, DataFlow::Node prev | prev = remoteFlow(t2) |
|
||||
t2 = t.smallstep(prev, result)
|
||||
or
|
||||
any(TaintTracking::AdditionalTaintStep dts).step(prev, result) and
|
||||
t = t2
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a type-tracked reference to a `RemoteFlowSource`.
|
||||
*/
|
||||
private DataFlow::Node remoteFlow() { result = remoteFlow(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/**
|
||||
* Gets a type-back-tracked instance of a code injection sink using type-tracker `t`.
|
||||
*/
|
||||
private DataFlow::Node endsInCodeInjectionSink(DataFlow::TypeBackTracker t) {
|
||||
t.start() and
|
||||
(
|
||||
result instanceof CodeInjection::Sink
|
||||
or
|
||||
result instanceof HeuristicCodeInjectionSink and
|
||||
not result instanceof StringOps::ConcatenationRoot // the heuristic CodeInjection sink looks for string-concats, we are not interrested in those here.
|
||||
)
|
||||
or
|
||||
exists(DataFlow::TypeBackTracker t2 | t = t2.smallstep(result, endsInCodeInjectionSink(t2)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to to a data-flow node that ends in a code injection sink.
|
||||
*/
|
||||
private DataFlow::Node endsInCodeInjectionSink() {
|
||||
result = endsInCodeInjectionSink(DataFlow::TypeBackTracker::end())
|
||||
}
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
// Basic detection of duplicate results with `js/code-injection`.
|
||||
not (
|
||||
sink.getNode().(StringOps::ConcatenationLeaf).getRoot() = endsInCodeInjectionSink() and
|
||||
remoteFlow() = source.getNode().(DataFlow::InvokeNode).getAnArgument()
|
||||
)
|
||||
select sink.getNode(), source, sink, "$@ flows to here and is used to construct code.",
|
||||
source.getNode(), "Improperly sanitized value"
|
||||
@@ -0,0 +1,4 @@
|
||||
function createObjectWrite() {
|
||||
const assignment = `obj[${JSON.stringify(key)}]=42`;
|
||||
return `(function(){${assignment}})` // NOT OK
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
const charMap = {
|
||||
'<': '\\u003C',
|
||||
'>' : '\\u003E',
|
||||
'/': '\\u002F',
|
||||
'\\': '\\\\',
|
||||
'\b': '\\b',
|
||||
'\f': '\\f',
|
||||
'\n': '\\n',
|
||||
'\r': '\\r',
|
||||
'\t': '\\t',
|
||||
'\0': '\\0',
|
||||
'\u2028': '\\u2028',
|
||||
'\u2029': '\\u2029'
|
||||
};
|
||||
|
||||
function escapeUnsafeChars(str) {
|
||||
return str.replace(/[<>\b\f\n\r\t\0\u2028\u2029]/g, x => charMap[x])
|
||||
}
|
||||
|
||||
function createObjectWrite() {
|
||||
const assignment = `obj[${escapeUnsafeChars(JSON.stringify(key))}]=42`;
|
||||
return `(function(){${assignment}})` // OK
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<include src="IncompleteSanitization.qhelp" />
|
||||
|
||||
</qhelp>
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* @name Incomplete multi-character sanitization
|
||||
* @description A sanitizer that removes a sequence of characters may reintroduce the dangerous sequence.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @precision high
|
||||
* @id js/incomplete-multi-character-sanitization
|
||||
* @tags correctness
|
||||
* security
|
||||
* external/cwe/cwe-116
|
||||
* external/cwe/cwe-20
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.IncompleteBlacklistSanitizer
|
||||
|
||||
predicate isDangerous(RegExpTerm t) {
|
||||
// path traversals
|
||||
t.getAMatchedString() = ["..", "/..", "../"]
|
||||
or
|
||||
exists(RegExpTerm start |
|
||||
start = t.(RegExpSequence).getAChild() and
|
||||
start.getConstantValue() = "." and
|
||||
start.getSuccessor().getConstantValue() = "." and
|
||||
not [start.getPredecessor(), start.getSuccessor().getSuccessor()].getConstantValue() = "."
|
||||
)
|
||||
or
|
||||
// HTML comments
|
||||
t.getAMatchedString() = "<!--"
|
||||
or
|
||||
// HTML scripts
|
||||
t.getAMatchedString().regexpMatch("(?i)<script.*")
|
||||
or
|
||||
exists(RegExpSequence seq | seq = t |
|
||||
t.getChild(0).getConstantValue() = "<" and
|
||||
// the `cript|scrip` case has been observed in the wild, not sure what the goal of that pattern is...
|
||||
t
|
||||
.getChild(0)
|
||||
.getSuccessor+()
|
||||
.getAMatchedString()
|
||||
.regexpMatch("(?i)iframe|script|cript|scrip|style")
|
||||
)
|
||||
or
|
||||
// HTML attributes
|
||||
exists(string dangerousPrefix | dangerousPrefix = ["ng-", "on"] |
|
||||
t.getAMatchedString().regexpMatch("(i?)" + dangerousPrefix + "[a-z]+")
|
||||
or
|
||||
exists(RegExpTerm start, RegExpTerm event | start = t.getAChild() |
|
||||
start.getConstantValue().regexpMatch("(?i)[^a-z]*" + dangerousPrefix) and
|
||||
event = start.getSuccessor() and
|
||||
exists(RegExpTerm quantified | quantified = event.(RegExpQuantifier).getChild(0) |
|
||||
quantified
|
||||
.(RegExpCharacterClass)
|
||||
.getAChild()
|
||||
.(RegExpCharacterRange)
|
||||
.isRange(["a", "A"], ["z", "Z"]) or
|
||||
[quantified, quantified.(RegExpRange).getAChild()].(RegExpCharacterClassEscape).getValue() =
|
||||
"w"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
from StringReplaceCall replace, RegExpTerm regexp, RegExpTerm dangerous
|
||||
where
|
||||
[replace.getRawReplacement(), replace.getCallback(1).getAReturn()].mayHaveStringValue("") and
|
||||
replace.isGlobal() and
|
||||
regexp = replace.getRegExp().getRoot() and
|
||||
dangerous.getRootTerm() = regexp and
|
||||
isDangerous(dangerous) and
|
||||
// avoid anchored terms
|
||||
not exists(RegExpAnchor a | a.getRootTerm() = regexp) and
|
||||
// avoid flagging wrappers
|
||||
not (
|
||||
dangerous instanceof RegExpAlt or
|
||||
dangerous instanceof RegExpGroup
|
||||
) and
|
||||
// don't flag replace operations in a loop
|
||||
not replace.getReceiver().getALocalSource() = replace
|
||||
select replace, "The replaced string may still contain a substring that starts matching at $@.",
|
||||
dangerous, dangerous.toString()
|
||||
40
javascript/ql/src/Security/CWE-200/PrivateFileExposure.qhelp
Normal file
40
javascript/ql/src/Security/CWE-200/PrivateFileExposure.qhelp
Normal file
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Libraries like <code>express</code> provide easy methods for serving entire
|
||||
directories of static files from a web server.
|
||||
However, using these can sometimes lead to accidental information exposure.
|
||||
If for example the <code>node_modules</code> folder is served, then an attacker
|
||||
can access the <code>_where</code> field from a <code>package.json</code> file,
|
||||
which gives access to the absolute path of the file.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Limit which folders of static files are served from a web server.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
In the example below, all the files from the <code>node_modules</code> are served.
|
||||
This allows clients to easily access all the files inside that folder,
|
||||
which includes potentially private information inside <code>package.json</code> files.
|
||||
</p>
|
||||
<sample src="examples/PrivateFileExposure.js"/>
|
||||
<p>
|
||||
The issue has been fixed below by only serving specific folders within the
|
||||
<code>node_modules</code> folder.
|
||||
</p>
|
||||
<sample src="examples/PrivateFileExposureFixed.js"/>
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>OWASP: <a href="https://www.owasp.org/index.php/Top_10-2017_A3-Sensitive_Data_Exposure">Sensitive Data Exposure</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
129
javascript/ql/src/Security/CWE-200/PrivateFileExposure.ql
Normal file
129
javascript/ql/src/Security/CWE-200/PrivateFileExposure.ql
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* @name Exposure of private files
|
||||
* @description Exposing a node_modules folder, or the project folder to the public, can cause exposure
|
||||
* of private information.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @id js/exposure-of-private-files
|
||||
* @tags security
|
||||
* external/cwe/cwe-200
|
||||
* @precision high
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Holds if `folder` is a node_modules folder, and at most 1 subdirectory down.
|
||||
*/
|
||||
bindingset[folder]
|
||||
predicate isNodeModuleFolder(string folder) {
|
||||
folder.regexpMatch("(\\.?\\.?/)*node_modules(/|(/[a-zA-Z@_-]+/?))?")
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a data-flow node that represents a path to the node_modules folder represented by the string-literal `path`.
|
||||
*/
|
||||
DataFlow::Node getANodeModulePath(string path) {
|
||||
result.getStringValue() = path and
|
||||
isNodeModuleFolder(path)
|
||||
or
|
||||
exists(DataFlow::CallNode resolve |
|
||||
resolve = DataFlow::moduleMember("path", ["resolve", "join"]).getACall()
|
||||
|
|
||||
result = resolve and
|
||||
resolve.getLastArgument() = getANodeModulePath(path)
|
||||
)
|
||||
or
|
||||
exists(StringOps::ConcatenationRoot root | root = result |
|
||||
root.getLastLeaf() = getANodeModulePath(path)
|
||||
)
|
||||
or
|
||||
result.getAPredecessor() = getANodeModulePath(path) // local data-flow
|
||||
or
|
||||
exists(string base, string folder |
|
||||
path = base + folder and
|
||||
folder.regexpMatch("(/)?[a-zA-Z@_-]+/?") and
|
||||
base.regexpMatch("(\\.?\\.?/)*node_modules(/)?") // node_modules, without any sub-folders.
|
||||
|
|
||||
exists(StringOps::ConcatenationRoot root | root = result |
|
||||
root.getNumOperand() = 2 and
|
||||
root.getFirstLeaf() = getANodeModulePath(base) and
|
||||
root.getLastLeaf().getStringValue() = folder
|
||||
)
|
||||
or
|
||||
exists(DataFlow::CallNode resolve |
|
||||
resolve = DataFlow::moduleMember("path", ["resolve", "join"]).getACall()
|
||||
|
|
||||
result = resolve and
|
||||
resolve.getNumArgument() = 2 and
|
||||
resolve.getArgument(0) = getANodeModulePath(path) and
|
||||
resolve.getArgument(1).mayHaveStringValue(folder)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a folder that contains a `package.json` file.
|
||||
*/
|
||||
pragma[noinline]
|
||||
Folder getAPackageJSONFolder() { result = any(PackageJSON json).getFile().getParentContainer() }
|
||||
|
||||
/**
|
||||
* Gets a reference to `dirname`, the home folder, the current working folder, or the root folder.
|
||||
* All of these might cause information to be leaked.
|
||||
*
|
||||
* For `dirname` that can happen if there is a `package.json` file in the same folder.
|
||||
* It is assumed that the presence of a `package.json` file means that a `node_modules` folder can also exist.
|
||||
*
|
||||
* For the root/home/working folder, they contain so much information that they must leak information somehow (e.g. ssh keys in the `~/.ssh` folder).
|
||||
*/
|
||||
DataFlow::Node getALeakingFolder(string description) {
|
||||
exists(ModuleScope ms | result.asExpr() = ms.getVariable("__dirname").getAnAccess()) and
|
||||
result.getFile().getParentContainer() = getAPackageJSONFolder() and
|
||||
description = "the folder " + result.getFile().getParentContainer().getRelativePath()
|
||||
or
|
||||
result = DataFlow::moduleImport("os").getAMemberCall("homedir") and
|
||||
description = "the home folder "
|
||||
or
|
||||
result.mayHaveStringValue("/") and
|
||||
description = "the root folder"
|
||||
or
|
||||
result.getStringValue() = [".", "./"] and
|
||||
description = "the current working folder"
|
||||
or
|
||||
result.getAPredecessor() = getALeakingFolder(description)
|
||||
or
|
||||
exists(StringOps::ConcatenationRoot root | root = result |
|
||||
root.getNumOperand() = 2 and
|
||||
root.getOperand(0) = getALeakingFolder(description) and
|
||||
root.getOperand(1).getStringValue() = "/"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data-flow node that represents a path to the private folder `path`.
|
||||
*/
|
||||
DataFlow::Node getAPrivateFolderPath(string description) {
|
||||
exists(string path |
|
||||
result = getANodeModulePath(path) and description = "the folder \"" + path + "\""
|
||||
)
|
||||
or
|
||||
result = getALeakingFolder(description)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gest a call that serves the folder `path` to the public.
|
||||
*/
|
||||
DataFlow::CallNode servesAPrivateFolder(string description) {
|
||||
result = DataFlow::moduleMember(["express", "connect"], "static").getACall() and
|
||||
result.getArgument(0) = getAPrivateFolderPath(description)
|
||||
or
|
||||
result = DataFlow::moduleImport("serve-static").getACall() and
|
||||
result.getArgument(0) = getAPrivateFolderPath(description)
|
||||
}
|
||||
|
||||
from Express::RouteSetup setup, string path
|
||||
where
|
||||
setup.isUseCall() and
|
||||
setup.getArgument([0 .. 1]) = servesAPrivateFolder(path).getEnclosingExpr()
|
||||
select setup, "Serves " + path + ", which can contain private information."
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
var express = require('express');
|
||||
|
||||
var app = express();
|
||||
|
||||
app.use('/node_modules', express.static(path.resolve(__dirname, '../node_modules')));
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
var express = require('express');
|
||||
|
||||
var app = express();
|
||||
|
||||
app.use("jquery", express.static('./node_modules/jquery/dist'));
|
||||
app.use("bootstrap", express.static('./node_modules/bootstrap/dist'));
|
||||
@@ -0,0 +1,71 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
|
||||
<p>
|
||||
|
||||
Certificate validation is the standard authentication method of a
|
||||
secure TLS connection. Without it, there is no guarantee about who the other party of a TLS connection is, making man-in-the-middle attacks more likely to occur
|
||||
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
||||
When testing software that uses TLS connections, it may be useful to
|
||||
disable the certificate validation temporarily. But disabling it in
|
||||
production environments is strongly discouraged, unless an alternative
|
||||
method of authentication is used.
|
||||
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
|
||||
Do not disable certificate validation for TLS connections.
|
||||
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
|
||||
<p>
|
||||
|
||||
The following example shows a HTTPS connection that
|
||||
transfers confidential information to a remote server. But the
|
||||
connection is not secure since the <code>rejectUnauthorized</code>
|
||||
option of the connection is set to <code>false</code>. As a
|
||||
consequence, anyone can impersonate the remote server, and receive the
|
||||
confidential information.
|
||||
|
||||
</p>
|
||||
|
||||
<sample src="examples/DisablingCertificateValidation.js"/>
|
||||
|
||||
<p>
|
||||
|
||||
To make the connection secure, the
|
||||
<code>rejectUnauthorized</code> option should have its default value,
|
||||
or be explicitly set to <code>true</code>.
|
||||
|
||||
</p>
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
|
||||
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Transport_Layer_Security">Transport Layer Security (TLS)</a></li>
|
||||
|
||||
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Man-in-the-middle_attack">Man-in-the-middle attack</a></li>
|
||||
|
||||
<li>Node.js: <a href="https://nodejs.org/api/tls.html">TLS (SSL)</a></li>
|
||||
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* @name Disabling certificate validation
|
||||
* @description Disabling cryptographic certificate validation can cause security vulnerabilities.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @precision very-high
|
||||
* @id js/disabling-certificate-validation
|
||||
* @tags security
|
||||
* external/cwe-295
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Gets an options object for a TLS connection.
|
||||
*/
|
||||
DataFlow::ObjectLiteralNode tlsOptions() {
|
||||
exists(DataFlow::InvokeNode invk | result.flowsTo(invk.getAnArgument()) |
|
||||
invk instanceof NodeJSLib::NodeJSClientRequest
|
||||
or
|
||||
invk = DataFlow::moduleMember("https", "Agent").getAnInstantiation()
|
||||
or
|
||||
exists(DataFlow::NewNode new |
|
||||
new = DataFlow::moduleMember("tls", "TLSSocket").getAnInstantiation()
|
||||
|
|
||||
invk = new or
|
||||
invk = new.getAMethodCall("renegotiate")
|
||||
)
|
||||
or
|
||||
invk = DataFlow::moduleMember("tls", ["connect", "createServer"]).getACall()
|
||||
)
|
||||
}
|
||||
|
||||
from DataFlow::PropWrite disable
|
||||
where
|
||||
exists(DataFlow::SourceNode env |
|
||||
env = NodeJSLib::process().getAPropertyRead("env") and
|
||||
disable = env.getAPropertyWrite("NODE_TLS_REJECT_UNAUTHORIZED") and
|
||||
disable.getRhs().mayHaveStringValue("0")
|
||||
)
|
||||
or
|
||||
disable = tlsOptions().getAPropertyWrite("rejectUnauthorized") and
|
||||
disable.getRhs().(AnalyzedNode).getTheBooleanValue() = false
|
||||
select disable, "Disabling certificate validation is strongly discouraged."
|
||||
@@ -0,0 +1,14 @@
|
||||
let https = require("https");
|
||||
|
||||
https.request(
|
||||
{
|
||||
hostname: "secure.my-online-bank.com",
|
||||
port: 443,
|
||||
method: "POST",
|
||||
path: "send-confidential-information",
|
||||
rejectUnauthorized: false // BAD
|
||||
},
|
||||
response => {
|
||||
// ... communicate with secure.my-online-bank.com
|
||||
}
|
||||
);
|
||||
36
javascript/ql/src/Security/CWE-312/BuildArtifactLeak.qhelp
Normal file
36
javascript/ql/src/Security/CWE-312/BuildArtifactLeak.qhelp
Normal file
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Sensitive information included in a build artifact can allow an attacker to access
|
||||
the sensitive information if the artifact is published.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Only store information that is meant to be publicly available in a build artifact.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The following example creates a <code>webpack</code> configuration that inserts all environment
|
||||
variables from the host into the build artifact:
|
||||
</p>
|
||||
<sample src="examples/build-leak.js"/>
|
||||
<p>
|
||||
The environment variables might include API keys or other sensitive information, and the build-system
|
||||
should instead insert only the environment variables that are supposed to be public.
|
||||
</p>
|
||||
<p>
|
||||
The issue has been fixed below, where only the <code>DEBUG</code> environment variable is inserted into the artifact.
|
||||
</p>
|
||||
<sample src="examples/build-leak-fixed.js"/>
|
||||
</example>
|
||||
<references>
|
||||
<li>webpack: <a href="https://webpack.js.org/plugins/define-plugin/">DefinePlugin API</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
23
javascript/ql/src/Security/CWE-312/BuildArtifactLeak.ql
Normal file
23
javascript/ql/src/Security/CWE-312/BuildArtifactLeak.ql
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @name Storage of sensitive information in build artifact
|
||||
* @description Including sensitive information in a build artifact can
|
||||
* expose it to an attacker.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id js/build-artifact-leak
|
||||
* @tags security
|
||||
* external/cwe/cwe-312
|
||||
* external/cwe/cwe-315
|
||||
* external/cwe/cwe-359
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.BuildArtifactLeak::BuildArtifactLeak
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"Sensitive data returned by $@ is stored in a build artifact here.", source.getNode(),
|
||||
source.getNode().(CleartextLogging::Source).describe()
|
||||
@@ -0,0 +1,9 @@
|
||||
const webpack = require("webpack");
|
||||
|
||||
module.exports = [{
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': JSON.stringify({ DEBUG: process.env.DEBUG })
|
||||
})
|
||||
]
|
||||
}];
|
||||
@@ -0,0 +1,9 @@
|
||||
const webpack = require("webpack");
|
||||
|
||||
module.exports = [{
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
"process.env": JSON.stringify(process.env)
|
||||
})
|
||||
]
|
||||
}];
|
||||
65
javascript/ql/src/Security/CWE-327/BadRandomness.qhelp
Normal file
65
javascript/ql/src/Security/CWE-327/BadRandomness.qhelp
Normal file
@@ -0,0 +1,65 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Generating secure random numbers can be an important part of creating a
|
||||
secure software system. This can be done using APIs that create
|
||||
cryptographically secure random numbers.
|
||||
</p>
|
||||
<p>
|
||||
However, using some mathematical operations on these cryptographically
|
||||
secure random numbers can create biased results, where some outcomes
|
||||
are more likely than others.
|
||||
Such biased results can make it easier for an attacker to guess the random
|
||||
numbers, and thereby break the security of the software system.
|
||||
</p>
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>
|
||||
Be very careful not to introduce bias when performing mathematical operations
|
||||
on cryptographically secure random numbers.
|
||||
</p>
|
||||
<p>
|
||||
If possible, avoid performing mathematical operations on cryptographically secure
|
||||
random numbers at all, and use a preexisting library instead.
|
||||
</p>
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>
|
||||
The example below uses the modulo operator to create an array of 10 random digits
|
||||
using random bytes as the source for randomness.
|
||||
</p>
|
||||
<sample src="examples/bad-random.js" />
|
||||
<p>
|
||||
The random byte is a uniformly random value between 0 and 255, and thus the result
|
||||
from using the modulo operator is slightly more likely to be between 0 and 5 than
|
||||
between 6 and 9.
|
||||
</p>
|
||||
<p>
|
||||
The issue has been fixed in the code below by using a library that correctly generates
|
||||
cryptographically secure random values.
|
||||
</p>
|
||||
<sample src="examples/bad-random-fixed.js" />
|
||||
<p>
|
||||
Alternatively, the issue can be fixed by fixing the math in the original code.
|
||||
In the code below the random byte is discarded if the value is greater than or equal to 250.
|
||||
Thus the modulo operator is used on a uniformly random number between 0 and 249, which
|
||||
results in a uniformly random digit between 0 and 9.
|
||||
</p>
|
||||
<sample src="examples/bad-random-fixed2.js" />
|
||||
|
||||
</example>
|
||||
|
||||
|
||||
<references>
|
||||
<li>Stack Overflow: <a href="https://stackoverflow.com/questions/3956478/understanding-randomness">Understanding “randomness”</a>.</li>
|
||||
<li>OWASP: <a href="https://owasp.org/www-community/vulnerabilities/Insecure_Randomness">Insecure Randomness</a>.</li>
|
||||
<li>OWASP: <a
|
||||
href="https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html#rule---use-strong-approved-authenticated-encryption">Rule
|
||||
- Use strong approved cryptographic algorithms</a>.
|
||||
</li>
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
204
javascript/ql/src/Security/CWE-327/BadRandomness.ql
Normal file
204
javascript/ql/src/Security/CWE-327/BadRandomness.ql
Normal 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"
|
||||
@@ -0,0 +1,3 @@
|
||||
const cryptoRandomString = require('crypto-random-string');
|
||||
|
||||
const digits = cryptoRandomString({length: 10, type: 'numeric'});
|
||||
@@ -0,0 +1,10 @@
|
||||
const crypto = require('crypto');
|
||||
|
||||
const digits = [];
|
||||
while (digits.length < 10) {
|
||||
const byte = crypto.randomBytes(1)[0];
|
||||
if (byte >= 250) {
|
||||
continue;
|
||||
}
|
||||
digits.push(byte % 10); // OK
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
const crypto = require('crypto');
|
||||
|
||||
const digits = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
digits.push(crypto.randomBytes(1)[0] % 10); // NOT OK
|
||||
}
|
||||
38
javascript/ql/src/Security/CWE-829/InsecureDownload.qhelp
Normal file
38
javascript/ql/src/Security/CWE-829/InsecureDownload.qhelp
Normal file
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Downloading executeables or other sensitive files over an unencrypted connection
|
||||
can leave a server open to man-in-the-middle attacks (MITM).
|
||||
Such an attack can allow an attacker to insert arbitrary content
|
||||
into the downloaded file, and in the worst case, allow the attacker to execute
|
||||
arbitrary code on the vulnerable system.
|
||||
</p>
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>
|
||||
Use a secure transfer protocol when downloading executables or other sensitive files.
|
||||
</p>
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>
|
||||
In this example, a server downloads a shell script from a remote URL using the <code>node-fetch</code>
|
||||
library, and then executes this shell script.
|
||||
</p>
|
||||
<sample src="examples/insecure-download.js" />
|
||||
<p>
|
||||
The HTTP protocol is vulnerable to MITM, and thus an attacker could potentially replace the downloaded
|
||||
shell script with arbitrary code, which gives the attacker complete control over the system.
|
||||
</p>
|
||||
<p>
|
||||
The issue has been fixed in the example below by replacing the HTTP protocol with the HTTPS protocol.
|
||||
</p>
|
||||
<sample src="examples/insecure-download.js" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>OWASP: <a href="https://owasp.org/www-community/attacks/Man-in-the-middle_attack">Man-in-the-middle attack</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
20
javascript/ql/src/Security/CWE-829/InsecureDownload.ql
Normal file
20
javascript/ql/src/Security/CWE-829/InsecureDownload.ql
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @name Download of sensitive file through insecure connection
|
||||
* @description Downloading executables and other sensitive files over an insecure connection
|
||||
* opens up for potential man-in-the-middle attacks.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id js/insecure-download
|
||||
* @tags security
|
||||
* external/cwe/cwe-829
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.InsecureDownload::InsecureDownload
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "$@ of sensitive file from $@.",
|
||||
sink.getNode().(Sink).getDownloadCall(), "Download", source.getNode(), "HTTP source"
|
||||
@@ -0,0 +1,6 @@
|
||||
const fetch = require("node-fetch");
|
||||
const cp = require("child_process");
|
||||
|
||||
fetch('http://mydownload.example.org/myscript.sh')
|
||||
.then(res => res.text())
|
||||
.then(script => cp.execSync(script));
|
||||
@@ -0,0 +1,6 @@
|
||||
const fetch = require("node-fetch");
|
||||
const cp = require("child_process");
|
||||
|
||||
fetch('https://mydownload.example.org/myscript.sh')
|
||||
.then(res => res.text())
|
||||
.then(script => cp.execSync(script));
|
||||
@@ -41,6 +41,11 @@ module ArrayTaintTracking {
|
||||
succ = call
|
||||
)
|
||||
or
|
||||
// `array.reduce` with tainted value in callback
|
||||
call.(DataFlow::MethodCallNode).getMethodName() = "reduce" and
|
||||
pred = call.getArgument(0).(DataFlow::FunctionNode).getAReturn() and // Require the argument to be a closure to avoid spurious call/return flow
|
||||
succ = call
|
||||
or
|
||||
// `array.push(e)`, `array.unshift(e)`: if `e` is tainted, then so is `array`.
|
||||
exists(string name |
|
||||
name = "push" or
|
||||
|
||||
@@ -291,11 +291,27 @@ module DOM {
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node { }
|
||||
|
||||
private string getADomPropertyName() {
|
||||
exists(ExternalInstanceMemberDecl decl |
|
||||
result = decl.getName() and
|
||||
isDomRootType(decl.getDeclaringType().getASupertype*())
|
||||
)
|
||||
}
|
||||
|
||||
private class DefaultRange extends Range {
|
||||
DefaultRange() {
|
||||
this.asExpr().(VarAccess).getVariable() instanceof DOMGlobalVariable
|
||||
or
|
||||
this = domValueRef().getAPropertyRead()
|
||||
exists(DataFlow::PropRead read |
|
||||
this = read and
|
||||
read = domValueRef().getAPropertyRead()
|
||||
|
|
||||
not read.mayHavePropertyName(_)
|
||||
or
|
||||
read.mayHavePropertyName(getADomPropertyName())
|
||||
or
|
||||
read.mayHavePropertyName(any(string s | exists(s.toInt())))
|
||||
)
|
||||
or
|
||||
this = domElementCreationOrQuery()
|
||||
or
|
||||
|
||||
@@ -82,20 +82,20 @@ File tryExtensions(Folder dir, string basename, int priority) {
|
||||
* Gets the main module described by `pkg` with the given `priority`.
|
||||
*/
|
||||
File resolveMainModule(PackageJSON pkg, int priority) {
|
||||
if exists(MainModulePath::of(pkg))
|
||||
then
|
||||
exists(PathExpr main | main = MainModulePath::of(pkg) |
|
||||
result = main.resolve() and priority = 0
|
||||
or
|
||||
result = tryExtensions(main.resolve(), "index", priority)
|
||||
or
|
||||
not exists(main.resolve()) and
|
||||
not exists(main.getExtension()) and
|
||||
exists(int n | n = main.getNumComponent() |
|
||||
result = tryExtensions(main.resolveUpTo(n - 1), main.getComponent(n - 1), priority)
|
||||
)
|
||||
exists(PathExpr main | main = MainModulePath::of(pkg) |
|
||||
result = main.resolve() and priority = 0
|
||||
or
|
||||
result = tryExtensions(main.resolve(), "index", priority)
|
||||
or
|
||||
not exists(main.resolve()) and
|
||||
not exists(main.getExtension()) and
|
||||
exists(int n | n = main.getNumComponent() |
|
||||
result = tryExtensions(main.resolveUpTo(n - 1), main.getComponent(n - 1), priority)
|
||||
)
|
||||
else result = tryExtensions(pkg.getFile().getParentContainer(), "index", priority)
|
||||
)
|
||||
or
|
||||
result =
|
||||
tryExtensions(pkg.getFile().getParentContainer(), "index", priority - prioritiesPerCandidate())
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -68,4 +68,9 @@ private DataFlow::Node getAnExportFromModule(Module mod) {
|
||||
result.analyze().getAValue() = mod.(NodeModule).getAModuleExportsValue()
|
||||
or
|
||||
exists(ASTNode export | result.getEnclosingExpr() = export | mod.exports(_, export))
|
||||
or
|
||||
exists(ExportDeclaration export |
|
||||
result = export.getSourceNode(_) and
|
||||
mod = export.getTopLevel()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -576,6 +576,22 @@ module Bluebird {
|
||||
|
||||
override DataFlow::Node getArrayNode() { result = getArgument(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* An async function created using a call to `bluebird.coroutine`.
|
||||
*/
|
||||
class BluebirdCoroutineDefinition extends DataFlow::CallNode {
|
||||
BluebirdCoroutineDefinition() { this = bluebird().getAMemberCall("coroutine") }
|
||||
}
|
||||
|
||||
private class BluebirdCoroutineDefinitionAsPartialInvoke extends DataFlow::PartialInvokeNode::Range,
|
||||
BluebirdCoroutineDefinition {
|
||||
override DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) {
|
||||
boundArgs = 0 and
|
||||
callback = getArgument(0) and
|
||||
result = this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -720,14 +720,8 @@ module StringOps {
|
||||
override DataFlow::Node getStringOperand() { result = getArgument(0) }
|
||||
}
|
||||
|
||||
private class MatchesCall extends Range, DataFlow::MethodCallNode {
|
||||
MatchesCall() { getMethodName() = "matches" }
|
||||
|
||||
override DataFlow::Node getRegExpOperand(boolean coerced) {
|
||||
result = getArgument(0) and coerced = true
|
||||
}
|
||||
|
||||
override DataFlow::Node getStringOperand() { result = getReceiver() }
|
||||
private class MatchCall extends DataFlow::MethodCallNode {
|
||||
MatchCall() { getMethodName() = "match" }
|
||||
}
|
||||
|
||||
private class ExecCall extends DataFlow::MethodCallNode {
|
||||
@@ -777,5 +771,22 @@ module StringOps {
|
||||
|
||||
override boolean getPolarity() { result = polarity }
|
||||
}
|
||||
|
||||
private class MatchTest extends Range, DataFlow::ValueNode {
|
||||
MatchCall match;
|
||||
boolean polarity;
|
||||
|
||||
MatchTest() {
|
||||
exists(Expr use | match.flowsToExpr(use) | impliesNotNull(astNode, use, polarity))
|
||||
}
|
||||
|
||||
override DataFlow::Node getRegExpOperand(boolean coerced) {
|
||||
result = match.getArgument(0) and coerced = true
|
||||
}
|
||||
|
||||
override DataFlow::Node getStringOperand() { result = match.getReceiver() }
|
||||
|
||||
override boolean getPolarity() { result = polarity }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,6 +267,12 @@ module TaintTracking {
|
||||
pred = DataFlow::valueNode(fos.getIterationDomain()) and
|
||||
succ = DataFlow::lvalueNode(fos.getLValue())
|
||||
)
|
||||
or
|
||||
// taint-tracking rest patterns in l-values. E.g. `const {...spread} = foo()` or `const [...spread] = foo()`.
|
||||
exists(DestructuringPattern pattern |
|
||||
pred = DataFlow::lvalueNode(pattern) and
|
||||
succ = DataFlow::lvalueNode(pattern.getRest())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -620,4 +620,45 @@ module ClientRequest {
|
||||
|
||||
override DataFlow::Node getADataNode() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `nugget` that downloads one of more files to a destination determined by an options object given as the second argument.
|
||||
*/
|
||||
class Nugget extends ClientRequest::Range, DataFlow::CallNode {
|
||||
Nugget() { this = DataFlow::moduleImport("nugget").getACall() }
|
||||
|
||||
override DataFlow::Node getUrl() { result = getArgument(0) }
|
||||
|
||||
override DataFlow::Node getHost() { none() }
|
||||
|
||||
override DataFlow::Node getADataNode() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A shell execution of `curl` that downloads some file.
|
||||
*/
|
||||
class CurlDownload extends ClientRequest::Range {
|
||||
SystemCommandExecution cmd;
|
||||
|
||||
CurlDownload() {
|
||||
this = cmd and
|
||||
(
|
||||
cmd.getACommandArgument().getStringValue() = "curl" or
|
||||
cmd
|
||||
.getACommandArgument()
|
||||
.(StringOps::ConcatenationRoot)
|
||||
.getConstantStringParts()
|
||||
.regexpMatch("curl .*")
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getUrl() {
|
||||
result = cmd.getArgumentList().getALocalSource().getAPropertyWrite().getRhs() or
|
||||
result = cmd.getACommandArgument().(StringOps::ConcatenationRoot).getALeaf()
|
||||
}
|
||||
|
||||
override DataFlow::Node getHost() { none() }
|
||||
|
||||
override DataFlow::Node getADataNode() { none() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
114
javascript/ql/src/semmle/javascript/frameworks/Micro.qll
Normal file
114
javascript/ql/src/semmle/javascript/frameworks/Micro.qll
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Provides a model of the `micro` NPM package.
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
|
||||
private module Micro {
|
||||
private import DataFlow
|
||||
|
||||
/**
|
||||
* A node that should be interpreted as a route handler, to use as starting
|
||||
* point for back-tracking.
|
||||
*/
|
||||
Node microRouteHandlerSink() {
|
||||
result = moduleMember("micro", "run").getACall().getLastArgument()
|
||||
or
|
||||
result = moduleImport("micro").getACall().getArgument(0)
|
||||
}
|
||||
|
||||
/** Gets a data flow node interpreted as a route handler. */
|
||||
private DataFlow::SourceNode microRouteHandler(DataFlow::TypeBackTracker t) {
|
||||
t.start() and
|
||||
result = microRouteHandlerSink().getALocalSource()
|
||||
or
|
||||
exists(DataFlow::TypeBackTracker t2 | result = microRouteHandler(t2).backtrack(t2, t))
|
||||
or
|
||||
exists(DataFlow::CallNode transformer |
|
||||
transformer = moduleImport("micro-compress").getACall()
|
||||
or
|
||||
transformer instanceof Bluebird::BluebirdCoroutineDefinition
|
||||
|
|
||||
microRouteHandler(t.continue()) = transformer and
|
||||
result = transformer.getArgument(0).getALocalSource()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a data flow node interpreted as a route handler. */
|
||||
DataFlow::SourceNode microRouteHandler() {
|
||||
result = microRouteHandler(DataFlow::TypeBackTracker::end())
|
||||
}
|
||||
|
||||
/**
|
||||
* A function passed to `micro` or `micro.run`.
|
||||
*/
|
||||
class MicroRouteHandler extends HTTP::Servers::StandardRouteHandler, DataFlow::FunctionNode {
|
||||
MicroRouteHandler() { this = microRouteHandler().getAFunctionValue() }
|
||||
}
|
||||
|
||||
class MicroRequestSource extends HTTP::Servers::RequestSource {
|
||||
MicroRouteHandler h;
|
||||
|
||||
MicroRequestSource() { this = h.getParameter(0) }
|
||||
|
||||
override HTTP::RouteHandler getRouteHandler() { result = h }
|
||||
}
|
||||
|
||||
class MicroResponseSource extends HTTP::Servers::ResponseSource {
|
||||
MicroRouteHandler h;
|
||||
|
||||
MicroResponseSource() { this = h.getParameter(1) }
|
||||
|
||||
override HTTP::RouteHandler getRouteHandler() { result = h }
|
||||
}
|
||||
|
||||
class MicroRequestExpr extends NodeJSLib::RequestExpr {
|
||||
override MicroRequestSource src;
|
||||
}
|
||||
|
||||
class MicroReseponseExpr extends NodeJSLib::ResponseExpr {
|
||||
override MicroResponseSource src;
|
||||
}
|
||||
|
||||
private HTTP::RouteHandler getRouteHandlerFromReqRes(DataFlow::Node node) {
|
||||
exists(HTTP::Servers::RequestSource src |
|
||||
src.ref().flowsTo(node) and
|
||||
result = src.getRouteHandler()
|
||||
)
|
||||
or
|
||||
exists(HTTP::Servers::ResponseSource src |
|
||||
src.ref().flowsTo(node) and
|
||||
result = src.getRouteHandler()
|
||||
)
|
||||
}
|
||||
|
||||
class MicroBodyParserCall extends HTTP::RequestInputAccess, DataFlow::CallNode {
|
||||
string name;
|
||||
|
||||
MicroBodyParserCall() {
|
||||
name = ["buffer", "text", "json"] and
|
||||
this = moduleMember("micro", name).getACall()
|
||||
}
|
||||
|
||||
override string getKind() { result = "body" }
|
||||
|
||||
override HTTP::RouteHandler getRouteHandler() {
|
||||
result = getRouteHandlerFromReqRes(getArgument(0))
|
||||
}
|
||||
|
||||
override predicate isUserControlledObject() { name = "json" }
|
||||
}
|
||||
|
||||
class MicroSendArgument extends HTTP::ResponseSendArgument {
|
||||
CallNode send;
|
||||
|
||||
MicroSendArgument() {
|
||||
send = moduleMember("micro", ["send", "sendError"]).getACall() and
|
||||
this = send.getLastArgument().asExpr()
|
||||
}
|
||||
|
||||
override HTTP::RouteHandler getRouteHandler() {
|
||||
result = getRouteHandlerFromReqRes(send.getArgument([0, 1]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,32 +17,12 @@ private import semmle.javascript.security.dataflow.RegExpInjectionCustomizations
|
||||
private import semmle.javascript.security.dataflow.ClientSideUrlRedirectCustomizations
|
||||
private import semmle.javascript.security.dataflow.ServerSideUrlRedirectCustomizations
|
||||
private import semmle.javascript.security.dataflow.InsecureRandomnessCustomizations
|
||||
private import HeuristicSinks as Sinks
|
||||
|
||||
/**
|
||||
* A heuristic sink for data flow in a security query.
|
||||
*/
|
||||
abstract class HeuristicSink extends DataFlow::Node { }
|
||||
class HeuristicSink = Sinks::HeuristicSink;
|
||||
|
||||
private class HeuristicCodeInjectionSink extends HeuristicSink, CodeInjection::Sink {
|
||||
HeuristicCodeInjectionSink() {
|
||||
isAssignedTo(this, "$where")
|
||||
or
|
||||
isAssignedToOrConcatenatedWith(this, "(?i)(command|cmd|exec|code|script|program)")
|
||||
or
|
||||
isArgTo(this, "(?i)(eval|run)") // "exec" clashes too often with `RegExp.prototype.exec`
|
||||
or
|
||||
exists(string srcPattern |
|
||||
// function/lambda syntax anywhere
|
||||
srcPattern = "(?s).*function\\s*\\(.*\\).*" or
|
||||
srcPattern = "(?s).*(\\(.*\\)|[A-Za-z_]+)\\s?=>.*"
|
||||
|
|
||||
isConcatenatedWithString(this, srcPattern)
|
||||
)
|
||||
or
|
||||
// dynamic property name
|
||||
isConcatenatedWithStrings("(?is)[a-z]+\\[", this, "(?s)\\].*")
|
||||
}
|
||||
}
|
||||
private class HeuristicCodeInjectionSink extends Sinks::HeuristicCodeInjectionSink,
|
||||
CodeInjection::Sink { }
|
||||
|
||||
private class HeuristicCommandInjectionSink extends HeuristicSink, CommandInjection::Sink {
|
||||
HeuristicCommandInjectionSink() {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Provides heuristically recognized sinks for security queries.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
private import SyntacticHeuristics
|
||||
|
||||
/**
|
||||
* A heuristic sink for data flow in a security query.
|
||||
*/
|
||||
abstract class HeuristicSink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A heuristically recognized sink for `js/code-injection` vulnerabilities.
|
||||
*/
|
||||
class HeuristicCodeInjectionSink extends HeuristicSink {
|
||||
HeuristicCodeInjectionSink() {
|
||||
isAssignedTo(this, "$where")
|
||||
or
|
||||
isAssignedToOrConcatenatedWith(this, "(?i)(command|cmd|exec|code|script|program)")
|
||||
or
|
||||
isArgTo(this, "(?i)(eval|run)") // "exec" clashes too often with `RegExp.prototype.exec`
|
||||
or
|
||||
exists(string srcPattern |
|
||||
// function/lambda syntax anywhere
|
||||
srcPattern = "(?s).*function\\s*\\(.*\\).*" or
|
||||
srcPattern = "(?s).*(\\(.*\\)|[A-Za-z_]+)\\s?=>.*"
|
||||
|
|
||||
isConcatenatedWithString(this, srcPattern)
|
||||
)
|
||||
or
|
||||
// dynamic property name
|
||||
isConcatenatedWithStrings("(?is)[a-z]+\\[", this, "(?s)\\].*")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Provides a dataflow tracking configuration for reasoning about
|
||||
* storage of sensitive information in build artifact.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `CleartextLogging::Configuration` is needed, otherwise
|
||||
* `CleartextLoggingCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Classes and predicates for storage of sensitive information in build artifact query.
|
||||
*/
|
||||
module BuildArtifactLeak {
|
||||
import BuildArtifactLeakCustomizations::BuildArtifactLeak
|
||||
import CleartextLoggingCustomizations::CleartextLogging as CleartextLogging
|
||||
|
||||
/**
|
||||
* A taint tracking configuration for storage of sensitive information in build artifact.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "BuildArtifactLeak" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel lbl) {
|
||||
source.(CleartextLogging::Source).getLabel() = lbl
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel lbl) {
|
||||
sink.(Sink).getLabel() = lbl
|
||||
}
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
node instanceof CleartextLogging::Barrier
|
||||
}
|
||||
|
||||
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
CleartextLogging::isSanitizerEdge(pred, succ)
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node trg) {
|
||||
CleartextLogging::isAdditionalTaintStep(src, trg)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Provides default sinks for reasoning about storage of sensitive information
|
||||
* in build artifact, as well as extension points for adding your own.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
private import semmle.javascript.dataflow.InferredTypes
|
||||
private import semmle.javascript.security.SensitiveActions::HeuristicNames
|
||||
|
||||
/**
|
||||
* Sinks for storage of sensitive information in build artifact.
|
||||
*/
|
||||
module BuildArtifactLeak {
|
||||
/**
|
||||
* A data flow sink for storage of sensitive information in a build artifact.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node {
|
||||
/**
|
||||
* Gets a data-flow label that leaks information for this sink.
|
||||
*/
|
||||
DataFlow::FlowLabel getLabel() { result.isTaint() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An instantiation of `webpack.DefintePlugin` that stores information in a compiled JavaScript file.
|
||||
*/
|
||||
class WebpackDefinePluginSink extends Sink {
|
||||
WebpackDefinePluginSink() {
|
||||
this = DataFlow::moduleMember("webpack", "DefinePlugin").getAnInstantiation().getAnArgument()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,23 +35,11 @@ module CleartextLogging {
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Barrier }
|
||||
|
||||
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
succ.(DataFlow::PropRead).getBase() = pred
|
||||
CleartextLogging::isSanitizerEdge(pred, succ)
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node trg) {
|
||||
// A taint propagating data flow edge through objects: a tainted write taints the entire object.
|
||||
exists(DataFlow::PropWrite write |
|
||||
write.getRhs() = src and
|
||||
trg.(DataFlow::SourceNode).flowsTo(write.getBase())
|
||||
)
|
||||
or
|
||||
// Taint through the arguments object.
|
||||
exists(DataFlow::CallNode call, Function f |
|
||||
src = call.getAnArgument() and
|
||||
f = call.getACallee() and
|
||||
not call.isImprecise() and
|
||||
trg.asExpr() = f.getArgumentsVariable().getAnAccess()
|
||||
)
|
||||
CleartextLogging::isAdditionalTaintStep(src, trg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,17 +176,50 @@ module CleartextLogging {
|
||||
|
||||
override string describe() { result = "process environment" }
|
||||
|
||||
override DataFlow::FlowLabel getLabel() {
|
||||
result.isTaint() or
|
||||
result instanceof PartiallySensitiveMap
|
||||
}
|
||||
override DataFlow::FlowLabel getLabel() { result.isTaint() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow label describing a map that might contain sensitive information in some properties.
|
||||
* Property reads on such maps where the property name is fixed is unlikely to leak sensitive information.
|
||||
* Holds if the edge `pred` -> `succ` should be sanitized for clear-text logging of sensitive information.
|
||||
*/
|
||||
class PartiallySensitiveMap extends DataFlow::FlowLabel {
|
||||
PartiallySensitiveMap() { this = "PartiallySensitiveMap" }
|
||||
predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
succ.(DataFlow::PropRead).getBase() = pred
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the edge `src` -> `trg` is an additional taint-step for clear-text logging of sensitive information.
|
||||
*/
|
||||
predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node trg) {
|
||||
// A taint propagating data flow edge through objects: a tainted write taints the entire object.
|
||||
exists(DataFlow::PropWrite write |
|
||||
write.getRhs() = src and
|
||||
trg.(DataFlow::SourceNode).flowsTo(write.getBase())
|
||||
)
|
||||
or
|
||||
// A property-copy step,
|
||||
// dst[x] = src[x]
|
||||
// dst[x] = JSON.stringify(src[x])
|
||||
exists(DataFlow::PropWrite write, DataFlow::PropRead read |
|
||||
read = write.getRhs()
|
||||
or
|
||||
exists(DataFlow::MethodCallNode stringify |
|
||||
stringify = write.getRhs() and
|
||||
stringify = DataFlow::globalVarRef("JSON").getAMethodCall("stringify") and
|
||||
stringify.getArgument(0) = read
|
||||
)
|
||||
|
|
||||
not exists(write.getPropertyName()) and
|
||||
not exists(read.getPropertyName()) and
|
||||
src = read.getBase() and
|
||||
trg = write.getBase().getALocalSource()
|
||||
)
|
||||
or
|
||||
// Taint through the arguments object.
|
||||
exists(DataFlow::CallNode call, Function f |
|
||||
src = call.getAnArgument() and
|
||||
f = call.getACallee() and
|
||||
not call.isImprecise() and
|
||||
trg.asExpr() = f.getArgumentsVariable().getAnAccess()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for reasoning about improper code
|
||||
* sanitization.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `ImproperCodeSanitization::Configuration` is needed, otherwise
|
||||
* `ImproperCodeSanitizationCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Classes and predicates for reasoning about improper code sanitization.
|
||||
*/
|
||||
module ImproperCodeSanitization {
|
||||
import ImproperCodeSanitizationCustomizations::ImproperCodeSanitization
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for reasoning about improper code sanitization vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "ImproperCodeSanitization" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node sanitizer) { sanitizer instanceof Sanitizer }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for reasoning about
|
||||
* improper code sanitization, as well as extension points for
|
||||
* adding your own.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Classes and predicates for reasoning about improper code sanitization.
|
||||
*/
|
||||
module ImproperCodeSanitization {
|
||||
/**
|
||||
* A data flow source for improper code sanitization.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A data flow sink for improper code sanitization.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer for improper code sanitization.
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A call to a HTML sanitizer seen as a source for improper code sanitization
|
||||
*/
|
||||
class HtmlSanitizerCallAsSource extends Source {
|
||||
HtmlSanitizerCallAsSource() { this instanceof HtmlSanitizerCall }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `JSON.stringify()` seen as a source for improper code sanitization
|
||||
*/
|
||||
class JSONStringifyAsSource extends Source {
|
||||
JSONStringifyAsSource() { this = DataFlow::globalVarRef("JSON").getAMemberCall("stringify") }
|
||||
}
|
||||
|
||||
/**
|
||||
* A leaf in a string-concatenation, where the string-concatenation constructs code that looks like a function.
|
||||
*/
|
||||
class FunctionStringConstruction extends Sink, StringOps::ConcatenationLeaf {
|
||||
FunctionStringConstruction() {
|
||||
exists(StringOps::ConcatenationRoot root, int i |
|
||||
root.getOperand(i) = this and
|
||||
not exists(this.getStringValue())
|
||||
|
|
||||
exists(StringOps::ConcatenationLeaf functionLeaf |
|
||||
functionLeaf = root.getOperand(any(int j | j < i))
|
||||
|
|
||||
functionLeaf
|
||||
.getStringValue()
|
||||
.regexpMatch([".*function( )?([a-zA-Z0-9]+)?( )?\\(.*", ".*eval\\(.*",
|
||||
".*new Function\\(.*", "(^|.*[^a-zA-Z0-9])\\(.*\\)( )?=>.*"])
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `String.prototype.replace` seen as a sanitizer for improper code sanitization.
|
||||
* All calls to replace that happens after the initial improper sanitization is seen as a sanitizer.
|
||||
*/
|
||||
class StringReplaceCallAsSanitizer extends Sanitizer, StringReplaceCall { }
|
||||
}
|
||||
@@ -50,14 +50,42 @@ module IndirectCommandInjection {
|
||||
// `require('minimist')(...)` => `{ _: [], a: ... b: ... }`
|
||||
this = DataFlow::moduleImport("minimist").getACall()
|
||||
or
|
||||
// `require('yargs').argv` => `{ _: [], a: ... b: ... }`
|
||||
this = DataFlow::moduleMember("yargs", "argv")
|
||||
or
|
||||
// `require('optimist').argv` => `{ _: [], a: ... b: ... }`
|
||||
this = DataFlow::moduleMember("optimist", "argv")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an instance of `yargs`.
|
||||
* Either directly imported as a module, or through some chained method call.
|
||||
*/
|
||||
private DataFlow::SourceNode yargs() {
|
||||
result = DataFlow::moduleImport("yargs")
|
||||
or
|
||||
// script used to generate list of chained methods: https://gist.github.com/erik-krogh/f8afe952c0577f4b563a993e613269ba
|
||||
exists(string method |
|
||||
not method =
|
||||
// the methods that does not return a chained `yargs` object.
|
||||
["getContext", "getDemandedOptions", "getDemandedCommands", "getDeprecatedOptions",
|
||||
"_getParseContext", "getOptions", "getGroups", "getStrict", "getStrictCommands",
|
||||
"getExitProcess", "locale", "getUsageInstance", "getCommandInstance"]
|
||||
|
|
||||
result = yargs().getAMethodCall(method)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of command line arguments (`argv`) parsed by the `yargs` libary.
|
||||
*/
|
||||
class YargsArgv extends Source {
|
||||
YargsArgv() {
|
||||
this = yargs().getAPropertyRead("argv")
|
||||
or
|
||||
this = yargs().getAMethodCall("parse") and
|
||||
this.(DataFlow::MethodCallNode).getNumArgument() = 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A command-line argument that effectively is system-controlled, and therefore not likely to be exploitable when used in the execution of another command.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Provides a taint tracking configuration for reasoning about download of sensitive file through insecure connection.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `InsecureDownload::Configuration` is needed, otherwise
|
||||
* `InsecureDownloadCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Classes and predicates for reasoning about download of sensitive file through insecure connection vulnerabilities.
|
||||
*/
|
||||
module InsecureDownload {
|
||||
import InsecureDownloadCustomizations::InsecureDownload
|
||||
|
||||
/**
|
||||
* A taint tracking configuration for download of sensitive file through insecure connection.
|
||||
*/
|
||||
class Configuration extends DataFlow::Configuration {
|
||||
Configuration() { this = "InsecureDownload" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for reasoning about
|
||||
* download of sensitive file through insecure connection, as well as
|
||||
* extension points for adding your own.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Classes and predicates for reasoning about download of sensitive file through insecure connection vulnerabilities.
|
||||
*/
|
||||
module InsecureDownload {
|
||||
/**
|
||||
* A data flow source for download of sensitive file through insecure connection.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A data flow sink for download of sensitive file through insecure connection.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node {
|
||||
/**
|
||||
* Gets the call that downloads the sensitive file.
|
||||
*/
|
||||
abstract DataFlow::Node getDownloadCall();
|
||||
}
|
||||
|
||||
/**
|
||||
* A sanitizer for download of sensitive file through insecure connection.
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A HTTP or FTP URL that refers to a file with a sensitive file extension,
|
||||
* seen as a source for download of sensitive file through insecure connection.
|
||||
*/
|
||||
class SensitiveFileUrl extends Source {
|
||||
SensitiveFileUrl() {
|
||||
exists(string str | str = this.getStringValue() |
|
||||
str.regexpMatch("http://.*|ftp://.*") and
|
||||
exists(string suffix | suffix = unsafeExtension() |
|
||||
str.suffix(str.length() - suffix.length() - 1).toLowerCase() = "." + suffix
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a file-extension that can potentially be dangerous.
|
||||
*
|
||||
* Archives are included, because they often contain source-code.
|
||||
*/
|
||||
string unsafeExtension() {
|
||||
result =
|
||||
["exe", "dmg", "pkg", "tar.gz", "zip", "sh", "bat", "cmd", "app", "apk", "msi", "dmg",
|
||||
"tar.gz", "zip", "js", "py", "jar", "war"]
|
||||
}
|
||||
|
||||
/**
|
||||
* A url downloaded by a client-request, seen as a sink for download of
|
||||
* sensitive file through insecure connection.a
|
||||
*/
|
||||
class ClientRequestURL extends Sink {
|
||||
ClientRequest request;
|
||||
|
||||
ClientRequestURL() { this = request.getUrl() }
|
||||
|
||||
override DataFlow::Node getDownloadCall() { result = request }
|
||||
}
|
||||
}
|
||||
@@ -36,16 +36,7 @@ module InsecureRandomness {
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
// Assume that all operations on tainted values preserve taint: crypto is hard
|
||||
succ.asExpr().(BinaryExpr).getAnOperand() = pred.asExpr()
|
||||
or
|
||||
succ.asExpr().(UnaryExpr).getOperand() = pred.asExpr()
|
||||
or
|
||||
exists(DataFlow::MethodCallNode mc |
|
||||
mc = DataFlow::globalVarRef("Math").getAMemberCall(_) and
|
||||
pred = mc.getAnArgument() and
|
||||
succ = mc
|
||||
)
|
||||
InsecureRandomness::isAdditionalTaintStep(pred, succ)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,11 +212,10 @@ module TaintedPath {
|
||||
DataFlow::Node output;
|
||||
|
||||
PreservingPathCall() {
|
||||
exists(string name | name = "dirname" or name = "toNamespacedPath" |
|
||||
this = NodeJSLib::Path::moduleMember(name).getACall() and
|
||||
input = getAnArgument() and
|
||||
output = this
|
||||
)
|
||||
this =
|
||||
NodeJSLib::Path::moduleMember(["dirname", "toNamespacedPath", "parse", "format"]).getACall() and
|
||||
input = getAnArgument() and
|
||||
output = this
|
||||
or
|
||||
// non-global replace or replace of something other than /\.\./g, /[/]/g, or /[\.]/g.
|
||||
this.getCalleeName() = "replace" and
|
||||
|
||||
@@ -4,3 +4,5 @@ test_locationRef
|
||||
| customization.js:3:3:3:14 | doc.location |
|
||||
test_domValueRef
|
||||
| customization.js:4:3:4:28 | doc.get ... 'test') |
|
||||
| tst.js:49:3:49:8 | window |
|
||||
| tst.js:50:3:50:8 | window |
|
||||
|
||||
10
javascript/ql/test/library-tests/DOM/externs/externs.js
Normal file
10
javascript/ql/test/library-tests/DOM/externs/externs.js
Normal file
@@ -0,0 +1,10 @@
|
||||
/** @externs */
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @name EventTarget
|
||||
*/
|
||||
function EventTarget() {}
|
||||
|
||||
/** @type {EventTarget} */
|
||||
var window;
|
||||
@@ -39,3 +39,13 @@
|
||||
factory2();
|
||||
|
||||
})();
|
||||
|
||||
(function pollute() {
|
||||
class C {
|
||||
foo() {
|
||||
this.x; // Should not be a domValueRef
|
||||
}
|
||||
}
|
||||
window.myApp = new C();
|
||||
window.myApp.foo();
|
||||
})();
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
module.exports.foo = function() {};
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"main": "dist/does-not-exist.js"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export function exported() {}
|
||||
|
||||
function notExported() {}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"main": "main.js"
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
getTopmostPackageJSON
|
||||
| absent_main/package.json:1:1:3:1 | {\\n " ... t.js"\\n} |
|
||||
| esmodules/package.json:1:1:3:1 | {\\n " ... n.js"\\n} |
|
||||
| lib1/package.json:1:1:3:1 | {\\n " ... n.js"\\n} |
|
||||
getAValueExportedBy
|
||||
| absent_main/package.json:1:1:3:1 | {\\n " ... t.js"\\n} | absent_main/index.js:1:1:1:0 | this |
|
||||
| absent_main/package.json:1:1:3:1 | {\\n " ... t.js"\\n} | absent_main/index.js:1:1:1:14 | module.exports |
|
||||
| absent_main/package.json:1:1:3:1 | {\\n " ... t.js"\\n} | absent_main/index.js:1:1:1:18 | module.exports.foo |
|
||||
| absent_main/package.json:1:1:3:1 | {\\n " ... t.js"\\n} | absent_main/index.js:1:22:1:21 | this |
|
||||
| absent_main/package.json:1:1:3:1 | {\\n " ... t.js"\\n} | absent_main/index.js:1:22:1:34 | function() {} |
|
||||
| esmodules/package.json:1:1:3:1 | {\\n " ... n.js"\\n} | esmodules/main.js:1:8:1:29 | functio ... ed() {} |
|
||||
| lib1/package.json:1:1:3:1 | {\\n " ... n.js"\\n} | lib1/foo.js:1:1:1:0 | this |
|
||||
| lib1/package.json:1:1:3:1 | {\\n " ... n.js"\\n} | lib1/foo.js:1:1:1:53 | module. ... in() {} |
|
||||
| lib1/package.json:1:1:3:1 | {\\n " ... n.js"\\n} | lib1/foo.js:1:18:1:53 | functio ... in() {} |
|
||||
|
||||
@@ -2,12 +2,12 @@ regexpTest
|
||||
| tst.js:6:9:6:28 | /^[a-z]+$/.test(str) |
|
||||
| tst.js:7:9:7:36 | /^[a-z] ... != null |
|
||||
| tst.js:8:9:8:28 | /^[a-z]+$/.exec(str) |
|
||||
| tst.js:9:9:9:31 | str.mat ... -z]+$/) |
|
||||
| tst.js:10:9:10:31 | str.mat ... -z]+$") |
|
||||
| tst.js:9:9:9:29 | str.mat ... -z]+$/) |
|
||||
| tst.js:10:9:10:29 | str.mat ... -z]+$") |
|
||||
| tst.js:12:9:12:24 | regexp.test(str) |
|
||||
| tst.js:13:9:13:32 | regexp. ... != null |
|
||||
| tst.js:14:9:14:24 | regexp.exec(str) |
|
||||
| tst.js:15:9:15:27 | str.matches(regexp) |
|
||||
| tst.js:15:9:15:25 | str.match(regexp) |
|
||||
| tst.js:18:9:18:13 | match |
|
||||
| tst.js:19:10:19:14 | match |
|
||||
| tst.js:20:9:20:21 | match == null |
|
||||
@@ -15,18 +15,20 @@ regexpTest
|
||||
| tst.js:22:9:22:13 | match |
|
||||
| tst.js:25:23:25:27 | match |
|
||||
| tst.js:29:21:29:36 | regexp.test(str) |
|
||||
| tst.js:33:21:33:39 | str.matches(regexp) |
|
||||
| tst.js:33:23:33:39 | str.match(regexp) |
|
||||
| tst.js:40:9:40:37 | regexp. ... defined |
|
||||
| tst.js:44:9:44:14 | match2 |
|
||||
| tst.js:45:10:45:15 | match2 |
|
||||
#select
|
||||
| tst.js:6:9:6:28 | /^[a-z]+$/.test(str) | tst.js:6:10:6:17 | ^[a-z]+$ | tst.js:6:9:6:18 | /^[a-z]+$/ | tst.js:6:25:6:27 | str | true |
|
||||
| tst.js:7:9:7:36 | /^[a-z] ... != null | tst.js:7:10:7:17 | ^[a-z]+$ | tst.js:7:9:7:18 | /^[a-z]+$/ | tst.js:7:25:7:27 | str | true |
|
||||
| tst.js:8:9:8:28 | /^[a-z]+$/.exec(str) | tst.js:8:10:8:17 | ^[a-z]+$ | tst.js:8:9:8:18 | /^[a-z]+$/ | tst.js:8:25:8:27 | str | true |
|
||||
| tst.js:9:9:9:31 | str.mat ... -z]+$/) | tst.js:9:22:9:29 | ^[a-z]+$ | tst.js:9:21:9:30 | /^[a-z]+$/ | tst.js:9:9:9:11 | str | true |
|
||||
| tst.js:10:9:10:31 | str.mat ... -z]+$") | tst.js:10:22:10:29 | ^[a-z]+$ | tst.js:10:21:10:30 | "^[a-z]+$" | tst.js:10:9:10:11 | str | true |
|
||||
| tst.js:9:9:9:29 | str.mat ... -z]+$/) | tst.js:9:20:9:27 | ^[a-z]+$ | tst.js:9:19:9:28 | /^[a-z]+$/ | tst.js:9:9:9:11 | str | true |
|
||||
| tst.js:10:9:10:29 | str.mat ... -z]+$") | tst.js:10:20:10:27 | ^[a-z]+$ | tst.js:10:19:10:28 | "^[a-z]+$" | tst.js:10:9:10:11 | str | true |
|
||||
| tst.js:12:9:12:24 | regexp.test(str) | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:12:9:12:14 | regexp | tst.js:12:21:12:23 | str | true |
|
||||
| tst.js:13:9:13:32 | regexp. ... != null | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:13:9:13:14 | regexp | tst.js:13:21:13:23 | str | true |
|
||||
| tst.js:14:9:14:24 | regexp.exec(str) | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:14:9:14:14 | regexp | tst.js:14:21:14:23 | str | true |
|
||||
| tst.js:15:9:15:27 | str.matches(regexp) | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:15:21:15:26 | regexp | tst.js:15:9:15:11 | str | true |
|
||||
| tst.js:15:9:15:25 | str.match(regexp) | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:15:19:15:24 | regexp | tst.js:15:9:15:11 | str | true |
|
||||
| tst.js:18:9:18:13 | match | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:17:17:17:22 | regexp | tst.js:17:29:17:31 | str | true |
|
||||
| tst.js:19:10:19:14 | match | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:17:17:17:22 | regexp | tst.js:17:29:17:31 | str | true |
|
||||
| tst.js:20:9:20:21 | match == null | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:17:17:17:22 | regexp | tst.js:17:29:17:31 | str | false |
|
||||
@@ -34,5 +36,7 @@ regexpTest
|
||||
| tst.js:22:9:22:13 | match | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:17:17:17:22 | regexp | tst.js:17:29:17:31 | str | true |
|
||||
| tst.js:25:23:25:27 | match | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:17:17:17:22 | regexp | tst.js:17:29:17:31 | str | true |
|
||||
| tst.js:29:21:29:36 | regexp.test(str) | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:29:21:29:26 | regexp | tst.js:29:33:29:35 | str | true |
|
||||
| tst.js:33:21:33:39 | str.matches(regexp) | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:33:33:33:38 | regexp | tst.js:33:21:33:23 | str | true |
|
||||
| tst.js:33:23:33:39 | str.match(regexp) | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:33:33:33:38 | regexp | tst.js:33:23:33:25 | str | true |
|
||||
| tst.js:40:9:40:37 | regexp. ... defined | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:40:9:40:14 | regexp | tst.js:40:21:40:23 | str | false |
|
||||
| tst.js:44:9:44:14 | match2 | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:43:28:43:33 | regexp | tst.js:43:18:43:20 | str | true |
|
||||
| tst.js:45:10:45:15 | match2 | tst.js:3:17:3:24 | ^[a-z]+$ | tst.js:43:28:43:33 | regexp | tst.js:43:18:43:20 | str | true |
|
||||
|
||||
@@ -6,13 +6,13 @@ function f(str) {
|
||||
if (/^[a-z]+$/.test(str)) {}
|
||||
if (/^[a-z]+$/.exec(str) != null) {}
|
||||
if (/^[a-z]+$/.exec(str)) {}
|
||||
if (str.matches(/^[a-z]+$/)) {}
|
||||
if (str.matches("^[a-z]+$")) {}
|
||||
if (str.match(/^[a-z]+$/)) {}
|
||||
if (str.match("^[a-z]+$")) {}
|
||||
|
||||
if (regexp.test(str)) {}
|
||||
if (regexp.exec(str) != null) {}
|
||||
if (regexp.exec(str)) {}
|
||||
if (str.matches(regexp)) {}
|
||||
if (str.match(regexp)) {}
|
||||
|
||||
let match = regexp.exec(str);
|
||||
if (match) {}
|
||||
@@ -30,7 +30,7 @@ function f(str) {
|
||||
});
|
||||
|
||||
something({
|
||||
someOption: str.matches(regexp)
|
||||
someOption: !!str.match(regexp)
|
||||
});
|
||||
|
||||
something({
|
||||
@@ -39,4 +39,13 @@ function f(str) {
|
||||
|
||||
if (regexp.exec(str) == undefined) {}
|
||||
if (regexp.exec(str) === undefined) {} // not recognized as RegExpTest
|
||||
|
||||
let match2 = str.match(regexp);
|
||||
if (match2) {}
|
||||
if (!match2) {}
|
||||
}
|
||||
|
||||
function something() {}
|
||||
|
||||
f("some string");
|
||||
f("someotherstring");
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
routeHandler
|
||||
| tst.js:5:7:10:1 | async ( ... llo";\\n} |
|
||||
| tst.js:12:1:15:1 | functio ... nse";\\n} |
|
||||
requestSource
|
||||
| tst.js:5:14:5:16 | req |
|
||||
| tst.js:12:26:12:28 | req |
|
||||
responseSource
|
||||
| tst.js:5:19:5:21 | res |
|
||||
| tst.js:12:31:12:33 | res |
|
||||
requestInputAccess
|
||||
| body | tst.js:7:5:7:19 | micro.json(req) |
|
||||
| header | tst.js:6:5:6:31 | req.hea ... -type'] |
|
||||
| header | tst.js:13:5:13:31 | req.hea ... -type'] |
|
||||
userControlledObject
|
||||
| tst.js:7:5:7:19 | micro.json(req) |
|
||||
responseSendArgument
|
||||
| tst.js:8:31:8:36 | "data" |
|
||||
responseSendArgumentHandler
|
||||
| tst.js:5:7:10:1 | async ( ... llo";\\n} | tst.js:8:31:8:36 | "data" |
|
||||
@@ -0,0 +1,17 @@
|
||||
import javascript
|
||||
|
||||
query HTTP::RouteHandler routeHandler() { any() }
|
||||
|
||||
query HTTP::Servers::RequestSource requestSource() { any() }
|
||||
|
||||
query HTTP::Servers::ResponseSource responseSource() { any() }
|
||||
|
||||
query HTTP::RequestInputAccess requestInputAccess(string kind) { kind = result.getKind() }
|
||||
|
||||
query HTTP::RequestInputAccess userControlledObject() { result.isUserControlledObject() }
|
||||
|
||||
query HTTP::ResponseSendArgument responseSendArgument() { any() }
|
||||
|
||||
query HTTP::ResponseSendArgument responseSendArgumentHandler(HTTP::RouteHandler h) {
|
||||
h = result.getRouteHandler()
|
||||
}
|
||||
19
javascript/ql/test/library-tests/frameworks/Micro/tst.js
Normal file
19
javascript/ql/test/library-tests/frameworks/Micro/tst.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const micro = require('micro')
|
||||
const bluebird = require('bluebird');
|
||||
const compress = require('micro-compress');
|
||||
|
||||
micro(async (req, res) => {
|
||||
req.headers['content-type'];
|
||||
micro.json(req);
|
||||
micro.sendError(req, res, "data");
|
||||
return "Hello";
|
||||
})
|
||||
|
||||
function* wrappedHandler(req, res) {
|
||||
req.headers['content-type'];
|
||||
yield "Response";
|
||||
}
|
||||
|
||||
let handler = bluebird.coroutine(wrappedHandler);
|
||||
|
||||
micro(compress(handler));
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-094/ImproperCodeSanitization.ql
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
9
javascript/ql/test/query-tests/Security/CWE-094/tmp.html
Normal file
9
javascript/ql/test/query-tests/Security/CWE-094/tmp.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<html>
|
||||
|
||||
<body>
|
||||
<script>
|
||||
var foo ="bla</script onload=\"\">";
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -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\\/ |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-116/IncompleteMultiCharacterSanitization.ql
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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. |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-200/PrivateFileExposure.ql
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "foo",
|
||||
"dependencies": {
|
||||
"async": "3.2.0"
|
||||
}
|
||||
}
|
||||
11
javascript/ql/test/query-tests/Security/CWE-200/lib/tst.js
Normal file
11
javascript/ql/test/query-tests/Security/CWE-200/lib/tst.js
Normal 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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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. |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-295/DisablingCertificateValidation.ql
|
||||
70
javascript/ql/test/query-tests/Security/CWE-295/tst.js
Normal file
70
javascript/ql/test/query-tests/Security/CWE-295/tst.js
Normal 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
|
||||
});
|
||||
@@ -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 |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-312/BuildArtifactLeak.ql
|
||||
@@ -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
|
||||
});
|
||||
@@ -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 |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-327/BadRandomness.ql
|
||||
129
javascript/ql/test/query-tests/Security/CWE-327/bad-random.js
Normal file
129
javascript/ql/test/query-tests/Security/CWE-327/bad-random.js
Normal 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
|
||||
}
|
||||
@@ -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 |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-829/InsecureDownload.ql
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user