Merge branch 'js-team-sprint' into build-leaks

This commit is contained in:
Erik Krogh Kristensen
2020-06-17 19:51:30 +02:00
69 changed files with 2135 additions and 75 deletions

View File

@@ -11,6 +11,7 @@
- [jGrowl](https://github.com/stanlemon/jGrowl)
- [jQuery](https://jquery.com/)
- [marsdb](https://www.npmjs.com/package/marsdb)
- [micro](https://www.npmjs.com/package/micro/)
- [minimongo](https://www.npmjs.com/package/minimongo/)
- [mssql](https://www.npmjs.com/package/mssql)
- [mysql](https://www.npmjs.com/package/mysql)
@@ -20,6 +21,7 @@
- [sqlite](https://www.npmjs.com/package/sqlite)
- [ssh2-streams](https://www.npmjs.com/package/ssh2-streams)
- [ssh2](https://www.npmjs.com/package/ssh2)
- [yargs](https://www.npmjs.com/package/yargs)
* 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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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>

View File

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

View File

@@ -0,0 +1,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>

View 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."

View File

@@ -0,0 +1,22 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
</overview>
<recommendation>
</recommendation>
<example>
</example>
<references>
</references>
</qhelp>

View File

@@ -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."

View 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>

View 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"

View File

@@ -0,0 +1,22 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
</overview>
<recommendation>
</recommendation>
<example>
</example>
<references>
</references>
</qhelp>

View 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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -78,4 +78,20 @@ module InsecureRandomness {
class CryptoKeySink extends Sink {
CryptoKeySink() { this instanceof CryptographicKey }
}
/**
* Holds if the step `pred` -> `succ` is an additional taint-step for random values that are not cryptographically secure.
*/
predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
// Assume that all operations on tainted values preserve taint: crypto is hard
succ.asExpr().(BinaryExpr).getAnOperand() = pred.asExpr()
or
succ.asExpr().(UnaryExpr).getOperand() = pred.asExpr()
or
exists(DataFlow::MethodCallNode mc |
mc = DataFlow::globalVarRef("Math").getAMemberCall(_) and
pred = mc.getAnArgument() and
succ = mc
)
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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. |

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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.

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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 |

View File

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

View 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

View File

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

View File

@@ -0,0 +1 @@
Security/CWE-730/ServerCrash.ql

View File

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