JavaScript: Autoformat all QL files.

This commit is contained in:
Max Schaefer
2019-01-07 10:15:45 +00:00
parent aa6b89dc34
commit 31bb39a810
380 changed files with 9957 additions and 13923 deletions

View File

@@ -18,47 +18,40 @@ import javascript
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "IncompleteHostnameRegExpTracking" }
override
predicate isSource(DataFlow::Node source) {
override predicate isSource(DataFlow::Node source) {
isIncompleteHostNameRegExpPattern(source.asExpr().getStringValue(), _)
}
override
predicate isSink(DataFlow::Node sink) {
isInterpretedAsRegExp(sink)
}
override predicate isSink(DataFlow::Node sink) { isInterpretedAsRegExp(sink) }
}
/**
* Holds if `pattern` is a regular expression pattern for URLs with a host matched by `hostPart`,
* and `pattern` contains a subtle mistake that allows it to match unexpected hosts.
*/
bindingset[pattern]
predicate isIncompleteHostNameRegExpPattern(string pattern, string hostPart) {
hostPart = pattern.regexpCapture(
"(?i).*" +
// an unescaped single `.`
"(?<!\\\\)[.]" +
// immediately followed by a sequence of subdomains, perhaps with some regex characters mixed in, followed by a known TLD
"([():|?a-z0-9-]+(\\\\)?[.](" + RegExpPatterns::commonTLD() + "))" +
".*", 1)
hostPart = pattern
.regexpCapture("(?i).*" +
// an unescaped single `.`
"(?<!\\\\)[.]" +
// immediately followed by a sequence of subdomains, perhaps with some regex characters mixed in, followed by a known TLD
"([():|?a-z0-9-]+(\\\\)?[.](" + RegExpPatterns::commonTLD() + "))" + ".*", 1)
}
from Expr e, string pattern, string hostPart
where
(
e.(RegExpLiteral).getValue() = pattern or
exists (Configuration cfg |
cfg.hasFlow(e.flow(), _) and
e.mayHaveStringValue(pattern)
)
) and
isIncompleteHostNameRegExpPattern(pattern, hostPart)
and
// ignore patterns with capture groups after the TLD
not pattern.regexpMatch("(?i).*[.](" + RegExpPatterns::commonTLD() + ").*[(][?]:.*[)].*")
select e, "This regular expression has an unescaped '.' before '" + hostPart + "', so it might match more hosts than expected."
(
e.(RegExpLiteral).getValue() = pattern
or
exists(Configuration cfg |
cfg.hasFlow(e.flow(), _) and
e.mayHaveStringValue(pattern)
)
) and
isIncompleteHostNameRegExpPattern(pattern, hostPart) and
// ignore patterns with capture groups after the TLD
not pattern.regexpMatch("(?i).*[.](" + RegExpPatterns::commonTLD() + ").*[(][?]:.*[)].*")
select e,
"This regular expression has an unescaped '.' before '" + hostPart +
"', so it might match more hosts than expected."

View File

