mirror of
https://github.com/github/codeql.git
synced 2026-04-29 10:45:15 +02:00
JavaScript: Autoformat all QL files.
This commit is contained in:
@@ -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."
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
* `<`) 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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user