mirror of
https://github.com/github/codeql.git
synced 2025-12-20 18:56:32 +01:00
Merge branch 'js-team-sprint' into build-leaks
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)
|
||||
|
||||
* TypeScript 3.9 is now supported.
|
||||
|
||||
@@ -35,6 +37,7 @@
|
||||
| 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. |
|
||||
| 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. |
|
||||
|
||||
## Changes to existing queries
|
||||
|
||||
|
||||
@@ -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,24 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
|
||||
<li>OWASP Top 10: <a href="https://www.owasp.org/index.php/Top_10-2017_A1-Injection">A1 Injection</a>.</li>
|
||||
|
||||
</references>
|
||||
|
||||
</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()
|
||||
27
javascript/ql/src/Security/CWE-200/PrivateFileExposure.qhelp
Normal file
27
javascript/ql/src/Security/CWE-200/PrivateFileExposure.qhelp
Normal file
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Placeholder
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Placeholder
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
Placeholder
|
||||
</p>
|
||||
</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>
|
||||
116
javascript/ql/src/Security/CWE-200/PrivateFileExposure.ql
Normal file
116
javascript/ql/src/Security/CWE-200/PrivateFileExposure.ql
Normal file
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* @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` that might cause information to be leaked.
|
||||
* 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.
|
||||
*/
|
||||
DataFlow::Node dirname() {
|
||||
exists(ModuleScope ms | result.asExpr() = ms.getVariable("__dirname").getAnAccess()) and
|
||||
result.getFile().getParentContainer() = getAPackageJSONFolder()
|
||||
or
|
||||
result.getAPredecessor() = dirname()
|
||||
or
|
||||
exists(StringOps::ConcatenationRoot root | root = result |
|
||||
root.getNumOperand() = 2 and
|
||||
root.getOperand(0) = dirname() 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 = dirname() and
|
||||
description = "the folder " + result.getFile().getParentContainer().getRelativePath()
|
||||
or
|
||||
result.getStringValue() = [".", "./"] and
|
||||
description = "the current working folder"
|
||||
}
|
||||
|
||||
/**
|
||||
* Gest a call that serves the folder `path` to the public.
|
||||
*/
|
||||
DataFlow::CallNode servesAPrivateFolder(string description) {
|
||||
result = DataFlow::moduleMember("express", "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,22 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* @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
|
||||
|
||||
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
|
||||
exists(DataFlow::ObjectLiteralNode options, DataFlow::InvokeNode invk |
|
||||
options.flowsTo(invk.getAnArgument()) and
|
||||
disable = options.getAPropertyWrite("rejectUnauthorized") and
|
||||
disable.getRhs().(AnalyzedNode).getTheBooleanValue() = false
|
||||
|
|
||||
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()
|
||||
)
|
||||
select disable, "Disabling certificate validation is strongly discouraged."
|
||||
36
javascript/ql/src/Security/CWE-327/BadRandomness.qhelp
Normal file
36
javascript/ql/src/Security/CWE-327/BadRandomness.qhelp
Normal file
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Placeholder
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
Placeholder.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
Placeholder
|
||||
</p>
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>NIST, FIPS 140 Annex a: <a href="http://csrc.nist.gov/publications/fips/fips140-2/fips1402annexa.pdf"> Approved Security Functions</a>.</li>
|
||||
<li>NIST, SP 800-131A: <a href="http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar1.pdf"> Transitions: Recommendation for Transitioning the Use of Cryptographic Algorithms and Key Lengths</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>
|
||||
<li>Stack Overflow: <a href="https://stackoverflow.com/questions/3956478/understanding-randomness">Understanding “randomness”</a>.</li>
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
195
javascript/ql/src/Security/CWE-327/BadRandomness.ql
Normal file
195
javascript/ql/src/Security/CWE-327/BadRandomness.ql
Normal file
@@ -0,0 +1,195 @@
|
||||
/**
|
||||
* @name Creating biased random numbers from 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 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.
|
||||
DataFlow::globalVarRef("Math")
|
||||
.getAMemberCall(["round", "floor", "ceil"])
|
||||
.getArgument(0)
|
||||
.asExpr() = div
|
||||
)
|
||||
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"
|
||||
22
javascript/ql/src/Security/CWE-730/ServerCrash.qhelp
Normal file
22
javascript/ql/src/Security/CWE-730/ServerCrash.qhelp
Normal file
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
100
javascript/ql/src/Security/CWE-730/ServerCrash.ql
Normal file
100
javascript/ql/src/Security/CWE-730/ServerCrash.ql
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* @name Server crash
|
||||
* @description A server that can be forced to crash may be vulnerable to denial-of-service
|
||||
* attacks.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id js/server-crash
|
||||
* @tags security
|
||||
* external/cwe/cwe-730
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Gets a function that `caller` invokes.
|
||||
*/
|
||||
Function getACallee(Function caller) {
|
||||
exists(DataFlow::InvokeNode invk |
|
||||
invk.getEnclosingFunction() = caller and result = invk.getACallee()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a function that `caller` invokes, excluding calls guarded in `try`-blocks.
|
||||
*/
|
||||
Function getAnUnguardedCallee(Function caller) {
|
||||
exists(DataFlow::InvokeNode invk |
|
||||
invk.getEnclosingFunction() = caller and
|
||||
result = invk.getACallee() and
|
||||
not exists(invk.asExpr().getEnclosingStmt().getEnclosingTryCatchStmt())
|
||||
)
|
||||
}
|
||||
|
||||
predicate isHeaderValue(HTTP::ExplicitHeaderDefinition def, DataFlow::Node node) {
|
||||
def.definesExplicitly(_, node.asExpr())
|
||||
}
|
||||
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "Configuration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) {
|
||||
// using control characters in a header value will cause an exception
|
||||
isHeaderValue(_, node)
|
||||
}
|
||||
}
|
||||
|
||||
predicate isLikelyToThrow(DataFlow::Node crash) {
|
||||
exists(Configuration cfg, DataFlow::Node sink | cfg.hasFlow(_, sink) | isHeaderValue(crash, sink))
|
||||
}
|
||||
|
||||
/**
|
||||
* A call that looks like it is asynchronous.
|
||||
*/
|
||||
class AsyncCall extends DataFlow::CallNode {
|
||||
DataFlow::FunctionNode callback;
|
||||
|
||||
AsyncCall() {
|
||||
callback.flowsTo(getLastArgument()) and
|
||||
callback.getParameter(0).getName() = ["e", "err", "error"] and
|
||||
callback.getNumParameter() = 2 and
|
||||
not exists(callback.getAReturn())
|
||||
}
|
||||
|
||||
DataFlow::FunctionNode getCallback() { result = callback }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a function that is invoked by `asyncCallback` without any try-block wrapping, `asyncCallback` is in turn is called indirectly by `routeHandler`.
|
||||
*
|
||||
* If the result throws an excection, the server of `routeHandler` will crash.
|
||||
*/
|
||||
Function getAPotentialServerCrasher(
|
||||
HTTP::RouteHandler routeHandler, DataFlow::FunctionNode asyncCallback
|
||||
) {
|
||||
exists(AsyncCall asyncCall |
|
||||
// the route handler transitively calls an async function
|
||||
asyncCall.getEnclosingFunction() =
|
||||
getACallee*(routeHandler.(DataFlow::FunctionNode).getFunction()) and
|
||||
asyncCallback = asyncCall.getCallback() and
|
||||
// the async function transitively calls a function that may throw an exception out of the the async function
|
||||
result = getAnUnguardedCallee*(asyncCallback.getFunction())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an AST node that is likely to throw an uncaught exception in `fun`.
|
||||
*/
|
||||
ExprOrStmt getALikelyExceptionThrower(Function fun) {
|
||||
result.getContainer() = fun and
|
||||
not exists([result.(Expr).getEnclosingStmt(), result.(Stmt)].getEnclosingTryCatchStmt()) and
|
||||
(isLikelyToThrow(result.(Expr).flow()) or result instanceof ThrowStmt)
|
||||
}
|
||||
|
||||
from HTTP::RouteHandler routeHandler, DataFlow::FunctionNode asyncCallback, ExprOrStmt crasher
|
||||
where crasher = getALikelyExceptionThrower(getAPotentialServerCrasher(routeHandler, asyncCallback))
|
||||
select crasher, "When an exception is thrown here and later exits $@, the server of $@ will crash.",
|
||||
asyncCallback, "this asynchronous callback", routeHandler, "this route handler"
|
||||
@@ -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())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,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 { }
|
||||
}
|
||||
@@ -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));
|
||||
@@ -79,6 +79,47 @@ nodes
|
||||
| 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] |
|
||||
@@ -149,6 +190,42 @@ edges
|
||||
| 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 |
|
||||
@@ -166,3 +243,8 @@ edges
|
||||
| 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 |
|
||||
|
||||
@@ -52,6 +52,23 @@ cp.exec("cmd.sh " + require("optimist").argv.foo); // NOT OK
|
||||
.usage('Usage: foo bar')
|
||||
.command();
|
||||
|
||||
cp.exec("cmd.sh " + args); // NOT OK - but not flagged yet.
|
||||
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,16 @@
|
||||
| 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. |
|
||||
@@ -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,37 @@
|
||||
|
||||
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.
|
||||
@@ -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,15 @@
|
||||
| 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 |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-327/BadRandomness.ql
|
||||
113
javascript/ql/test/query-tests/Security/CWE-327/bad-random.js
Normal file
113
javascript/ql/test/query-tests/Security/CWE-327/bad-random.js
Normal file
@@ -0,0 +1,113 @@
|
||||
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 [INCONSISTENCY].
|
||||
|
||||
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 [INCONSISTENCY].
|
||||
|
||||
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
|
||||
@@ -0,0 +1,6 @@
|
||||
| server-crash.js:7:5:7:14 | throw err; | When an exception is thrown here and later exits $@, the server of $@ will crash. | server-crash.js:6:28:8:3 | (err, x ... OK\\n } | this asynchronous callback | server-crash.js:31:25:73:1 | (req, r ... });\\n} | this route handler |
|
||||
| server-crash.js:11:3:11:11 | throw 42; | When an exception is thrown here and later exits $@, the server of $@ will crash. | server-crash.js:50:28:52:3 | (err, x ... ();\\n } | this asynchronous callback | server-crash.js:31:25:73:1 | (req, r ... });\\n} | this route handler |
|
||||
| server-crash.js:16:7:16:16 | throw err; | When an exception is thrown here and later exits $@, the server of $@ will crash. | server-crash.js:15:30:17:5 | (err, x ... K\\n } | this asynchronous callback | server-crash.js:31:25:73:1 | (req, r ... });\\n} | this route handler |
|
||||
| server-crash.js:28:5:28:14 | throw err; | When an exception is thrown here and later exits $@, the server of $@ will crash. | server-crash.js:27:28:29:3 | (err, x ... OK\\n } | this asynchronous callback | server-crash.js:31:25:73:1 | (req, r ... });\\n} | this route handler |
|
||||
| server-crash.js:33:5:33:14 | throw err; | When an exception is thrown here and later exits $@, the server of $@ will crash. | server-crash.js:32:28:34:3 | (err, x ... OK\\n } | this asynchronous callback | server-crash.js:31:25:73:1 | (req, r ... });\\n} | this route handler |
|
||||
| server-crash.js:41:5:41:48 | res.set ... header) | When an exception is thrown here and later exits $@, the server of $@ will crash. | server-crash.js:40:28:42:3 | (err, x ... OK\\n } | this asynchronous callback | server-crash.js:31:25:73:1 | (req, r ... });\\n} | this route handler |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-730/ServerCrash.ql
|
||||
@@ -0,0 +1,73 @@
|
||||
const express = require("express");
|
||||
const app = express();
|
||||
const fs = require("fs");
|
||||
|
||||
function indirection1() {
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
throw err; // NOT OK
|
||||
});
|
||||
}
|
||||
function indirection2() {
|
||||
throw 42; // NOT OK
|
||||
}
|
||||
function indirection3() {
|
||||
try {
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
throw err; // NOT OK
|
||||
});
|
||||
} catch (e) {}
|
||||
}
|
||||
function indirection4() {
|
||||
throw 42; // OK: guarded caller
|
||||
}
|
||||
function indirection5() {
|
||||
indirection6();
|
||||
}
|
||||
function indirection6() {
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
throw err; // NOT OK
|
||||
});
|
||||
}
|
||||
app.get("/async-throw", (req, res) => {
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
throw err; // NOT OK
|
||||
});
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
try {
|
||||
throw err; // OK: guarded throw
|
||||
} catch (e) {}
|
||||
});
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
res.setHeader("reflected", req.query.header); // NOT OK
|
||||
});
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
try {
|
||||
res.setHeader("reflected", req.query.header); // OK: guarded call
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
indirection1();
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
indirection2();
|
||||
});
|
||||
|
||||
indirection3();
|
||||
try {
|
||||
indirection4();
|
||||
} catch (e) {}
|
||||
indirection5();
|
||||
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
req.query.foo; // OK
|
||||
});
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
req.query.foo.toString(); // OK
|
||||
});
|
||||
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
req.query.foo.bar; // NOT OK [INCONSISTENCY]: need to add property reads as sinks
|
||||
});
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
res.setHeader("reflected", unknown); // OK
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user