@@ -15,44 +15,52 @@ private import semmle.javascript.dataflow.InferredTypes
from DataFlow::MethodCallNode call, string name, DataFlow::Node substring, string target
where
(name = "indexOf" or name = "lastIndexOf" or name = "includes" or name = "startsWith" or name = "endsWith") and
call.getMethodName() = name and
substring = call.getArgument(0) and
substring.mayHaveStringValue(target) and
(
// target contains a domain on a common TLD, and perhaps some other URL components
target.regexpMatch("(?i)([a-z]*:?//)?\\.?([a-z0-9-]+\\.)+(" + RegExpPatterns::commonTLD() + ")(:[0-9]+)?/?") or
// target is a HTTP URL to a domain on any TLD
target.regexpMatch("(?i)https?://([a-z0-9-]+\\.)+([a-z]+)(:[0-9]+)?/?")
) and
// whitelist
not (
(name = "indexOf" or name = "lastIndexOf") and
(
// arithmetic on the indexOf-result
any(ArithmeticExpr e).getAnOperand().getUnderlyingValue() = call.asExpr()
or
// non-trivial position check on the indexOf-result
exists(EqualityTest test, Expr n |
test.hasOperands(call.asExpr(), n) |
not n.getIntValue() = [-1..0]
)
)
or
// the leading dot in a subdomain sequence makes the suffix-check safe (if it is performed on the host of the url)
name = "endsWith" and
target.regexpMatch("(?i)\\.([a-z0-9-]+)(\\.[a-z0-9-]+)+")
or
// the trailing slash makes the prefix-check safe
(
name = "startsWith"
or
name = "indexOf" and
exists(EqualityTest test, Expr n |
test.hasOperands(call.asExpr(), n) and
n.getIntValue() = 0
)
) and
target.regexpMatch(".*/")
(
name = "indexOf" or
name = "lastIndexOf" or
name = "includes" or
name = "startsWith" or
name = "endsWith"
) and
call.getMethodName() = name and
substring = call.getArgument(0) and
substring.mayHaveStringValue(target) and
(
// target contains a domain on a common TLD, and perhaps some other URL components
target
.regexpMatch("(?i)([a-z]*:?//)?\\.?([a-z0-9-]+\\.)+(" + RegExpPatterns::commonTLD() +
")(:[0-9]+)?/?")
or
// target is a HTTP URL to a domain on any TLD
target.regexpMatch("(?i)https?://([a-z0-9-]+\\.)+([a-z]+)(:[0-9]+)?/?")
) and
// whitelist
not (
(name = "indexOf" or name = "lastIndexOf") and
(
// arithmetic on the indexOf-result
any(ArithmeticExpr e).getAnOperand().getUnderlyingValue() = call.asExpr()
or
// non-trivial position check on the indexOf-result
exists(EqualityTest test, Expr n | test.hasOperands(call.asExpr(), n) |
not n.getIntValue() = [-1 .. 0]
)
)
or
// the leading dot in a subdomain sequence makes the suffix-check safe (if it is performed on the host of the url)
name = "endsWith" and
target.regexpMatch("(?i)\\.([a-z0-9-]+)(\\.[a-z0-9-]+)+")
or
// the trailing slash makes the prefix-check safe
(
name = "startsWith"
or
name = "indexOf" and
exists(EqualityTest test, Expr n |
test.hasOperands(call.asExpr(), n) and
n.getIntValue() = 0
)
) and
target.regexpMatch(".*/")
)
select call, "'$@' may be at an arbitrary position in the sanitized URL.", substring, target

View File

@@ -9,6 +9,7 @@
* correctness
* external/cwe/cwe-020
*/
import javascript
/**
@@ -16,9 +17,10 @@ import javascript
*/
class IndexOfCall extends DataFlow::MethodCallNode {
IndexOfCall() {
exists (string name | name = getMethodName() |
exists(string name | name = getMethodName() |
name = "indexOf" or
name = "lastIndexOf") and
name = "lastIndexOf"
) and
getNumArgument() = 1
}
@@ -27,7 +29,7 @@ class IndexOfCall extends DataFlow::MethodCallNode {
result = getReceiver() or
result = getArgument(0)
}
/**
* Gets an `indexOf` call with the same receiver, argument, and method name, including this call itself.
*/
@@ -38,11 +40,9 @@ class IndexOfCall extends DataFlow::MethodCallNode {
}
/**
* Gets an expression that refers to the return value of this call.
* Gets an expression that refers to the return value of this call.
*/
Expr getAUse() {
this.flowsToExpr(result)
}
Expr getAUse() { this.flowsToExpr(result) }
}
/**
@@ -66,28 +66,26 @@ class LiteralLengthExpr extends DotExpr {
/**
* Gets the value of the string literal whose length is taken.
*/
string getBaseValue() {
result = getBase().getStringValue()
}
string getBaseValue() { result = getBase().getStringValue() }
}
/**
* Holds if `length` is derived from the length of the given `indexOf`-operand.
*/
predicate isDerivedFromLength(DataFlow::Node length, DataFlow::Node operand) {
exists (IndexOfCall call | operand = call.getAnOperand() |
exists(IndexOfCall call | operand = call.getAnOperand() |
length = getStringSource(operand).getAPropertyRead("length")
or
exists (string val | val = operand.asExpr().getStringValue() |
exists(string val | val = operand.asExpr().getStringValue() |
// Find a literal length with the same string constant
exists (LiteralLengthExpr lengthExpr |
exists(LiteralLengthExpr lengthExpr |
lengthExpr.getContainer() = call.getContainer() and
lengthExpr.getBaseValue() = val and
length = lengthExpr.flow()
)
or
// Find an integer constant that equals the length of string constant
exists (Expr lengthExpr |
exists(Expr lengthExpr |
lengthExpr.getContainer() = call.getContainer() and
lengthExpr.getIntValue() = val.length() and
length = lengthExpr.flow()
@@ -97,9 +95,10 @@ predicate isDerivedFromLength(DataFlow::Node length, DataFlow::Node operand) {
or
isDerivedFromLength(length.getAPredecessor(), operand)
or
exists (SubExpr sub |
exists(SubExpr sub |
isDerivedFromLength(sub.getAnOperand().flow(), operand) and
length = sub.flow())
length = sub.flow()
)
}
/**
@@ -110,45 +109,43 @@ predicate isDerivedFromLength(DataFlow::Node length, DataFlow::Node operand) {
*/
class UnsafeIndexOfComparison extends EqualityTest {
IndexOfCall indexOf;
DataFlow::Node testedValue;
UnsafeIndexOfComparison() {
hasOperands(indexOf.getAUse(), testedValue.asExpr()) and
isDerivedFromLength(testedValue, indexOf.getReceiver()) and
isDerivedFromLength(testedValue, indexOf.getArgument(0)) and
// Ignore cases like `x.indexOf("/") === x.length - 1` that can only be bypassed if `x` is the empty string.
// Sometimes strings are just known to be non-empty from the context, and it is unlikely to be a security issue,
// since it's obviously not a domain name check.
not indexOf.getArgument(0).mayHaveStringValue(any(string s | s.length() = 1)) and
// Relative string length comparison, such as A.length > B.length, or (A.length - B.length) > 0
not exists (RelationalComparison compare |
not exists(RelationalComparison compare |
isDerivedFromLength(compare.getAnOperand().flow(), indexOf.getReceiver()) and
isDerivedFromLength(compare.getAnOperand().flow(), indexOf.getArgument(0))
) and
// Check for indexOf being -1
not exists (EqualityTest test, Expr minusOne |
not exists(EqualityTest test, Expr minusOne |
test.hasOperands(indexOf.getAnEquivalentIndexOfCall().getAUse(), minusOne) and
minusOne.getIntValue() = -1
) and
// Check for indexOf being >1, or >=0, etc
not exists (RelationalComparison test |
not exists(RelationalComparison test |
test.getGreaterOperand() = indexOf.getAnEquivalentIndexOfCall().getAUse() and
exists (int value | value = test.getLesserOperand().getIntValue() |
exists(int value | value = test.getLesserOperand().getIntValue() |
value >= 0
or
not test.isInclusive() and
value = -1)
value = -1
)
)
}
IndexOfCall getIndexOf() {
result = indexOf
}
IndexOfCall getIndexOf() { result = indexOf }
}
from UnsafeIndexOfComparison comparison
select comparison, "This suffix check is missing a length comparison to correctly handle " + comparison.getIndexOf().getMethodName() + " returning -1."
select comparison,
"This suffix check is missing a length comparison to correctly handle " +
comparison.getIndexOf().getMethodName() + " returning -1."

View File

@@ -20,5 +20,5 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "This path depends on $@.",
source.getNode(), "a user-provided value"
select sink.getNode(), source, sink, "This path depends on $@.", source.getNode(),
"a user-provided value"

View File

@@ -17,10 +17,10 @@ import semmle.javascript.security.dataflow.CommandInjection::CommandInjection
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node highlight
where cfg.hasFlowPath(source, sink) and
if cfg.isSinkWithHighlight(sink.getNode(), _) then
cfg.isSinkWithHighlight(sink.getNode(), highlight)
else
highlight = sink.getNode()
select highlight, source, sink, "This command depends on $@.",
source.getNode(), "a user-provided value"
where
cfg.hasFlowPath(source, sink) and
if cfg.isSinkWithHighlight(sink.getNode(), _)
then cfg.isSinkWithHighlight(sink.getNode(), highlight)
else highlight = sink.getNode()
select highlight, source, sink, "This command depends on $@.", source.getNode(),
"a user-provided value"

View File

@@ -18,4 +18,4 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to $@.",
source.getNode(), "user-provided value"
source.getNode(), "user-provided value"

View File

@@ -18,4 +18,4 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Stored cross-site scripting vulnerability due to $@.",
source.getNode(), "stored value"
source.getNode(), "stored value"

View File

@@ -17,5 +17,6 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, sink.getNode().(Sink).getVulnerabilityKind() + " vulnerability due to $@.",
source.getNode(), "user-provided value"
select sink.getNode(), source, sink,
sink.getNode().(Sink).getVulnerabilityKind() + " vulnerability due to $@.", source.getNode(),
"user-provided value"

View File

@@ -1,14 +1,14 @@
/**
* @name Database query built from user-controlled sources
* @description Building a database query from user-controlled sources is vulnerable to insertion of
* malicious code by the user.
* @kind path-problem
* @problem.severity error
* @precision high
* @id js/sql-injection
* @tags security
* external/cwe/cwe-089
*/
* @name Database query built from user-controlled sources
* @description Building a database query from user-controlled sources is vulnerable to insertion of
* malicious code by the user.
* @kind path-problem
* @problem.severity error
* @precision high
* @id js/sql-injection
* @tags security
* external/cwe/cwe-089
*/
import javascript
import semmle.javascript.security.dataflow.SqlInjection
@@ -16,8 +16,11 @@ import semmle.javascript.security.dataflow.NosqlInjection
import DataFlow::PathGraph
from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where (cfg instanceof SqlInjection::Configuration or
cfg instanceof NosqlInjection::Configuration) and
cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "This query depends on $@.",
source.getNode(), "a user-provided value"
where
(
cfg instanceof SqlInjection::Configuration or
cfg instanceof NosqlInjection::Configuration
) and
cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "This query depends on $@.", source.getNode(),
"a user-provided value"

View File

@@ -19,4 +19,4 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "$@ flows to here and is interpreted as code.",
source.getNode(), "User-provided value"
source.getNode(), "User-provided value"

View File

@@ -8,10 +8,13 @@
* @tags security
* external/cwe/cwe-094
*/
import javascript
import semmle.javascript.security.dataflow.UnsafeDynamicMethodAccess::UnsafeDynamicMethodAccess
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink, source, sink, "Invocation of method derived from $@ may lead to remote code execution.", source.getNode(), "user-controlled value"
select sink, source, sink,
"Invocation of method derived from $@ may lead to remote code execution.", source.getNode(),
"user-controlled value"

View File

@@ -30,13 +30,16 @@ import javascript
*/
language[monotonicAggregates]
string getStringValue(RegExpLiteral rl) {
exists (RegExpTerm root | root = rl.getRoot() |
exists(RegExpTerm root | root = rl.getRoot() |
result = root.(RegExpConstant).getValue()
or
result = strictconcat(RegExpTerm ch, int i |
ch = root.(RegExpSequence).getChild(i) |
ch.(RegExpConstant).getValue() order by i
)
ch = root.(RegExpSequence).getChild(i)
|
ch.(RegExpConstant).getValue()
order by
i
)
)
}
@@ -67,7 +70,7 @@ class Replacement extends DataFlow::Node {
RegExpLiteral pattern;
Replacement() {
exists (DataFlow::MethodCallNode mcn | this = mcn |
exists(DataFlow::MethodCallNode mcn | this = mcn |
mcn.getMethodName() = "replace" and
mcn.getArgument(0).asExpr() = pattern and
mcn.getNumArgument() = 2 and
@@ -79,7 +82,7 @@ class Replacement extends DataFlow::Node {
* Holds if this replacement replaces the string `input` with `output`.
*/
predicate replaces(string input, string output) {
exists (DataFlow::MethodCallNode mcn |
exists(DataFlow::MethodCallNode mcn |
mcn = this and
input = getStringValue(pattern) and
output = mcn.getArgument(1).asExpr().getStringValue()
@@ -93,7 +96,7 @@ class Replacement extends DataFlow::Node {
* using `&`.
*/
predicate escapes(string char, string metachar) {
exists (string regexp, string repl |
exists(string regexp, string repl |
escapingScheme(metachar, regexp) and
replaces(char, repl) and
repl.regexpMatch(regexp)
@@ -107,7 +110,7 @@ class Replacement extends DataFlow::Node {
* `&lt;`) using `<`.
*/
predicate unescapes(string metachar, string char) {
exists (string regexp, string orig |
exists(string regexp, string orig |
escapingScheme(metachar, regexp) and
replaces(orig, char) and
orig.regexpMatch(regexp)
@@ -126,11 +129,10 @@ class Replacement extends DataFlow::Node {
* performs an escaping.
*/
Replacement getAnEarlierEscaping(string metachar) {
exists (Replacement pred | pred = this.getPreviousReplacement() |
if pred.escapes(_, metachar) then
result = pred
else
result = pred.getAnEarlierEscaping(metachar)
exists(Replacement pred | pred = this.getPreviousReplacement() |
if pred.escapes(_, metachar)
then result = pred
else result = pred.getAnEarlierEscaping(metachar)
)
}
@@ -139,21 +141,21 @@ class Replacement extends DataFlow::Node {
* performs a unescaping.
*/
Replacement getALaterUnescaping(string metachar) {
exists (Replacement succ | this = succ.getPreviousReplacement() |
if succ.unescapes(metachar, _) then
result = succ
else
result = succ.getALaterUnescaping(metachar)
exists(Replacement succ | this = succ.getPreviousReplacement() |
if succ.unescapes(metachar, _)
then result = succ
else result = succ.getALaterUnescaping(metachar)
)
}
}
from Replacement primary, Replacement supplementary, string message, string metachar
where primary.escapes(metachar, _) and
supplementary = primary.getAnEarlierEscaping(metachar) and
message = "may double-escape '" + metachar + "' characters from $@"
or
primary.unescapes(_, metachar) and
supplementary = primary.getALaterUnescaping(metachar) and
message = "may produce '" + metachar + "' characters that are double-unescaped $@"
where
primary.escapes(metachar, _) and
supplementary = primary.getAnEarlierEscaping(metachar) and
message = "may double-escape '" + metachar + "' characters from $@"
or
primary.unescapes(_, metachar) and
supplementary = primary.getALaterUnescaping(metachar) and
message = "may produce '" + metachar + "' characters that are double-unescaped $@"
select primary, "This replacement " + message + ".", supplementary, "here"

View File

@@ -17,9 +17,7 @@ import javascript
/**
* Gets a character that is commonly used as a meta-character.
*/
string metachar() {
result = "'\"\\&<>\n\r\t*|{}[]%$".charAt(_)
}
string metachar() { result = "'\"\\&<>\n\r\t*|{}[]%$".charAt(_) }
/** Gets a string matched by `e` in a `replace` call. */
string getAMatchedString(Expr e) {
@@ -36,7 +34,7 @@ RegExpConstant getAMatchedConstant(RegExpTerm t) {
or
result = getAMatchedConstant(t.(RegExpGroup).getAChild())
or
exists (RegExpCharacterClass recc | recc = t and not recc.isInverted() |
exists(RegExpCharacterClass recc | recc = t and not recc.isInverted() |
result = getAMatchedConstant(recc.getAChild())
)
}
@@ -48,10 +46,11 @@ predicate isSimple(RegExpTerm t) {
isSimple(t.(RegExpGroup).getAChild())
or
(
t instanceof RegExpAlt or
t instanceof RegExpCharacterClass and not t.(RegExpCharacterClass).isInverted()
t instanceof RegExpAlt
or
t instanceof RegExpCharacterClass and not t.(RegExpCharacterClass).isInverted()
) and
forall (RegExpTerm ch | ch = t.getAChild() | isSimple(ch))
forall(RegExpTerm ch | ch = t.getAChild() | isSimple(ch))
}
/**
@@ -62,7 +61,7 @@ predicate isBackslashEscape(MethodCallExpr mce, RegExpLiteral re) {
mce.getMethodName() = "replace" and
re = mce.getArgument(0) and
re.isGlobal() and
exists (string new | new = mce.getArgument(1).getStringValue() |
exists(string new | new = mce.getArgument(1).getStringValue() |
// `new` is `\$&`, `\$1` or similar
new.regexpMatch("\\\\\\$(&|\\d)")
or
@@ -79,17 +78,22 @@ predicate allBackslashesEscaped(DataFlow::Node nd) {
nd = DataFlow::globalVarRef("JSON").getAMemberCall("stringify")
or
// check whether `nd` itself escapes backslashes
exists (RegExpLiteral rel | isBackslashEscape(nd.asExpr(), rel) |
exists(RegExpLiteral rel | isBackslashEscape(nd.asExpr(), rel) |
// if it's a complex regexp, we conservatively assume that it probably escapes backslashes
not isSimple(rel.getRoot()) or
getAMatchedString(rel) = "\\"
)
or
// flow through string methods
exists (DataFlow::MethodCallNode mc, string m |
exists(DataFlow::MethodCallNode mc, string m |
m = "replace" or
m = "slice" or m = "substr" or m = "substring" or
m = "toLowerCase" or m = "toUpperCase" or m = "trim" |
m = "slice" or
m = "substr" or
m = "substring" or
m = "toLowerCase" or
m = "toUpperCase" or
m = "trim"
|
mc = nd and m = mc.getMethodName() and allBackslashesEscaped(mc.getReceiver())
)
or
@@ -98,31 +102,32 @@ predicate allBackslashesEscaped(DataFlow::Node nd) {
}
from MethodCallExpr repl, Expr old, string msg
where repl.getMethodName() = "replace" and
old = repl.getArgument(0) and
(
not old.(RegExpLiteral).isGlobal() and
msg = "This replaces only the first occurrence of " + old + "." and
// only flag if this is likely to be a sanitizer or URL encoder or decoder
exists (string m | m = getAMatchedString(old) |
// sanitizer
m = metachar()
or
exists (string urlEscapePattern | urlEscapePattern = "(%[0-9A-Fa-f]{2})+" |
// URL decoder
m.regexpMatch(urlEscapePattern)
or
// URL encoder
repl.getArgument(1).getStringValue().regexpMatch(urlEscapePattern)
)
) and
// don't flag replace operations in a loop
not DataFlow::valueNode(repl.getReceiver()) = DataFlow::valueNode(repl).getASuccessor+()
or
exists (RegExpLiteral rel |
isBackslashEscape(repl, rel) and
not allBackslashesEscaped(DataFlow::valueNode(repl)) and
msg = "This does not backslash-escape the backslash character."
)
where
repl.getMethodName() = "replace" and
old = repl.getArgument(0) and
(
not old.(RegExpLiteral).isGlobal() and
msg = "This replaces only the first occurrence of " + old + "." and
// only flag if this is likely to be a sanitizer or URL encoder or decoder
exists(string m | m = getAMatchedString(old) |
// sanitizer
m = metachar()
or
exists(string urlEscapePattern | urlEscapePattern = "(%[0-9A-Fa-f]{2})+" |
// URL decoder
m.regexpMatch(urlEscapePattern)
or
// URL encoder
repl.getArgument(1).getStringValue().regexpMatch(urlEscapePattern)
)
) and
// don't flag replace operations in a loop
not DataFlow::valueNode(repl.getReceiver()) = DataFlow::valueNode(repl).getASuccessor+()
or
exists(RegExpLiteral rel |
isBackslashEscape(repl, rel) and
not allBackslashesEscaped(DataFlow::valueNode(repl)) and
msg = "This does not backslash-escape the backslash character."
)
)
select old, msg

View File

@@ -16,4 +16,4 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "$@ flows here and is used in a format string.",
source.getNode(), "User-provided value"
source.getNode(), "User-provided value"

View File

@@ -15,4 +15,4 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "$@ flows directly to outbound network request",
source.getNode(), "File data"
source.getNode(), "File data"

View File

@@ -17,5 +17,6 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Stack trace information from $@ may be exposed to an external user here.",
source.getNode(), "here"
select sink.getNode(), source, sink,
"Stack trace information from $@ may be exposed to an external user here.", source.getNode(),
"here"

View File

@@ -20,21 +20,22 @@ import DataFlow::PathGraph
* Holds if `tl` is used in a browser environment.
*/
predicate inBrowserEnvironment(TopLevel tl) {
tl instanceof InlineScript or
tl instanceof CodeInAttribute or
exists (GlobalVarAccess e |
e.getTopLevel() = tl |
e.getName() = "window"
) or
exists (Module m | inBrowserEnvironment(m) |
tl instanceof InlineScript
or
tl instanceof CodeInAttribute
or
exists(GlobalVarAccess e | e.getTopLevel() = tl | e.getName() = "window")
or
exists(Module m | inBrowserEnvironment(m) |
tl = m.getAnImportedModule() or
m = tl.(Module).getAnImportedModule()
)
}
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink) and
// ignore logging to the browser console (even though it is not a good practice)
not inBrowserEnvironment(sink.getNode().asExpr().getTopLevel())
where
cfg.hasFlowPath(source, sink) and
// ignore logging to the browser console (even though it is not a good practice)
not inBrowserEnvironment(sink.getNode().asExpr().getTopLevel())
select sink.getNode(), source, sink, "Sensitive data returned by $@ is logged here.",
source.getNode(), source.getNode().(Source).describe()
source.getNode(), source.getNode().(Source).describe()

View File

@@ -19,4 +19,4 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Sensitive data returned by $@ is stored here.",
source.getNode(), source.getNode().(Source).describe()
source.getNode(), source.getNode().(Source).describe()

View File

@@ -21,13 +21,12 @@ import javascript
* Dependencies in `package.json` files are excluded by this predicate.
*/
predicate config(string key, string val, Locatable valElement) {
exists (JSONObject obj |
not exists(PackageJSON p | obj = p.getADependenciesObject(_)) |
exists(JSONObject obj | not exists(PackageJSON p | obj = p.getADependenciesObject(_)) |
obj.getPropValue(key) = valElement and
val = valElement.(JSONString).getValue()
)
or
exists (YAMLMapping m, YAMLString keyElement |
exists(YAMLMapping m, YAMLString keyElement |
m.maps(keyElement, valElement) and
key = keyElement.getValue() and
val = valElement.(YAMLString).getValue()
@@ -43,10 +42,14 @@ predicate exclude(File f) {
}
from string key, string val, Locatable valElement
where config(key, val, valElement) and val != "" and
(key.toLowerCase() = "password"
or
key.toLowerCase() != "readme" and
val.regexpMatch("(?is).*password\\s*=(?!\\s*;).*")) and
not exclude(valElement.getFile())
where
config(key, val, valElement) and
val != "" and
(
key.toLowerCase() = "password"
or
key.toLowerCase() != "readme" and
val.regexpMatch("(?is).*password\\s*=(?!\\s*;).*")
) and
not exclude(valElement.getFile())
select valElement, "Avoid plaintext passwords in configuration files."

View File

@@ -15,7 +15,9 @@ import semmle.javascript.security.SensitiveActions
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink) and
not source.getNode().asExpr() instanceof CleartextPasswordExpr // flagged by js/insufficient-password-hash
select sink.getNode(), source, sink, "Sensitive data from $@ is used in a broken or weak cryptographic algorithm.",
source.getNode(), source.getNode().(Source).describe()
where
cfg.hasFlowPath(source, sink) and
not source.getNode().asExpr() instanceof CleartextPasswordExpr // flagged by js/insufficient-password-hash
select sink.getNode(), source, sink,
"Sensitive data from $@ is used in a broken or weak cryptographic algorithm.", source.getNode(),
source.getNode().(Source).describe()

View File

@@ -10,6 +10,7 @@
* @tags security
* external/cwe/cwe-338
*/
import javascript
import semmle.javascript.security.dataflow.InsecureRandomness::InsecureRandomness
import DataFlow::PathGraph
@@ -17,4 +18,4 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Cryptographically insecure $@ in a security context.",
source.getNode(), "random value"
source.getNode(), "random value"

View File

@@ -10,7 +10,6 @@
* external/cwe/cwe-639
*/
import javascript
import semmle.javascript.security.dataflow.CorsMisconfigurationForCredentials::CorsMisconfigurationForCredentials
import DataFlow::PathGraph
@@ -18,5 +17,5 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "$@ leak vulnerability due to $@.",
sink.getNode().(Sink).getCredentialsHeader(), "Credential",
source.getNode(), "a misconfigured CORS header value"
sink.getNode().(Sink).getCredentialsHeader(), "Credential", source.getNode(),
"a misconfigured CORS header value"

View File

@@ -40,7 +40,7 @@ predicate hasCookieMiddleware(Express::RouteHandlerExpr expr, Express::RouteHand
* ```
*/
DataFlow::CallNode csrfMiddlewareCreation() {
exists (DataFlow::SourceNode callee | result = callee.getACall() |
exists(DataFlow::SourceNode callee | result = callee.getACall() |
callee = DataFlow::moduleImport("csurf")
or
callee = DataFlow::moduleImport("lusca") and
@@ -57,18 +57,17 @@ predicate hasCsrfMiddleware(Express::RouteHandlerExpr handler) {
csrfMiddlewareCreation().flowsToExpr(handler.getAMatchingAncestor())
}
from Express::RouterDefinition router, Express::RouteSetup setup, Express::RouteHandlerExpr handler,
Express::RouteHandlerExpr cookie
where router = setup.getRouter()
and handler = setup.getARouteHandlerExpr()
and hasCookieMiddleware(handler, cookie)
and not hasCsrfMiddleware(handler)
from
Express::RouterDefinition router, Express::RouteSetup setup, Express::RouteHandlerExpr handler,
Express::RouteHandlerExpr cookie
where
router = setup.getRouter() and
handler = setup.getARouteHandlerExpr() and
hasCookieMiddleware(handler, cookie) and
not hasCsrfMiddleware(handler) and
// Only warn for the last handler in a chain.
and handler.isLastHandler()
handler.isLastHandler() and
// Only warn for dangerous for handlers, such as for POST and PUT.
and not setup.getRequestMethod().isSafe()
select cookie, "This cookie middleware is serving a request handler $@ without CSRF protection.", handler, "here"
not setup.getRequestMethod().isSafe()
select cookie, "This cookie middleware is serving a request handler $@ without CSRF protection.",
handler, "here"

View File

@@ -9,7 +9,7 @@
* @tags security
* external/cwe/cwe-250
* external/cwe/cwe-400
*/
*/
import javascript
import semmle.javascript.security.dataflow.RemotePropertyInjection::RemotePropertyInjection
@@ -18,4 +18,4 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "A $@ is used as" + sink.getNode().(Sink).getMessage(),
source.getNode(), "user-provided value"
source.getNode(), "user-provided value"

View File

@@ -16,5 +16,4 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Unsafe deserialization of $@.",
source.getNode(), "user input"
select sink.getNode(), source, sink, "Unsafe deserialization of $@.", source.getNode(), "user input"

View File

@@ -18,5 +18,5 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"Hard-coded data from $@ is interpreted as " + sink.getNode().(Sink).getKind() + ".",
source.getNode(), "here"
"Hard-coded data from $@ is interpreted as " + sink.getNode().(Sink).getKind() + ".",
source.getNode(), "here"

View File

@@ -18,5 +18,5 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Untrusted URL redirection due to $@.",
source.getNode(), "user-provided value"
select sink.getNode(), source, sink, "Untrusted URL redirection due to $@.", source.getNode(),
"user-provided value"

View File

@@ -16,5 +16,5 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Untrusted URL redirection due to $@.",
source.getNode(), "user-provided value"
select sink.getNode(), source, sink, "Untrusted URL redirection due to $@.", source.getNode(),
"user-provided value"

View File

@@ -17,5 +17,6 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "A $@ is parsed as XML without guarding against external entity expansion.",
source.getNode(), "user-provided value"
select sink.getNode(), source, sink,
"A $@ is parsed as XML without guarding against external entity expansion.", source.getNode(),
"user-provided value"

View File

@@ -16,5 +16,6 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Links in this email can be hijacked by poisoning the HTTP host header $@.",
source.getNode(), "here"
select sink.getNode(), source, sink,
"Links in this email can be hijacked by poisoning the HTTP host header $@.", source.getNode(),
"here"

View File

@@ -17,4 +17,4 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "$@ flows here and is used in an XPath expression.",
source.getNode(), "User-provided value"
source.getNode(), "User-provided value"

View File

@@ -19,4 +19,4 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "This regular expression is constructed from a $@.",
source.getNode(), "user-provided value"
source.getNode(), "user-provided value"

View File

@@ -17,5 +17,5 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"Invocation of method with $@ name may dispatch to unexpected target and cause an exception.",
source.getNode(), "user-controlled"
"Invocation of method with $@ name may dispatch to unexpected target and cause an exception.",
source.getNode(), "user-controlled"

View File

@@ -17,9 +17,12 @@ import javascript
import semmle.javascript.security.dataflow.MissingRateLimiting
import semmle.javascript.RestrictedLocations
from ExpensiveRouteHandler r, Express::RouteHandlerExpr rhe,
string explanation, DataFlow::Node reference, string referenceLabel
where r = rhe.getBody() and
r.explain(explanation, reference, referenceLabel) and
not rhe instanceof RateLimitedRouteHandlerExpr
select (FirstLineOf)rhe, "This route handler " + explanation + ", but is not rate-limited.", reference, referenceLabel
from
ExpensiveRouteHandler r, Express::RouteHandlerExpr rhe, string explanation,
DataFlow::Node reference, string referenceLabel
where
r = rhe.getBody() and
r.explain(explanation, reference, referenceLabel) and
not rhe instanceof RateLimitedRouteHandlerExpr
select rhe.(FirstLineOf), "This route handler " + explanation + ", but is not rate-limited.",
reference, referenceLabel

View File

@@ -17,5 +17,6 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "A $@ is parsed as XML without guarding against uncontrolled entity expansion.",
source.getNode(), "user-provided value"
select sink.getNode(), source, sink,
"A $@ is parsed as XML without guarding against uncontrolled entity expansion.", source.getNode(),
"user-provided value"

View File

@@ -17,11 +17,13 @@ private import semmle.javascript.security.dataflow.HardcodedCredentials::Hardcod
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, string value
where cfg.hasFlowPath(source, sink) and
// use source value in message if it's available
if source.getNode().asExpr() instanceof ConstantString then
value = "The hard-coded value \"" + source.getNode().asExpr().(ConstantString).getStringValue() + "\""
else
value = "This hard-coded value"
select source.getNode(), source, sink, value + " is used as $@.",
sink.getNode(), sink.getNode().(Sink).getKind()
where
cfg.hasFlowPath(source, sink) and
// use source value in message if it's available
if source.getNode().asExpr() instanceof ConstantString
then
value = "The hard-coded value \"" + source.getNode().asExpr().(ConstantString).getStringValue() +
"\""
else value = "This hard-coded value"
select source.getNode(), source, sink, value + " is used as $@.", sink.getNode(),
sink.getNode().(Sink).getKind()

View File

@@ -27,20 +27,14 @@ predicate flowsToGuardExpr(DataFlow::Node nd, SensitiveActionGuardConditional gu
* `var ok = x == y; if (ok) login()`.
*/
class SensitiveActionGuardComparison extends Comparison {
SensitiveActionGuardConditional guard;
SensitiveActionGuardComparison() {
flowsToGuardExpr(DataFlow::valueNode(this), guard)
}
SensitiveActionGuardComparison() { flowsToGuardExpr(DataFlow::valueNode(this), guard) }
/**
* Gets the guard that uses this comparison.
*/
SensitiveActionGuardConditional getGuard() {
result = guard
}
SensitiveActionGuardConditional getGuard() { result = guard }
}
/**
@@ -48,17 +42,11 @@ class SensitiveActionGuardComparison extends Comparison {
* This sink should not be presented to the client of this query.
*/
class SensitiveActionGuardComparisonOperand extends Sink {
SensitiveActionGuardComparison comparison;
SensitiveActionGuardComparisonOperand() {
asExpr() = comparison.getAnOperand()
}
override SensitiveAction getAction() {
result = comparison.getGuard().getAction()
}
SensitiveActionGuardComparisonOperand() { asExpr() = comparison.getAnOperand() }
override SensitiveAction getAction() { result = comparison.getGuard().getAction() }
}
/**
@@ -67,19 +55,27 @@ class SensitiveActionGuardComparisonOperand extends Sink {
* If flow from `source` taints `sink`, then an attacker can
* control if `action` should be executed or not.
*/
predicate isTaintedGuardForSensitiveAction(DataFlow::PathNode sink, DataFlow::PathNode source, SensitiveAction action) {
predicate isTaintedGuardForSensitiveAction(
DataFlow::PathNode sink, DataFlow::PathNode source, SensitiveAction action
) {
action = sink.getNode().(Sink).getAction() and
// exclude the intermediary sink
not sink.getNode() instanceof SensitiveActionGuardComparisonOperand and
exists (Configuration cfg |
exists(Configuration cfg |
// ordinary taint tracking to a guard
cfg.hasFlowPath(source, sink) or
cfg.hasFlowPath(source, sink)
or
// taint tracking to both operands of a guard comparison
exists (SensitiveActionGuardComparison cmp, DataFlow::PathNode lSource, DataFlow::PathNode rSource,
DataFlow::PathNode lSink, DataFlow::PathNode rSink |
exists(
SensitiveActionGuardComparison cmp, DataFlow::PathNode lSource, DataFlow::PathNode rSource,
DataFlow::PathNode lSink, DataFlow::PathNode rSink
|
sink.getNode() = cmp.getGuard() and
cfg.hasFlowPath(lSource, lSink) and lSink.getNode() = DataFlow::valueNode(cmp.getLeftOperand()) and
cfg.hasFlowPath(rSource, rSink) and rSink.getNode() = DataFlow::valueNode(cmp.getRightOperand()) |
cfg.hasFlowPath(lSource, lSink) and
lSink.getNode() = DataFlow::valueNode(cmp.getLeftOperand()) and
cfg.hasFlowPath(rSource, rSink) and
rSink.getNode() = DataFlow::valueNode(cmp.getRightOperand())
|
source = lSource or
source = rSource
)
@@ -92,26 +88,28 @@ predicate isTaintedGuardForSensitiveAction(DataFlow::PathNode sink, DataFlow::Pa
* Example: `if (e) return; action(x)`.
*/
predicate isEarlyAbortGuard(DataFlow::PathNode e, SensitiveAction action) {
exists (IfStmt guard |
exists(IfStmt guard |
// `e` is in the condition of an if-statement ...
e.getNode().(Sink).asExpr().getParentExpr*() = guard.getCondition() and
// ... where the then-branch always throws or returns
exists (Stmt abort |
exists(Stmt abort |
abort instanceof ThrowStmt or
abort instanceof ReturnStmt |
abort instanceof ReturnStmt
|
abort.nestedIn(guard) and
abort.getBasicBlock().(ReachableBasicBlock).postDominates(guard.getThen().getBasicBlock() )
abort.getBasicBlock().(ReachableBasicBlock).postDominates(guard.getThen().getBasicBlock())
) and
// ... and the else-branch does not exist
not exists (guard.getElse()) |
not exists(guard.getElse())
|
// ... and `action` is outside the if-statement
not action.asExpr().getEnclosingStmt().nestedIn(guard)
)
}
from DataFlow::PathNode source, DataFlow::PathNode sink, SensitiveAction action
where isTaintedGuardForSensitiveAction(sink, source, action) and
not isEarlyAbortGuard(sink, action)
where
isTaintedGuardForSensitiveAction(sink, source, action) and
not isEarlyAbortGuard(sink, action)
select sink.getNode(), source, sink, "This condition guards a sensitive $@, but $@ controls it.",
action, "action",
source.getNode(), "a user-provided value"
action, "action", source.getNode(), "a user-provided value"

View File

@@ -9,15 +9,20 @@
* external/cwe/cwe-807
* external/cwe/cwe-290
*/
import javascript
import semmle.javascript.security.dataflow.DifferentKindsComparisonBypass::DifferentKindsComparisonBypass
from DifferentKindsComparison cmp, DataFlow::Node lSource, DataFlow::Node rSource
where lSource = cmp.getLSource() and rSource = cmp.getRSource() and
not (
// Standard names for the double submit cookie pattern (CSRF protection)
exists (DataFlow::PropRead s | s = lSource or s = rSource |
s.getPropertyName().regexpMatch("(?i).*(csrf|state|token).*")
)
)
select cmp, "This comparison of $@ and $@ is a potential security risk since it is controlled by the user.", lSource, lSource.toString(), rSource, rSource.toString()
where
lSource = cmp.getLSource() and
rSource = cmp.getRSource() and
not (
// Standard names for the double submit cookie pattern (CSRF protection)
exists(DataFlow::PropRead s | s = lSource or s = rSource |
s.getPropertyName().regexpMatch("(?i).*(csrf|state|token).*")
)
)
select cmp,
"This comparison of $@ and $@ is a potential security risk since it is controlled by the user.",
lSource, lSource.toString(), rSource, rSource.toString()

View File

@@ -15,5 +15,5 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Potential type confusion for $@.",
source.getNode(), "HTTP request parameter"
select sink.getNode(), source, sink, "Potential type confusion for $@.", source.getNode(),
"HTTP request parameter"

View File

@@ -15,5 +15,5 @@ import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Password from $@ is hashed insecurely.",
source.getNode(), source.getNode().(Source).describe()
select sink.getNode(), source, sink, "Password from $@ is hashed insecurely.", source.getNode(),
source.getNode().(Source).describe()

View File

@@ -14,7 +14,8 @@ import semmle.javascript.security.dataflow.RequestForgery::RequestForgery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node request
where cfg.hasFlowPath(source, sink) and
request = sink.getNode().(Sink).getARequest()
select request, source, sink, "The $@ of this request depends on $@.",
sink.getNode(), sink.getNode().(Sink).getKind(), source, "a user-provided value"
where
cfg.hasFlowPath(source, sink) and
request = sink.getNode().(Sink).getARequest()
select request, source, sink, "The $@ of this request depends on $@.", sink.getNode(),
sink.getNode().(Sink).getKind(), source, "a user-provided value"