mirror of
https://github.com/github/codeql.git
synced 2026-05-04 21:25:44 +02:00
Merge pull request #14383 from geoffw0/nsstringregex
Swift: Add regular expression evaluation models for StringProtocol and NSString methods
This commit is contained in:
5
swift/ql/lib/change-notes/2023-10-05-regex-models.md
Normal file
5
swift/ql/lib/change-notes/2023-10-05-regex-models.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
|
||||
* Added models of `StringProtocol` and `NSString` methods that evaluate regular expressions.
|
||||
@@ -69,10 +69,15 @@ abstract class RegexCreation extends DataFlow::Node {
|
||||
abstract DataFlow::Node getStringInput();
|
||||
|
||||
/**
|
||||
* Gets a dataflow node for the options input that might contain parse mode
|
||||
* flags (if any).
|
||||
* Gets a dataflow node for an options input that might contain options
|
||||
* such as parse mode flags (if any).
|
||||
*/
|
||||
DataFlow::Node getOptionsInput() { none() }
|
||||
DataFlow::Node getAnOptionsInput() { none() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `getAnOptionsInput()` instead.
|
||||
*/
|
||||
deprecated DataFlow::Node getOptionsInput() { result = this.getAnOptionsInput() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,7 +115,7 @@ private class NSRegularExpressionRegexCreation extends RegexCreation {
|
||||
|
||||
override DataFlow::Node getStringInput() { result = input }
|
||||
|
||||
override DataFlow::Node getOptionsInput() {
|
||||
override DataFlow::Node getAnOptionsInput() {
|
||||
result.asExpr() = this.asExpr().(CallExpr).getArgument(1).getExpr()
|
||||
}
|
||||
}
|
||||
@@ -121,7 +126,8 @@ private newtype TRegexParseMode =
|
||||
MkDotAll() or // dot matches all characters, including line terminators
|
||||
MkMultiLine() or // `^` and `$` also match beginning and end of lines
|
||||
MkUnicodeBoundary() or // Unicode UAX 29 word boundary mode
|
||||
MkUnicode() // Unicode matching
|
||||
MkUnicode() or // Unicode matching
|
||||
MkAnchoredStart() // match must begin at start of string
|
||||
|
||||
/**
|
||||
* A regular expression parse mode flag.
|
||||
@@ -142,6 +148,8 @@ class RegexParseMode extends TRegexParseMode {
|
||||
this = MkUnicodeBoundary() and result = "UNICODEBOUNDARY"
|
||||
or
|
||||
this = MkUnicode() and result = "UNICODE"
|
||||
or
|
||||
this = MkAnchoredStart() and result = "ANCHOREDSTART"
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,9 +215,9 @@ class RegexRegexAdditionalFlowStep extends RegexAdditionalFlowStep {
|
||||
}
|
||||
|
||||
/**
|
||||
* An additional flow step for `NSRegularExpression`.
|
||||
* An additional flow step for `NSRegularExpression.Options`.
|
||||
*/
|
||||
class NSRegularExpressionRegexAdditionalFlowStep extends RegexAdditionalFlowStep {
|
||||
private class NSRegularExpressionRegexAdditionalFlowStep extends RegexAdditionalFlowStep {
|
||||
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { none() }
|
||||
|
||||
override predicate setsParseMode(DataFlow::Node node, RegexParseMode mode, boolean isSet) {
|
||||
@@ -257,6 +265,34 @@ class NSRegularExpressionRegexAdditionalFlowStep extends RegexAdditionalFlowStep
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An additional flow step for `NSString.CompareOptions`.
|
||||
*/
|
||||
private class NSStringRegexAdditionalFlowStep extends RegexAdditionalFlowStep {
|
||||
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { none() }
|
||||
|
||||
override predicate setsParseMode(DataFlow::Node node, RegexParseMode mode, boolean isSet) {
|
||||
// `NSString.CompareOptions` values (these are typically combined with
|
||||
// `NSString.CompareOptions.regularExpression`, then passed into a `StringProtocol`
|
||||
// or `NSString` method).
|
||||
node.asExpr()
|
||||
.(MemberRefExpr)
|
||||
.getMember()
|
||||
.(FieldDecl)
|
||||
.hasQualifiedName("NSString.CompareOptions", "caseInsensitive") and
|
||||
mode = MkIgnoreCase() and
|
||||
isSet = true
|
||||
or
|
||||
node.asExpr()
|
||||
.(MemberRefExpr)
|
||||
.getMember()
|
||||
.(FieldDecl)
|
||||
.hasQualifiedName("NSString.CompareOptions", "anchored") and
|
||||
mode = MkAnchoredStart() and
|
||||
isSet = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call that evaluates a regular expression. For example, the call to `firstMatch` in:
|
||||
* ```
|
||||
@@ -265,27 +301,46 @@ class NSRegularExpressionRegexAdditionalFlowStep extends RegexAdditionalFlowStep
|
||||
*/
|
||||
abstract class RegexEval extends CallExpr {
|
||||
/**
|
||||
* Gets the input to this call that is the regular expression being evaluated. This may
|
||||
* be a regular expression object or a string literal.
|
||||
* Gets the input to this call that is the regular expression being evaluated.
|
||||
* This may be a regular expression object or a string literal.
|
||||
*
|
||||
* Consider using `getARegex()` instead (which tracks the regular expression
|
||||
* input back to its source).
|
||||
*/
|
||||
abstract Expr getRegexInput();
|
||||
abstract DataFlow::Node getRegexInputNode();
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `getRegexInputNode()` instead.
|
||||
*/
|
||||
deprecated Expr getRegexInput() { result = this.getRegexInputNode().asExpr() }
|
||||
|
||||
/**
|
||||
* Gets the input to this call that is the string the regular expression is evaluated on.
|
||||
*/
|
||||
abstract Expr getStringInput();
|
||||
abstract DataFlow::Node getStringInputNode();
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `getStringInputNode()` instead.
|
||||
*/
|
||||
deprecated Expr getStringInput() { result = this.getStringInputNode().asExpr() }
|
||||
|
||||
/**
|
||||
* Gets a dataflow node for an options input that might contain options such
|
||||
* as parse mode flags (if any).
|
||||
*/
|
||||
DataFlow::Node getAnOptionsInput() { none() }
|
||||
|
||||
/**
|
||||
* Gets a regular expression value that is evaluated here (if any can be identified).
|
||||
*/
|
||||
RegExp getARegex() {
|
||||
// string literal used directly as a regex
|
||||
DataFlow::exprNode(result).(ParsedStringRegex).getAParse().asExpr() = this.getRegexInput()
|
||||
DataFlow::exprNode(result).(ParsedStringRegex).getAParse() = this.getRegexInputNode()
|
||||
or
|
||||
// string literal -> regex object -> use
|
||||
exists(RegexCreation regexCreation |
|
||||
DataFlow::exprNode(result).(ParsedStringRegex).getAParse() = regexCreation.getStringInput() and
|
||||
RegexUseFlow::flow(regexCreation, DataFlow::exprNode(this.getRegexInput()))
|
||||
RegexUseFlow::flow(regexCreation, this.getRegexInputNode())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -298,7 +353,10 @@ abstract class RegexEval extends CallExpr {
|
||||
// parse mode flag is set
|
||||
any(RegexAdditionalFlowStep s).setsParseMode(setNode, result, true) and
|
||||
// reaches this eval
|
||||
RegexParseModeFlow::flow(setNode, DataFlow::exprNode(this.getRegexInput()))
|
||||
(
|
||||
RegexParseModeFlow::flow(setNode, this.getRegexInputNode()) or
|
||||
RegexParseModeFlow::flow(setNode, this.getAnOptionsInput())
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -307,15 +365,15 @@ abstract class RegexEval extends CallExpr {
|
||||
* A call to a function that always evaluates a regular expression.
|
||||
*/
|
||||
private class AlwaysRegexEval extends RegexEval {
|
||||
Expr regexInput;
|
||||
Expr stringInput;
|
||||
DataFlow::Node regexInput;
|
||||
DataFlow::Node stringInput;
|
||||
|
||||
AlwaysRegexEval() {
|
||||
this.getStaticTarget()
|
||||
.(Method)
|
||||
.hasQualifiedName("Regex", ["firstMatch(in:)", "prefixMatch(in:)", "wholeMatch(in:)"]) and
|
||||
regexInput = this.getQualifier() and
|
||||
stringInput = this.getArgument(0).getExpr()
|
||||
regexInput.asExpr() = this.getQualifier() and
|
||||
stringInput.asExpr() = this.getArgument(0).getExpr()
|
||||
or
|
||||
this.getStaticTarget()
|
||||
.(Method)
|
||||
@@ -327,8 +385,8 @@ private class AlwaysRegexEval extends RegexEval {
|
||||
"replaceMatches(in:options:range:withTemplate:)",
|
||||
"stringByReplacingMatches(in:options:range:withTemplate:)"
|
||||
]) and
|
||||
regexInput = this.getQualifier() and
|
||||
stringInput = this.getArgument(0).getExpr()
|
||||
regexInput.asExpr() = this.getQualifier() and
|
||||
stringInput.asExpr() = this.getArgument(0).getExpr()
|
||||
or
|
||||
this.getStaticTarget()
|
||||
.(Method)
|
||||
@@ -339,8 +397,8 @@ private class AlwaysRegexEval extends RegexEval {
|
||||
"split(separator:maxSplits:omittingEmptySubsequences:)", "starts(with:)",
|
||||
"trimmingPrefix(_:)", "wholeMatch(of:)"
|
||||
]) and
|
||||
regexInput = this.getArgument(0).getExpr() and
|
||||
stringInput = this.getQualifier()
|
||||
regexInput.asExpr() = this.getArgument(0).getExpr() and
|
||||
stringInput.asExpr() = this.getQualifier()
|
||||
or
|
||||
this.getStaticTarget()
|
||||
.(Method)
|
||||
@@ -351,11 +409,103 @@ private class AlwaysRegexEval extends RegexEval {
|
||||
"replacing(_:with:maxReplacements:)", "replacing(_:with:subrange:maxReplacements:)",
|
||||
"trimPrefix(_:)"
|
||||
]) and
|
||||
regexInput = this.getArgument(0).getExpr() and
|
||||
stringInput = this.getQualifier()
|
||||
regexInput.asExpr() = this.getArgument(0).getExpr() and
|
||||
stringInput.asExpr() = this.getQualifier()
|
||||
}
|
||||
|
||||
override Expr getRegexInput() { result = regexInput }
|
||||
override DataFlow::Node getRegexInputNode() { result = regexInput }
|
||||
|
||||
override Expr getStringInput() { result = stringInput }
|
||||
override DataFlow::Node getStringInputNode() { result = stringInput }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a function that sometimes evaluates a regular expression, if
|
||||
* `NSString.CompareOptions.regularExpression` is set as an `options` argument.
|
||||
*
|
||||
* This is a helper class for `NSStringCompareOptionsRegexEval`.
|
||||
*/
|
||||
private class NSStringCompareOptionsPotentialRegexEval extends CallExpr {
|
||||
DataFlow::Node regexInput;
|
||||
DataFlow::Node stringInput;
|
||||
DataFlow::Node optionsInput;
|
||||
|
||||
NSStringCompareOptionsPotentialRegexEval() {
|
||||
(
|
||||
this.getStaticTarget()
|
||||
.(Method)
|
||||
.hasQualifiedName("StringProtocol",
|
||||
["range(of:options:range:locale:)", "replacingOccurrences(of:with:options:range:)"])
|
||||
or
|
||||
this.getStaticTarget()
|
||||
.(Method)
|
||||
.hasQualifiedName("NSString",
|
||||
[
|
||||
"range(of:options:)", "range(of:options:range:)", "range(of:options:range:locale:)",
|
||||
"replacingOccurrences(of:with:options:range:)"
|
||||
])
|
||||
) and
|
||||
regexInput.asExpr() = this.getArgument(0).getExpr() and
|
||||
stringInput.asExpr() = this.getQualifier() and
|
||||
optionsInput.asExpr() = this.getArgumentWithLabel("options").getExpr()
|
||||
}
|
||||
|
||||
DataFlow::Node getRegexInput() { result = regexInput }
|
||||
|
||||
DataFlow::Node getStringInput() { result = stringInput }
|
||||
|
||||
DataFlow::Node getAnOptionsInput() { result = optionsInput }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow configuration for tracking `NSString.CompareOptions.regularExpression`
|
||||
* values from where they are created to the point of use.
|
||||
*/
|
||||
private module NSStringCompareOptionsFlagConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node node) {
|
||||
// creation of a `NSString.CompareOptions.regularExpression` value
|
||||
node.asExpr()
|
||||
.(MemberRefExpr)
|
||||
.getMember()
|
||||
.(FieldDecl)
|
||||
.hasQualifiedName("NSString.CompareOptions", "regularExpression")
|
||||
}
|
||||
|
||||
predicate isSink(DataFlow::Node node) {
|
||||
// use in a [potential] regex eval `options` argument
|
||||
any(NSStringCompareOptionsPotentialRegexEval potentialEval).getAnOptionsInput() = node
|
||||
}
|
||||
|
||||
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {
|
||||
// flow out from collection content at the sink.
|
||||
isSink(node) and
|
||||
c.getAReadContent() instanceof DataFlow::Content::CollectionContent
|
||||
}
|
||||
}
|
||||
|
||||
module NSStringCompareOptionsFlagFlow = DataFlow::Global<NSStringCompareOptionsFlagConfig>;
|
||||
|
||||
/**
|
||||
* A call to a function that evaluates a regular expression because
|
||||
* `NSString.CompareOptions.regularExpression` is set as an `options` argument.
|
||||
*/
|
||||
private class NSStringCompareOptionsRegexEval extends RegexEval instanceof NSStringCompareOptionsPotentialRegexEval
|
||||
{
|
||||
NSStringCompareOptionsRegexEval() {
|
||||
// check there is flow from a `NSString.CompareOptions.regularExpression` value to an `options` argument;
|
||||
// if there isn't, the input won't be interpretted as a regular expression.
|
||||
NSStringCompareOptionsFlagFlow::flow(_,
|
||||
this.(NSStringCompareOptionsPotentialRegexEval).getAnOptionsInput())
|
||||
}
|
||||
|
||||
override DataFlow::Node getRegexInputNode() {
|
||||
result = this.(NSStringCompareOptionsPotentialRegexEval).getRegexInput()
|
||||
}
|
||||
|
||||
override DataFlow::Node getStringInputNode() {
|
||||
result = this.(NSStringCompareOptionsPotentialRegexEval).getStringInput()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnOptionsInput() {
|
||||
result = this.(NSStringCompareOptionsPotentialRegexEval).getAnOptionsInput()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ private module StringLiteralUseConfig implements DataFlow::ConfigSig {
|
||||
|
||||
predicate isSink(DataFlow::Node node) {
|
||||
// evaluated directly as a regular expression
|
||||
node.asExpr() = any(RegexEval eval).getRegexInput()
|
||||
node = any(RegexEval eval).getRegexInputNode()
|
||||
or
|
||||
// used to create a regular expression object
|
||||
node = any(RegexCreation regexCreation).getStringInput()
|
||||
@@ -41,7 +41,7 @@ private module RegexUseConfig implements DataFlow::ConfigSig {
|
||||
|
||||
predicate isSink(DataFlow::Node node) {
|
||||
// evaluation of the regex
|
||||
node.asExpr() = any(RegexEval eval).getRegexInput()
|
||||
node = any(RegexEval eval).getRegexInputNode()
|
||||
}
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
@@ -65,8 +65,11 @@ private module RegexParseModeConfig implements DataFlow::StateConfigSig {
|
||||
}
|
||||
|
||||
predicate isSink(DataFlow::Node node, FlowState flowstate) {
|
||||
// evaluation of the regex
|
||||
node.asExpr() = any(RegexEval eval).getRegexInput() and
|
||||
// evaluation of a regex
|
||||
(
|
||||
node = any(RegexEval eval).getRegexInputNode() or
|
||||
node = any(RegexEval eval).getAnOptionsInput()
|
||||
) and
|
||||
exists(flowstate)
|
||||
}
|
||||
|
||||
@@ -86,7 +89,7 @@ private module RegexParseModeConfig implements DataFlow::StateConfigSig {
|
||||
or
|
||||
// flow through regex creation
|
||||
exists(RegexCreation create |
|
||||
nodeFrom = create.getOptionsInput() and
|
||||
nodeFrom = create.getAnOptionsInput() and
|
||||
nodeTo = create
|
||||
)
|
||||
or
|
||||
|
||||
@@ -37,7 +37,7 @@ class RegexInjectionAdditionalFlowStep extends Unit {
|
||||
* These cases are modeled separately.
|
||||
*/
|
||||
private class EvalRegexInjectionSink extends RegexInjectionSink {
|
||||
EvalRegexInjectionSink() { this.asExpr() = any(RegexEval e).getRegexInput() }
|
||||
EvalRegexInjectionSink() { this = any(RegexEval e).getRegexInputNode() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6324,14 +6324,24 @@ redos_variants.swift:
|
||||
# 579| [RegExpConstant, RegExpNormalChar] c
|
||||
|
||||
regex.swift:
|
||||
# 111| [RegExpDot] .
|
||||
# 112| [RegExpDot] .
|
||||
|
||||
# 111| [RegExpStar] .*
|
||||
# 112| [RegExpStar] .*
|
||||
#-----| 0 -> [RegExpDot] .
|
||||
|
||||
# 133| [RegExpDot] .
|
||||
# 129| [RegExpDot] .
|
||||
|
||||
# 133| [RegExpStar] .*
|
||||
# 129| [RegExpStar] .*
|
||||
#-----| 0 -> [RegExpDot] .
|
||||
|
||||
# 131| [RegExpDot] .
|
||||
|
||||
# 131| [RegExpStar] .*
|
||||
#-----| 0 -> [RegExpDot] .
|
||||
|
||||
# 136| [RegExpDot] .
|
||||
|
||||
# 136| [RegExpStar] .*
|
||||
#-----| 0 -> [RegExpDot] .
|
||||
|
||||
# 150| [RegExpDot] .
|
||||
@@ -6339,252 +6349,302 @@ regex.swift:
|
||||
# 150| [RegExpStar] .*
|
||||
#-----| 0 -> [RegExpDot] .
|
||||
|
||||
# 150| [RegExpDot] .
|
||||
# 151| [RegExpDot] .
|
||||
|
||||
# 150| [RegExpPlus] .+
|
||||
# 151| [RegExpStar] .*
|
||||
#-----| 0 -> [RegExpDot] .
|
||||
|
||||
# 157| [RegExpGroup] ([\w.]+)
|
||||
# 152| [RegExpDot] .
|
||||
|
||||
# 152| [RegExpStar] .*
|
||||
#-----| 0 -> [RegExpDot] .
|
||||
|
||||
# 153| [RegExpDot] .
|
||||
|
||||
# 153| [RegExpStar] .*
|
||||
#-----| 0 -> [RegExpDot] .
|
||||
|
||||
# 155| [RegExpDot] .
|
||||
|
||||
# 155| [RegExpStar] .*
|
||||
#-----| 0 -> [RegExpDot] .
|
||||
|
||||
# 160| [RegExpDot] .
|
||||
|
||||
# 160| [RegExpStar] .*
|
||||
#-----| 0 -> [RegExpDot] .
|
||||
|
||||
# 160| [RegExpDot] .
|
||||
|
||||
# 160| [RegExpPlus] .+
|
||||
#-----| 0 -> [RegExpDot] .
|
||||
|
||||
# 167| [RegExpGroup] ([\w.]+)
|
||||
#-----| 0 -> [RegExpPlus] [\w.]+
|
||||
|
||||
# 157| [RegExpStar] ([\w.]+)*
|
||||
# 167| [RegExpStar] ([\w.]+)*
|
||||
#-----| 0 -> [RegExpGroup] ([\w.]+)
|
||||
|
||||
# 157| [RegExpCharacterClass] [\w.]
|
||||
# 167| [RegExpCharacterClass] [\w.]
|
||||
#-----| 0 -> [RegExpCharacterClassEscape] \w
|
||||
#-----| 1 -> [RegExpConstant, RegExpNormalChar] .
|
||||
|
||||
# 157| [RegExpPlus] [\w.]+
|
||||
# 167| [RegExpPlus] [\w.]+
|
||||
#-----| 0 -> [RegExpCharacterClass] [\w.]
|
||||
|
||||
# 157| [RegExpCharacterClassEscape] \w
|
||||
# 167| [RegExpCharacterClassEscape] \w
|
||||
|
||||
# 157| [RegExpConstant, RegExpNormalChar] .
|
||||
# 167| [RegExpConstant, RegExpNormalChar] .
|
||||
|
||||
# 164| [RegExpConstant, RegExpNormalChar]
|
||||
# 164|
|
||||
# 174| [RegExpConstant, RegExpNormalChar]
|
||||
# 174|
|
||||
|
||||
# 165| [RegExpConstant, RegExpEscape] \n
|
||||
# 175| [RegExpConstant, RegExpEscape] \n
|
||||
|
||||
# 166| [RegExpConstant, RegExpEscape] \n
|
||||
# 176| [RegExpConstant, RegExpEscape] \n
|
||||
|
||||
# 176| [RegExpConstant, RegExpNormalChar] aa
|
||||
# 186| [RegExpConstant, RegExpNormalChar] aa
|
||||
|
||||
# 176| [RegExpAlt] aa|bb
|
||||
# 186| [RegExpAlt] aa|bb
|
||||
#-----| 0 -> [RegExpConstant, RegExpNormalChar] aa
|
||||
#-----| 1 -> [RegExpConstant, RegExpNormalChar] bb
|
||||
|
||||
# 176| [RegExpConstant, RegExpNormalChar] bb
|
||||
# 186| [RegExpConstant, RegExpNormalChar] bb
|
||||
|
||||
# 180| [RegExpConstant, RegExpNormalChar] aa
|
||||
# 190| [RegExpConstant, RegExpNormalChar] aa
|
||||
|
||||
# 180| [RegExpAlt] aa|
|
||||
# 180| bb
|
||||
# 190| [RegExpAlt] aa|
|
||||
# 190| bb
|
||||
#-----| 0 -> [RegExpConstant, RegExpNormalChar] aa
|
||||
#-----| 1 -> [RegExpConstant, RegExpNormalChar]
|
||||
#-----| bb
|
||||
|
||||
# 180| [RegExpConstant, RegExpNormalChar]
|
||||
# 180| bb
|
||||
# 190| [RegExpConstant, RegExpNormalChar]
|
||||
# 190| bb
|
||||
|
||||
# 188| [RegExpCharacterClass] [a-z]
|
||||
# 198| [RegExpCharacterClass] [a-z]
|
||||
#-----| 0 -> [RegExpCharacterRange] a-z
|
||||
|
||||
# 188| [RegExpConstant, RegExpNormalChar] a
|
||||
# 198| [RegExpConstant, RegExpNormalChar] a
|
||||
|
||||
# 188| [RegExpCharacterRange] a-z
|
||||
# 198| [RegExpCharacterRange] a-z
|
||||
#-----| 0 -> [RegExpConstant, RegExpNormalChar] a
|
||||
#-----| 1 -> [RegExpConstant, RegExpNormalChar] z
|
||||
|
||||
# 188| [RegExpConstant, RegExpNormalChar] z
|
||||
# 198| [RegExpConstant, RegExpNormalChar] z
|
||||
|
||||
# 189| [RegExpCharacterClass] [a-zA-Z]
|
||||
# 199| [RegExpCharacterClass] [a-zA-Z]
|
||||
#-----| 0 -> [RegExpCharacterRange] a-z
|
||||
#-----| 1 -> [RegExpCharacterRange] A-Z
|
||||
|
||||
# 189| [RegExpConstant, RegExpNormalChar] a
|
||||
# 199| [RegExpConstant, RegExpNormalChar] a
|
||||
|
||||
# 189| [RegExpCharacterRange] a-z
|
||||
# 199| [RegExpCharacterRange] a-z
|
||||
#-----| 0 -> [RegExpConstant, RegExpNormalChar] a
|
||||
#-----| 1 -> [RegExpConstant, RegExpNormalChar] z
|
||||
|
||||
# 189| [RegExpConstant, RegExpNormalChar] z
|
||||
# 199| [RegExpConstant, RegExpNormalChar] z
|
||||
|
||||
# 189| [RegExpConstant, RegExpNormalChar] A
|
||||
# 199| [RegExpConstant, RegExpNormalChar] A
|
||||
|
||||
# 189| [RegExpCharacterRange] A-Z
|
||||
# 199| [RegExpCharacterRange] A-Z
|
||||
#-----| 0 -> [RegExpConstant, RegExpNormalChar] A
|
||||
#-----| 1 -> [RegExpConstant, RegExpNormalChar] Z
|
||||
|
||||
# 189| [RegExpConstant, RegExpNormalChar] Z
|
||||
# 199| [RegExpConstant, RegExpNormalChar] Z
|
||||
|
||||
# 192| [RegExpCharacterClass] [a-]
|
||||
# 202| [RegExpCharacterClass] [a-]
|
||||
#-----| 0 -> [RegExpConstant, RegExpNormalChar] a
|
||||
#-----| 1 -> [RegExpConstant, RegExpNormalChar] -
|
||||
|
||||
# 192| [RegExpConstant, RegExpNormalChar] a
|
||||
# 202| [RegExpConstant, RegExpNormalChar] a
|
||||
|
||||
# 192| [RegExpConstant, RegExpNormalChar] -
|
||||
# 202| [RegExpConstant, RegExpNormalChar] -
|
||||
|
||||
# 193| [RegExpCharacterClass] [-a]
|
||||
# 203| [RegExpCharacterClass] [-a]
|
||||
#-----| 0 -> [RegExpConstant, RegExpNormalChar] -
|
||||
#-----| 1 -> [RegExpConstant, RegExpNormalChar] a
|
||||
|
||||
# 193| [RegExpConstant, RegExpNormalChar] -
|
||||
# 203| [RegExpConstant, RegExpNormalChar] -
|
||||
|
||||
# 193| [RegExpConstant, RegExpNormalChar] a
|
||||
# 203| [RegExpConstant, RegExpNormalChar] a
|
||||
|
||||
# 194| [RegExpCharacterClass] [-]
|
||||
# 204| [RegExpCharacterClass] [-]
|
||||
#-----| 0 -> [RegExpConstant, RegExpNormalChar] -
|
||||
|
||||
# 194| [RegExpConstant, RegExpNormalChar] -
|
||||
# 204| [RegExpConstant, RegExpNormalChar] -
|
||||
|
||||
# 195| [RegExpCharacterClass] [*]
|
||||
# 205| [RegExpCharacterClass] [*]
|
||||
#-----| 0 -> [RegExpConstant, RegExpNormalChar] *
|
||||
|
||||
# 195| [RegExpConstant, RegExpNormalChar] *
|
||||
# 205| [RegExpConstant, RegExpNormalChar] *
|
||||
|
||||
# 196| [RegExpCharacterClass] [^a]
|
||||
# 206| [RegExpCharacterClass] [^a]
|
||||
#-----| 0 -> [RegExpConstant, RegExpNormalChar] a
|
||||
|
||||
# 196| [RegExpConstant, RegExpNormalChar] a
|
||||
# 206| [RegExpConstant, RegExpNormalChar] a
|
||||
|
||||
# 197| [RegExpCharacterClass] [a^]
|
||||
# 207| [RegExpCharacterClass] [a^]
|
||||
#-----| 0 -> [RegExpConstant, RegExpNormalChar] a
|
||||
#-----| 1 -> [RegExpConstant, RegExpNormalChar] ^
|
||||
|
||||
# 197| [RegExpConstant, RegExpNormalChar] a
|
||||
# 207| [RegExpConstant, RegExpNormalChar] a
|
||||
|
||||
# 197| [RegExpConstant, RegExpNormalChar] ^
|
||||
# 207| [RegExpConstant, RegExpNormalChar] ^
|
||||
|
||||
# 198| [RegExpCharacterClass] [\\]
|
||||
# 208| [RegExpCharacterClass] [\\]
|
||||
#-----| 0 -> [RegExpConstant, RegExpEscape] \\
|
||||
|
||||
# 198| [RegExpConstant, RegExpEscape] \\
|
||||
# 208| [RegExpConstant, RegExpEscape] \\
|
||||
|
||||
# 199| [RegExpCharacterClass] [\\\]]
|
||||
# 209| [RegExpCharacterClass] [\\\]]
|
||||
#-----| 0 -> [RegExpConstant, RegExpEscape] \\
|
||||
#-----| 1 -> [RegExpConstant, RegExpEscape] \]
|
||||
|
||||
# 199| [RegExpConstant, RegExpEscape] \\
|
||||
# 209| [RegExpConstant, RegExpEscape] \\
|
||||
|
||||
# 199| [RegExpConstant, RegExpEscape] \]
|
||||
# 209| [RegExpConstant, RegExpEscape] \]
|
||||
|
||||
# 200| [RegExpCharacterClass] [:]
|
||||
# 210| [RegExpCharacterClass] [:]
|
||||
#-----| 0 -> [RegExpConstant, RegExpNormalChar] :
|
||||
|
||||
# 200| [RegExpConstant, RegExpNormalChar] :
|
||||
# 210| [RegExpConstant, RegExpNormalChar] :
|
||||
|
||||
# 201| [RegExpNamedCharacterProperty] [:digit:]
|
||||
# 211| [RegExpNamedCharacterProperty] [:digit:]
|
||||
|
||||
# 202| [RegExpNamedCharacterProperty] [:alnum:]
|
||||
# 212| [RegExpNamedCharacterProperty] [:alnum:]
|
||||
|
||||
# 205| [RegExpCharacterClass] []a]
|
||||
# 215| [RegExpCharacterClass] []a]
|
||||
#-----| 0 -> [RegExpConstant, RegExpNormalChar] ]
|
||||
#-----| 1 -> [RegExpConstant, RegExpNormalChar] a
|
||||
|
||||
# 205| [RegExpConstant, RegExpNormalChar] ]
|
||||
# 215| [RegExpConstant, RegExpNormalChar] ]
|
||||
|
||||
# 205| [RegExpConstant, RegExpNormalChar] a
|
||||
# 215| [RegExpConstant, RegExpNormalChar] a
|
||||
|
||||
# 206| [RegExpNamedCharacterProperty] [:aaaaa:]
|
||||
# 217| [RegExpNamedCharacterProperty] [:aaaaa:]
|
||||
|
||||
# 211| [RegExpZeroWidthMatch] (?i)
|
||||
# 222| [RegExpZeroWidthMatch] (?i)
|
||||
|
||||
# 211| [RegExpSequence] (?i)abc
|
||||
# 222| [RegExpSequence] (?i)abc
|
||||
#-----| 0 -> [RegExpZeroWidthMatch] (?i)
|
||||
#-----| 1 -> [RegExpConstant, RegExpNormalChar] abc
|
||||
|
||||
# 211| [RegExpConstant, RegExpNormalChar] abc
|
||||
# 222| [RegExpConstant, RegExpNormalChar] abc
|
||||
|
||||
# 212| [RegExpZeroWidthMatch] (?s)
|
||||
# 223| [RegExpZeroWidthMatch] (?s)
|
||||
|
||||
# 212| [RegExpSequence] (?s)abc
|
||||
# 223| [RegExpSequence] (?s)abc
|
||||
#-----| 0 -> [RegExpZeroWidthMatch] (?s)
|
||||
#-----| 1 -> [RegExpConstant, RegExpNormalChar] abc
|
||||
|
||||
# 212| [RegExpConstant, RegExpNormalChar] abc
|
||||
# 223| [RegExpConstant, RegExpNormalChar] abc
|
||||
|
||||
# 213| [RegExpZeroWidthMatch] (?is)
|
||||
# 224| [RegExpZeroWidthMatch] (?is)
|
||||
|
||||
# 213| [RegExpSequence] (?is)abc
|
||||
# 224| [RegExpSequence] (?is)abc
|
||||
#-----| 0 -> [RegExpZeroWidthMatch] (?is)
|
||||
#-----| 1 -> [RegExpConstant, RegExpNormalChar] abc
|
||||
|
||||
# 213| [RegExpConstant, RegExpNormalChar] abc
|
||||
# 224| [RegExpConstant, RegExpNormalChar] abc
|
||||
|
||||
# 214| [RegExpGroup] (?i-s)
|
||||
# 225| [RegExpGroup] (?i-s)
|
||||
#-----| 0 -> [RegExpConstant, RegExpNormalChar] -s
|
||||
|
||||
# 214| [RegExpSequence] (?i-s)abc
|
||||
# 225| [RegExpSequence] (?i-s)abc
|
||||
#-----| 0 -> [RegExpGroup] (?i-s)
|
||||
#-----| 1 -> [RegExpConstant, RegExpNormalChar] abc
|
||||
|
||||
# 214| [RegExpConstant, RegExpNormalChar] -s
|
||||
# 225| [RegExpConstant, RegExpNormalChar] -s
|
||||
|
||||
# 214| [RegExpConstant, RegExpNormalChar] abc
|
||||
# 225| [RegExpConstant, RegExpNormalChar] abc
|
||||
|
||||
# 217| [RegExpConstant, RegExpNormalChar] abc
|
||||
# 228| [RegExpConstant, RegExpNormalChar] abc
|
||||
|
||||
# 217| [RegExpSequence] abc(?i)def
|
||||
# 228| [RegExpSequence] abc(?i)def
|
||||
#-----| 0 -> [RegExpConstant, RegExpNormalChar] abc
|
||||
#-----| 1 -> [RegExpZeroWidthMatch] (?i)
|
||||
#-----| 2 -> [RegExpConstant, RegExpNormalChar] def
|
||||
|
||||
# 217| [RegExpZeroWidthMatch] (?i)
|
||||
# 228| [RegExpZeroWidthMatch] (?i)
|
||||
|
||||
# 217| [RegExpConstant, RegExpNormalChar] def
|
||||
# 228| [RegExpConstant, RegExpNormalChar] def
|
||||
|
||||
# 218| [RegExpConstant, RegExpNormalChar] abc
|
||||
# 229| [RegExpConstant, RegExpNormalChar] abc
|
||||
|
||||
# 218| [RegExpSequence] abc(?i:def)ghi
|
||||
# 229| [RegExpSequence] abc(?i:def)ghi
|
||||
#-----| 0 -> [RegExpConstant, RegExpNormalChar] abc
|
||||
#-----| 1 -> [RegExpGroup] (?i:def)
|
||||
#-----| 2 -> [RegExpConstant, RegExpNormalChar] ghi
|
||||
|
||||
# 218| [RegExpGroup] (?i:def)
|
||||
# 229| [RegExpGroup] (?i:def)
|
||||
#-----| 0 -> [RegExpConstant, RegExpNormalChar] :def
|
||||
|
||||
# 218| [RegExpConstant, RegExpNormalChar] :def
|
||||
# 229| [RegExpConstant, RegExpNormalChar] :def
|
||||
|
||||
# 218| [RegExpConstant, RegExpNormalChar] ghi
|
||||
# 229| [RegExpConstant, RegExpNormalChar] ghi
|
||||
|
||||
# 219| [RegExpZeroWidthMatch] (?i)
|
||||
# 230| [RegExpZeroWidthMatch] (?i)
|
||||
|
||||
# 219| [RegExpConstant, RegExpNormalChar] abc
|
||||
# 230| [RegExpConstant, RegExpNormalChar] abc
|
||||
|
||||
# 219| [RegExpConstant, RegExpNormalChar] -i
|
||||
# 230| [RegExpConstant, RegExpNormalChar] -i
|
||||
|
||||
# 219| [RegExpConstant, RegExpNormalChar] def
|
||||
# 230| [RegExpConstant, RegExpNormalChar] def
|
||||
|
||||
# 222| [RegExpConstant, RegExpNormalChar] abc
|
||||
# 233| [RegExpConstant, RegExpNormalChar] abc
|
||||
|
||||
# 223| [RegExpConstant, RegExpNormalChar] abc
|
||||
# 234| [RegExpConstant, RegExpNormalChar] abc
|
||||
|
||||
# 224| [RegExpConstant, RegExpNormalChar] abc
|
||||
# 235| [RegExpConstant, RegExpNormalChar] abc
|
||||
|
||||
# 225| [RegExpConstant, RegExpNormalChar] abc
|
||||
# 236| [RegExpConstant, RegExpNormalChar] abc
|
||||
|
||||
# 226| [RegExpConstant, RegExpNormalChar] abc
|
||||
# 237| [RegExpConstant, RegExpNormalChar] abc
|
||||
|
||||
# 227| [RegExpConstant, RegExpNormalChar] abc
|
||||
# 238| [RegExpConstant, RegExpNormalChar] abc
|
||||
|
||||
# 230| [RegExpDot] .
|
||||
# 241| [RegExpDot] .
|
||||
|
||||
# 230| [RegExpStar] .*
|
||||
# 241| [RegExpStar] .*
|
||||
#-----| 0 -> [RegExpDot] .
|
||||
|
||||
# 231| [RegExpDot] .
|
||||
# 242| [RegExpDot] .
|
||||
|
||||
# 231| [RegExpStar] .*
|
||||
# 242| [RegExpStar] .*
|
||||
#-----| 0 -> [RegExpDot] .
|
||||
|
||||
# 232| [RegExpDot] .
|
||||
# 243| [RegExpDot] .
|
||||
|
||||
# 232| [RegExpStar] .*
|
||||
# 243| [RegExpStar] .*
|
||||
#-----| 0 -> [RegExpDot] .
|
||||
|
||||
# 235| [RegExpDot] .
|
||||
# 246| [RegExpDot] .
|
||||
|
||||
# 235| [RegExpStar] .*
|
||||
# 246| [RegExpStar] .*
|
||||
#-----| 0 -> [RegExpDot] .
|
||||
|
||||
# 251| [RegExpDot] .
|
||||
|
||||
# 251| [RegExpStar] .*
|
||||
#-----| 0 -> [RegExpDot] .
|
||||
|
||||
# 252| [RegExpDot] .
|
||||
|
||||
# 252| [RegExpStar] .*
|
||||
#-----| 0 -> [RegExpDot] .
|
||||
|
||||
# 253| [RegExpDot] .
|
||||
|
||||
# 253| [RegExpStar] .*
|
||||
#-----| 0 -> [RegExpDot] .
|
||||
|
||||
# 254| [RegExpDot] .
|
||||
|
||||
# 254| [RegExpStar] .*
|
||||
#-----| 0 -> [RegExpDot] .
|
||||
|
||||
# 257| [RegExpDot] .
|
||||
|
||||
# 257| [RegExpStar] .*
|
||||
#-----| 0 -> [RegExpDot] .
|
||||
|
||||
@@ -46,16 +46,16 @@ func myRegexpVariantsTests(myUrl: URL) throws {
|
||||
_ = try Regex("a*b").firstMatch(in: tainted) // $ regex=a*b input=tainted
|
||||
_ = try Regex("(a*)b").firstMatch(in: tainted) // $ regex=(a*)b input=tainted
|
||||
_ = try Regex("(a)*b").firstMatch(in: tainted) // $ regex=(a)*b input=tainted
|
||||
_ = try Regex("(a*)*b").firstMatch(in: tainted) // $ regex=(a*)*b input=tainted redos-vulnerable=
|
||||
_ = try Regex("((a*)*b)").firstMatch(in: tainted) // $ regex=((a*)*b) input=tainted redos-vulnerable=
|
||||
_ = try Regex("(a*)*b").firstMatch(in: tainted) // $ regex=(a*)*b input=tainted redos-vulnerable
|
||||
_ = try Regex("((a*)*b)").firstMatch(in: tainted) // $ regex=((a*)*b) input=tainted redos-vulnerable
|
||||
|
||||
_ = try Regex("(a|aa?)b").firstMatch(in: tainted) // $ regex=(a|aa?)b input=tainted
|
||||
_ = try Regex("(a|aa?)*b").firstMatch(in: tainted) // $ regex=(a|aa?)*b input=tainted redos-vulnerable=
|
||||
_ = try Regex("(a|aa?)*b").firstMatch(in: tainted) // $ regex=(a|aa?)*b input=tainted redos-vulnerable
|
||||
|
||||
// from the qhelp:
|
||||
// attack string: "_" x lots + "!"
|
||||
|
||||
_ = try Regex("^_(__|.)+_$").firstMatch(in: tainted) // $ regex=^_(__|.)+_$ input=tainted redos-vulnerable=
|
||||
_ = try Regex("^_(__|.)+_$").firstMatch(in: tainted) // $ regex=^_(__|.)+_$ input=tainted redos-vulnerable
|
||||
_ = try Regex("^_(__|[^_])+_$").firstMatch(in: tainted) // $ regex=^_(__|[^_])+_$ input=tainted
|
||||
|
||||
// real world cases:
|
||||
@@ -63,518 +63,518 @@ func myRegexpVariantsTests(myUrl: URL) throws {
|
||||
// Adapted from marked (https://github.com/markedjs/marked), which is licensed
|
||||
// under the MIT license; see file licenses/marked-LICENSE.
|
||||
// GOOD
|
||||
_ = try Regex(#"^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)"#).firstMatch(in: tainted) // $ SPURIOUS: redos-vulnerable=
|
||||
_ = try Regex(#"^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)"#).firstMatch(in: tainted) // $ regex=^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*) SPURIOUS: redos-vulnerable
|
||||
// BAD
|
||||
// attack string: "_" + "__".repeat(100)
|
||||
_ = try Regex(#"^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)"#).wholeMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)"#).wholeMatch(in: tainted) // $ redos-vulnerable regex=^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)
|
||||
|
||||
// GOOD
|
||||
// Adapted from marked (https://github.com/markedjs/marked), which is licensed
|
||||
// under the MIT license; see file licenses/marked-LICENSE.
|
||||
_ = try Regex(#"^\b_((?:__|[^_])+?)_\b|^\*((?:\*\*|[^*])+?)\*(?!\*)"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"^\b_((?:__|[^_])+?)_\b|^\*((?:\*\*|[^*])+?)\*(?!\*)"#).firstMatch(in: tainted) // $ regex=^\b_((?:__|[^_])+?)_\b|^\*((?:\*\*|[^*])+?)\*(?!\*)
|
||||
|
||||
// GOOD - there is no witness in the end that could cause the regexp to not match
|
||||
// Adapted from brace-expansion (https://github.com/juliangruber/brace-expansion),
|
||||
// which is licensed under the MIT license; see file licenses/brace-expansion-LICENSE.
|
||||
_ = try Regex("(.*,)+.+").firstMatch(in: tainted)
|
||||
_ = try Regex("(.*,)+.+").firstMatch(in: tainted) // $ regex=(.*,)+.+
|
||||
|
||||
// BAD
|
||||
// attack string: " '" + "\\\\".repeat(100)
|
||||
// Adapted from CodeMirror (https://github.com/codemirror/codemirror),
|
||||
// which is licensed under the MIT license; see file licenses/CodeMirror-LICENSE.
|
||||
_ = try Regex(#"^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?"#).firstMatch(in: tainted) // $ redos-vulnerable regex=^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?
|
||||
|
||||
// GOOD
|
||||
// Adapted from jest (https://github.com/facebook/jest), which is licensed
|
||||
// under the MIT license; see file licenses/jest-LICENSE.
|
||||
_ = try Regex(#"^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*"#).firstMatch(in: tainted) // $ regex="^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*"
|
||||
|
||||
// BAD
|
||||
// attack string: "/" + "\\/a".repeat(100)
|
||||
// Adapted from ANodeBlog (https://github.com/gefangshuai/ANodeBlog),
|
||||
// which is licensed under the Apache License 2.0; see file licenses/ANodeBlog-LICENSE.
|
||||
_ = try Regex(#"\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)"#).firstMatch(in: tainted) // $ redos-vulnerable regex="\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)"
|
||||
|
||||
// BAD
|
||||
// attack string: "##".repeat(100) + "\na"
|
||||
// Adapted from CodeMirror (https://github.com/codemirror/codemirror),
|
||||
// which is licensed under the MIT license; see file licenses/CodeMirror-LICENSE.
|
||||
_ = try Regex(#"^([\s\[\{\(]|#.*)*$"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"^([\s\[\{\(]|#.*)*$"#).firstMatch(in: tainted) // $ redos-vulnerable regex=^([\s\[\{\(]|#.*)*$
|
||||
|
||||
// BAD
|
||||
// attack string: "a" + "[]".repeat(100) + ".b\n"
|
||||
// Adapted from Knockout (https://github.com/knockout/knockout), which is
|
||||
// licensed under the MIT license; see file licenses/knockout-LICENSE
|
||||
_ = try Regex(#"^[\_$a-z][\_$a-z0-9]*(\[.*?\])*(\.[\_$a-z][\_$a-z0-9]*(\[.*?\])*)*$"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"^[\_$a-z][\_$a-z0-9]*(\[.*?\])*(\.[\_$a-z][\_$a-z0-9]*(\[.*?\])*)*$"#).firstMatch(in: tainted) // $ redos-vulnerable regex=^[\_$a-z][\_$a-z0-9]*(\[.*?\])*(\.[\_$a-z][\_$a-z0-9]*(\[.*?\])*)*$
|
||||
|
||||
// BAD
|
||||
// attack string: "[" + "][".repeat(100) + "]!"
|
||||
// Adapted from Prototype.js (https://github.com/prototypejs/prototype), which
|
||||
// is licensed under the MIT license; see file licenses/Prototype.js-LICENSE.
|
||||
_ = try Regex(#"(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)"#).firstMatch(in: tainted) // $ redos-vulnerable regex=(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)
|
||||
|
||||
// BAD
|
||||
// attack string: "'" + "\\a".repeat(100) + '"'
|
||||
// Adapted from Prism (https://github.com/PrismJS/prism), which is licensed
|
||||
// under the MIT license; see file licenses/Prism-LICENSE.
|
||||
_ = try Regex(#"("|')(\\?.)*?\1"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"("|')(\\?.)*?\1"#).firstMatch(in: tainted) // $ redos-vulnerable regex=("|')(\\?.)*?\1
|
||||
|
||||
// more cases:
|
||||
|
||||
// GOOD
|
||||
_ = try Regex(#"(\r\n|\r|\n)+"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"(\r\n|\r|\n)+"#).firstMatch(in: tainted) // $ regex=(\r\n|\r|\n)+
|
||||
|
||||
// GOOD
|
||||
_ = try Regex("(a|.)*").firstMatch(in: tainted)
|
||||
_ = try Regex("(a|.)*").firstMatch(in: tainted) // $ regex=(a|.)*
|
||||
|
||||
// BAD - testing the NFA
|
||||
// attack string: "a" x lots + "!"
|
||||
_ = try Regex("^([a-z]+)+$").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("^([a-z]*)*$").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"^([a-zA-Z0-9])(([\\.-]|[_]+)?([a-zA-Z0-9]+))*(@){1}[a-z0-9]+[.]{1}(([a-z]{2,3})|([a-z]{2,3}[.]{1}[a-z]{2,3}))$"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("^(([a-z])+.)+[A-Z]([a-z])+$").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("^([a-z]+)+$").firstMatch(in: tainted) // $ redos-vulnerable regex=^([a-z]+)+$
|
||||
_ = try Regex("^([a-z]*)*$").firstMatch(in: tainted) // $ redos-vulnerable regex=^([a-z]*)*$
|
||||
_ = try Regex(#"^([a-zA-Z0-9])(([\\.-]|[_]+)?([a-zA-Z0-9]+))*(@){1}[a-z0-9]+[.]{1}(([a-z]{2,3})|([a-z]{2,3}[.]{1}[a-z]{2,3}))$"#).firstMatch(in: tainted) // $ redos-vulnerable regex=^([a-zA-Z0-9])(([\\.-]|[_]+)?([a-zA-Z0-9]+))*(@){1}[a-z0-9]+[.]{1}(([a-z]{2,3})|([a-z]{2,3}[.]{1}[a-z]{2,3}))$
|
||||
_ = try Regex("^(([a-z])+.)+[A-Z]([a-z])+$").firstMatch(in: tainted) // $ redos-vulnerable regex=^(([a-z])+.)+[A-Z]([a-z])+$
|
||||
|
||||
// BAD
|
||||
// attack string: "b" x lots + "!"
|
||||
_ = try Regex("(b|a?b)*c").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("(b|a?b)*c").firstMatch(in: tainted) // $ redos-vulnerable regex=(b|a?b)*c
|
||||
|
||||
// GOOD
|
||||
_ = try Regex(#"(.|\n)*!"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"(.|\n)*!"#).firstMatch(in: tainted) // $ regex=(.|\n)*!
|
||||
|
||||
// BAD
|
||||
// attack string: "\n".repeat(100) + "."
|
||||
_ = try Regex(#"(?s)(.|\n)*!"#).firstMatch(in: tainted) // $ modes=DOTALL redos-vulnerable=
|
||||
_ = try Regex(#"(?s)(.|\n)*!"#).firstMatch(in: tainted) // $ modes=DOTALL redos-vulnerable regex=(?s)(.|\n)*!
|
||||
|
||||
// GOOD
|
||||
_ = try Regex(#"([\w.]+)*"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"([\w.]+)*"#).firstMatch(in: tainted) // $ regex=([\w.]+)*
|
||||
// BAD
|
||||
// attack string: "a" x lots + "!"
|
||||
_ = try Regex(#"([\w.]+)*"#).wholeMatch(in: tainted) // $ MISSING: redos-vulnerable=
|
||||
_ = try Regex(#"([\w.]+)*"#).wholeMatch(in: tainted) // $ regex=([\w.]+)* MISSING: redos-vulnerable
|
||||
|
||||
// BAD
|
||||
// attack string: "b" x lots + "!"
|
||||
_ = try Regex(#"(([\s\S]|[^a])*)""#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"(([\s\S]|[^a])*)""#).firstMatch(in: tainted) // $ redos-vulnerable regex=(([\s\S]|[^a])*)"
|
||||
|
||||
// GOOD - there is no witness in the end that could cause the regexp to not match
|
||||
_ = try Regex(#"([^"']+)*"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"([^"']+)*"#).firstMatch(in: tainted) // $ regex=([^"']+)*
|
||||
|
||||
// BAD
|
||||
// attack string: "b" x lots + "!"
|
||||
_ = try Regex(#"((.|[^a])*)""#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"((.|[^a])*)""#).firstMatch(in: tainted) // $ redos-vulnerable regex=((.|[^a])*)"
|
||||
|
||||
// GOOD
|
||||
_ = try Regex(#"((a|[^a])*)""#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"((a|[^a])*)""#).firstMatch(in: tainted) // $ regex=((a|[^a])*)"
|
||||
|
||||
// BAD
|
||||
// attack string: "b" x lots + "!"
|
||||
_ = try Regex(#"((b|[^a])*)""#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"((b|[^a])*)""#).firstMatch(in: tainted) // $ redos-vulnerable regex=((b|[^a])*)"
|
||||
|
||||
// BAD
|
||||
// attack string: "G" x lots + "!"
|
||||
_ = try Regex(#"((G|[^a])*)""#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"((G|[^a])*)""#).firstMatch(in: tainted) // $ redos-vulnerable regex=((G|[^a])*)"
|
||||
|
||||
// BAD
|
||||
// attack string: "0" x lots + "!"
|
||||
_ = try Regex(#"(([0-9]|[^a])*)""#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"(([0-9]|[^a])*)""#).firstMatch(in: tainted) // $ redos-vulnerable regex=(([0-9]|[^a])*)"
|
||||
|
||||
// BAD [NOT DETECTED]
|
||||
// (no confirmed attack string)
|
||||
_ = try Regex(#"(?:=(?:([!#\$%&'\*\+\-\.\^_`\|~0-9A-Za-z]+)|"((?:\\[\x00-\x7f]|[^\x00-\x08\x0a-\x1f\x7f"])*)"))?"#).firstMatch(in: tainted) // $ MISSING: redos-vulnerable=
|
||||
_ = try Regex(#"(?:=(?:([!#\$%&'\*\+\-\.\^_`\|~0-9A-Za-z]+)|"((?:\\[\x00-\x7f]|[^\x00-\x08\x0a-\x1f\x7f"])*)"))?"#).firstMatch(in: tainted) // $ regex=(?:=(?:([!#\$%&'\*\+\-\.\^_`\|~0-9A-Za-z]+)|"((?:\\[\x00-\x7f]|[^\x00-\x08\x0a-\x1f\x7f"])*)"))? MISSING: redos-vulnerable
|
||||
|
||||
// BAD [NOT DETECTED]
|
||||
// (no confirmed attack string)
|
||||
_ = try Regex(#""((?:\\[\x00-\x7f]|[^\x00-\x08\x0a-\x1f\x7f"])*)""#).firstMatch(in: tainted) // $ MISSING: redos-vulnerable=
|
||||
_ = try Regex(#""((?:\\[\x00-\x7f]|[^\x00-\x08\x0a-\x1f\x7f"])*)""#).firstMatch(in: tainted) // $ regex="((?:\\[\x00-\x7f]|[^\x00-\x08\x0a-\x1f\x7f"])*)" MISSING: redos-vulnerable
|
||||
|
||||
// GOOD
|
||||
_ = try Regex(#""((?:\\[\x00-\x7f]|[^\x00-\x08\x0a-\x1f\x7f"\\])*)""#).firstMatch(in: tainted)
|
||||
_ = try Regex(#""((?:\\[\x00-\x7f]|[^\x00-\x08\x0a-\x1f\x7f"\\])*)""#).firstMatch(in: tainted) // $ regex="((?:\\[\x00-\x7f]|[^\x00-\x08\x0a-\x1f\x7f"\\])*)"
|
||||
|
||||
// BAD
|
||||
// attack string: "d" x lots + "!"
|
||||
_ = try Regex(#"(([a-z]|[d-h])*)""#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"(([a-z]|[d-h])*)""#).firstMatch(in: tainted) // $ redos-vulnerable regex=(([a-z]|[d-h])*)"
|
||||
|
||||
// BAD
|
||||
// attack string: "_" x lots
|
||||
_ = try Regex(#"(([^a-z]|[^0-9])*)""#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"(([^a-z]|[^0-9])*)""#).firstMatch(in: tainted) // $ redos-vulnerable regex=(([^a-z]|[^0-9])*)"
|
||||
|
||||
// BAD
|
||||
// attack string: "0" x lots + "!"
|
||||
_ = try Regex(#"((\d|[0-9])*)""#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"((\d|[0-9])*)""#).firstMatch(in: tainted) // $ redos-vulnerable regex=((\d|[0-9])*)"
|
||||
|
||||
// BAD
|
||||
// attack string: "\n" x lots + "."
|
||||
_ = try Regex(#"((\s|\s)*)""#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"((\s|\s)*)""#).firstMatch(in: tainted) // $ redos-vulnerable regex=((\s|\s)*)"
|
||||
|
||||
// BAD
|
||||
// attack string: "G" x lots + "!"
|
||||
_ = try Regex(#"((\w|G)*)""#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"((\w|G)*)""#).firstMatch(in: tainted) // $ redos-vulnerable regex=((\w|G)*)"
|
||||
|
||||
// GOOD
|
||||
_ = try Regex(#"((\s|\d)*)""#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"((\s|\d)*)""#).firstMatch(in: tainted) // $ regex=((\s|\d)*)"
|
||||
|
||||
// BAD
|
||||
// attack string: "5" x lots + "!"
|
||||
_ = try Regex(#"((\d|\d)*)""#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"((\d|\d)*)""#).firstMatch(in: tainted) // $ redos-vulnerable regex=((\d|\d)*)"
|
||||
|
||||
// BAD
|
||||
// attack string: "0" x lots + "!"
|
||||
_ = try Regex(#"((\d|\w)*)""#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"((\d|\w)*)""#).firstMatch(in: tainted) // $ redos-vulnerable regex=((\d|\w)*)"
|
||||
|
||||
// BAD
|
||||
// attack string: "5" x lots + "!"
|
||||
_ = try Regex(#"((\d|5)*)""#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"((\d|5)*)""#).firstMatch(in: tainted) // $ redos-vulnerable regex=((\d|5)*)"
|
||||
|
||||
// BAD
|
||||
// attack string: "\u{000C}" x lots + "!",
|
||||
_ = try Regex(#"((\s|[\f])*)""#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"((\s|[\f])*)""#).firstMatch(in: tainted) // $ redos-vulnerable regex=((\s|[\f])*)"
|
||||
|
||||
// BAD
|
||||
// attack string: "\n" x lots + "."
|
||||
_ = try Regex(#"((\s|[\v]|\\v)*)""#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"((\s|[\v]|\\v)*)""#).firstMatch(in: tainted) // $ redos-vulnerable regex=((\s|[\v]|\\v)*)"
|
||||
|
||||
// BAD
|
||||
// attack string: "\u{000C}" x lots + "!",
|
||||
_ = try Regex(#"((\f|[\f])*)""#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"((\f|[\f])*)""#).firstMatch(in: tainted) // $ redos-vulnerable regex=((\f|[\f])*)"
|
||||
|
||||
// BAD
|
||||
// attack string: "\n" x lots + "."
|
||||
_ = try Regex(#"((\W|\D)*)""#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"((\W|\D)*)""#).firstMatch(in: tainted) // $ redos-vulnerable regex=((\W|\D)*)"
|
||||
|
||||
// BAD
|
||||
// attack string: "a" x lots + "!"
|
||||
_ = try Regex(#"((\S|\w)*)""#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"((\S|\w)*)""#).firstMatch(in: tainted) // $ redos-vulnerable regex=((\S|\w)*)"
|
||||
|
||||
// BAD
|
||||
// attack string: "a" x lots + "!"
|
||||
_ = try Regex(#"((\S|[\w])*)""#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"((\S|[\w])*)""#).firstMatch(in: tainted) // $ redos-vulnerable regex=((\S|[\w])*)"
|
||||
|
||||
// BAD
|
||||
// attack string: "1s" x lots + "!"
|
||||
_ = try Regex(#"((1s|[\da-z])*)""#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"((1s|[\da-z])*)""#).firstMatch(in: tainted) // $ redos-vulnerable regex=((1s|[\da-z])*)"
|
||||
|
||||
// BAD
|
||||
// attack string: "0" x lots + "!"
|
||||
_ = try Regex(#"((0|[\d])*)""#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"((0|[\d])*)""#).firstMatch(in: tainted) // $ redos-vulnerable regex=((0|[\d])*)"
|
||||
|
||||
// BAD
|
||||
// attack string: "0" x lots + "!"
|
||||
_ = try Regex(#"(([\d]+)*)""#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"(([\d]+)*)""#).firstMatch(in: tainted) // $ redos-vulnerable regex=(([\d]+)*)"
|
||||
|
||||
// GOOD - there is no witness in the end that could cause the regexp to not match
|
||||
_ = try Regex(#"(\d+(X\d+)?)+"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"(\d+(X\d+)?)+"#).firstMatch(in: tainted) // $ regex=(\d+(X\d+)?)+
|
||||
// BAD
|
||||
// attack string: "0" x lots + "!"
|
||||
_ = try Regex(#"(\d+(X\d+)?)+"#).wholeMatch(in: tainted) // $ MISSING: redos-vulnerable=
|
||||
_ = try Regex(#"(\d+(X\d+)?)+"#).wholeMatch(in: tainted) // $ regex=(\d+(X\d+)?)+ MISSING: redos-vulnerable
|
||||
|
||||
// GOOD - there is no witness in the end that could cause the regexp to not match
|
||||
_ = try Regex("([0-9]+(X[0-9]*)?)*").firstMatch(in: tainted)
|
||||
_ = try Regex("([0-9]+(X[0-9]*)?)*").firstMatch(in: tainted) // $ regex=([0-9]+(X[0-9]*)?)*
|
||||
// BAD
|
||||
// attack string: "0" x lots + "!"
|
||||
_ = try Regex("([0-9]+(X[0-9]*)?)*").wholeMatch(in: tainted) // $ MISSING: redos-vulnerable=
|
||||
_ = try Regex("([0-9]+(X[0-9]*)?)*").wholeMatch(in: tainted) // $ regex=([0-9]+(X[0-9]*)?)* MISSING: redos-vulnerable
|
||||
|
||||
// GOOD
|
||||
_ = try Regex("^([^>]+)*(>|$)").firstMatch(in: tainted)
|
||||
_ = try Regex("^([^>]+)*(>|$)").firstMatch(in: tainted) // $ regex=^([^>]+)*(>|$)
|
||||
|
||||
// BAD
|
||||
// attack string: "##".repeat(100) + "\na"
|
||||
_ = try Regex("^([^>a]+)*(>|$)").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("^([^>a]+)*(>|$)").firstMatch(in: tainted) // $ redos-vulnerable regex=^([^>a]+)*(>|$)
|
||||
|
||||
// BAD
|
||||
// attack string: "\n" x lots + "."
|
||||
_ = try Regex(#"(\n\s*)+$"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"(\n\s*)+$"#).firstMatch(in: tainted) // $ redos-vulnerable regex=(\n\s*)+$
|
||||
|
||||
// BAD
|
||||
// attack string: "\n" x lots + "."
|
||||
_ = try Regex(#"^(?:\s+|#.*|\(\?#[^)]*\))*(?:[?*+]|\{\d+(?:,\d*)?})"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"^(?:\s+|#.*|\(\?#[^)]*\))*(?:[?*+]|\{\d+(?:,\d*)?})"#).firstMatch(in: tainted) // $ redos-vulnerable regex=^(?:\s+|#.*|\(\?#[^)]*\))*(?:[?*+]|\{\d+(?:,\d*)?})
|
||||
|
||||
// BAD
|
||||
// (no confirmed attack string)
|
||||
_ = try Regex(#"\{\[\s*([a-zA-Z]+)\(([a-zA-Z]+)\)((\s*([a-zA-Z]+)\: ?([ a-zA-Z{}]+),?)+)*\s*\]\}"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"\{\[\s*([a-zA-Z]+)\(([a-zA-Z]+)\)((\s*([a-zA-Z]+)\: ?([ a-zA-Z{}]+),?)+)*\s*\]\}"#).firstMatch(in: tainted) // $ redos-vulnerable regex="\{\[\s*([a-zA-Z]+)\(([a-zA-Z]+)\)((\s*([a-zA-Z]+)\: ?([ a-zA-Z{}]+),?)+)*\s*\]\}"
|
||||
|
||||
// BAD
|
||||
// attack string: "a" x lots + "!"
|
||||
_ = try Regex("(a+|b+|c+)*c").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("(a+|b+|c+)*c").firstMatch(in: tainted) // $ redos-vulnerable regex=(a+|b+|c+)*c
|
||||
|
||||
// BAD
|
||||
// attack string: "a" x lots + "!"
|
||||
_ = try Regex("(((a+a?)*)+b+)").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("(((a+a?)*)+b+)").firstMatch(in: tainted) // $ redos-vulnerable regex=(((a+a?)*)+b+)
|
||||
|
||||
// BAD
|
||||
// attack string: "a" x lots + "!"
|
||||
_ = try Regex("(a+)+bbbb").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("(a+)+bbbb").firstMatch(in: tainted) // $ redos-vulnerable regex=(a+)+bbbb
|
||||
|
||||
// GOOD
|
||||
_ = try Regex("(a+)+aaaaa*a+").firstMatch(in: tainted)
|
||||
_ = try Regex("(a+)+aaaaa*a+").firstMatch(in: tainted) // $ regex=(a+)+aaaaa*a+
|
||||
// BAD
|
||||
// attack string: "a" x lots + "!"
|
||||
_ = try Regex("(a+)+aaaaa*a+").wholeMatch(in: tainted) // $ MISSING: redos-vulnerable=
|
||||
_ = try Regex("(a+)+aaaaa*a+").wholeMatch(in: tainted) // $ regex=(a+)+aaaaa*a+ MISSING: redos-vulnerable
|
||||
|
||||
// BAD
|
||||
// attack string: "a" x lots + "!"
|
||||
_ = try Regex("(a+)+aaaaa$").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("(a+)+aaaaa$").firstMatch(in: tainted) // $ redos-vulnerable regex=(a+)+aaaaa$
|
||||
|
||||
// GOOD
|
||||
_ = try Regex(#"(\n+)+\n\n"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"(\n+)+\n\n"#).firstMatch(in: tainted) // $ regex=(\n+)+\n\n
|
||||
// BAD
|
||||
// attack string: "\n" x lots + "."
|
||||
_ = try Regex(#"(\n+)+\n\n"#).wholeMatch(in: tainted) // $ MISSING: redos-vulnerable=
|
||||
_ = try Regex(#"(\n+)+\n\n"#).wholeMatch(in: tainted) // $ regex=(\n+)+\n\n MISSING: redos-vulnerable
|
||||
|
||||
// BAD
|
||||
// attack string: "\n" x lots + "."
|
||||
_ = try Regex(#"(\n+)+\n\n$"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"(\n+)+\n\n$"#).firstMatch(in: tainted) // $ redos-vulnerable regex=(\n+)+\n\n$
|
||||
|
||||
// BAD
|
||||
// attack string: " " x lots + "X"
|
||||
_ = try Regex("([^X]+)*$").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("([^X]+)*$").firstMatch(in: tainted) // $ redos-vulnerable regex=([^X]+)*$
|
||||
|
||||
// BAD
|
||||
// attack string: "b" x lots + "!"
|
||||
_ = try Regex("(([^X]b)+)*$").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("(([^X]b)+)*$").firstMatch(in: tainted) // $ redos-vulnerable regex=(([^X]b)+)*$
|
||||
|
||||
// GOOD
|
||||
_ = try Regex("(([^X]b)+)*($|[^X]b)").firstMatch(in: tainted)
|
||||
_ = try Regex("(([^X]b)+)*($|[^X]b)").firstMatch(in: tainted) // $ regex=(([^X]b)+)*($|[^X]b)
|
||||
// BAD
|
||||
// attack string: "b" x lots + "!"
|
||||
_ = try Regex("(([^X]b)+)*($|[^X]b)").wholeMatch(in: tainted) // $ MISSING: redos-vulnerable=
|
||||
_ = try Regex("(([^X]b)+)*($|[^X]b)").wholeMatch(in: tainted) // $ regex=(([^X]b)+)*($|[^X]b) MISSING: redos-vulnerable
|
||||
|
||||
// BAD
|
||||
// attack string: "b" x lots + "!"
|
||||
_ = try Regex("(([^X]b)+)*($|[^X]c)").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("(([^X]b)+)*($|[^X]c)").firstMatch(in: tainted) // $ redos-vulnerable regex=(([^X]b)+)*($|[^X]c)
|
||||
|
||||
// GOOD
|
||||
_ = try Regex("((ab)+)*ababab").firstMatch(in: tainted)
|
||||
_ = try Regex("((ab)+)*ababab").firstMatch(in: tainted) // $ regex=((ab)+)*ababab
|
||||
// BAD
|
||||
// attack string: "ab" x lots + "!"
|
||||
_ = try Regex("((ab)+)*ababab").wholeMatch(in: tainted) // $ MISSING: redos-vulnerable=
|
||||
_ = try Regex("((ab)+)*ababab").wholeMatch(in: tainted) // $ regex=((ab)+)*ababab MISSING: redos-vulnerable
|
||||
|
||||
// GOOD
|
||||
_ = try Regex("((ab)+)*abab(ab)*(ab)+").firstMatch(in: tainted)
|
||||
_ = try Regex("((ab)+)*abab(ab)*(ab)+").firstMatch(in: tainted) // $ regex=((ab)+)*abab(ab)*(ab)+
|
||||
// BAD
|
||||
// attack string: "ab" x lots + "!"
|
||||
_ = try Regex("((ab)+)*abab(ab)*(ab)+").wholeMatch(in: tainted) // $ MISSING: redos-vulnerable=
|
||||
_ = try Regex("((ab)+)*abab(ab)*(ab)+").wholeMatch(in: tainted) // $ regex=((ab)+)*abab(ab)*(ab)+ MISSING: redos-vulnerable
|
||||
|
||||
// GOOD
|
||||
_ = try Regex("((ab)+)*").firstMatch(in: tainted)
|
||||
_ = try Regex("((ab)+)*").firstMatch(in: tainted) // $ regex=((ab)+)*
|
||||
// BAD
|
||||
// attack string: "ab" x lots + "!"
|
||||
_ = try Regex("((ab)+)*").wholeMatch(in: tainted) // $ MISSING: redos-vulnerable=
|
||||
_ = try Regex("((ab)+)*").wholeMatch(in: tainted) // $ regex=((ab)+)* MISSING: redos-vulnerable
|
||||
|
||||
// BAD
|
||||
// attack string: "ab" x lots + "!"
|
||||
_ = try Regex("((ab)+)*$").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("((ab)+)*$").firstMatch(in: tainted) // $ redos-vulnerable regex=((ab)+)*$
|
||||
|
||||
// GOOD
|
||||
_ = try Regex("((ab)+)*[a1][b1][a2][b2][a3][b3]").firstMatch(in: tainted)
|
||||
_ = try Regex("((ab)+)*[a1][b1][a2][b2][a3][b3]").firstMatch(in: tainted) // $ regex=((ab)+)*[a1][b1][a2][b2][a3][b3]
|
||||
// BAD
|
||||
// attack string: "ab" x lots + "!"
|
||||
_ = try Regex("((ab)+)*[a1][b1][a2][b2][a3][b3]").wholeMatch(in: tainted) // $ MISSING: redos-vulnerable=
|
||||
_ = try Regex("((ab)+)*[a1][b1][a2][b2][a3][b3]").wholeMatch(in: tainted) // $ regex=((ab)+)*[a1][b1][a2][b2][a3][b3] MISSING: redos-vulnerable
|
||||
|
||||
// BAD
|
||||
// (no confirmed attack string)
|
||||
_ = try Regex(#"([\n\s]+)*(.)"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"([\n\s]+)*(.)"#).firstMatch(in: tainted) // $ redos-vulnerable regex=([\n\s]+)*(.)
|
||||
|
||||
// GOOD - any witness passes through the accept state.
|
||||
_ = try Regex("(A*A*X)*").firstMatch(in: tainted)
|
||||
_ = try Regex("(A*A*X)*").firstMatch(in: tainted) // $ regex=(A*A*X)*
|
||||
|
||||
// GOOD
|
||||
_ = try Regex(#"([^\\\]]+)*"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"([^\\\]]+)*"#).firstMatch(in: tainted) // $ regex=([^\\\]]+)*
|
||||
|
||||
// BAD
|
||||
_ = try Regex(#"(\w*foobarbaz\w*foobarbaz\w*foobarbaz\w*foobarbaz\s*foobarbaz\d*foobarbaz\w*)+-"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"(\w*foobarbaz\w*foobarbaz\w*foobarbaz\w*foobarbaz\s*foobarbaz\d*foobarbaz\w*)+-"#).firstMatch(in: tainted) // $ redos-vulnerable regex=(\w*foobarbaz\w*foobarbaz\w*foobarbaz\w*foobarbaz\s*foobarbaz\d*foobarbaz\w*)+-
|
||||
|
||||
// GOOD
|
||||
// (these regexs explore a query performance issue we had at one point)
|
||||
_ = try Regex(#"(\w*foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobar)+"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"(\w*foobarfoobarfoobar)+"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"(\w*foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobar)+"#).firstMatch(in: tainted) // $ regex=(\w*foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobar)+
|
||||
_ = try Regex(#"(\w*foobarfoobarfoobar)+"#).firstMatch(in: tainted) // $ regex=(\w*foobarfoobarfoobar)+
|
||||
|
||||
// BAD (but cannot currently construct a prefix)
|
||||
// attack string: "aa" + "b" x lots + "!"
|
||||
_ = try Regex("a{2,3}(b+)+X").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("a{2,3}(b+)+X").firstMatch(in: tainted) // $ redos-vulnerable regex=a{2,3}(b+)+X
|
||||
|
||||
// BAD (and a good prefix test)
|
||||
// (no confirmed attack string)
|
||||
_ = try Regex(#"^<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"^<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>"#).firstMatch(in: tainted) // $ redos-vulnerable regex=^<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>
|
||||
|
||||
// GOOD
|
||||
_ = try Regex(#"(a+)*[\s\S][\s\S][\s\S]?"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"(a+)*[\s\S][\s\S][\s\S]?"#).firstMatch(in: tainted) // $ regex=(a+)*[\s\S][\s\S][\s\S]?
|
||||
|
||||
// GOOD - but we fail to see that repeating the attack string ends in the "accept any" state (due to not parsing the range `[\s\S]{2,3}`).
|
||||
_ = try Regex(#"(a+)*[\s\S]{2,3}"#).firstMatch(in: tainted) // $ SPURIOUS: redos-vulnerable=
|
||||
_ = try Regex(#"(a+)*[\s\S]{2,3}"#).firstMatch(in: tainted) // $ regex=(a+)*[\s\S]{2,3} SPURIOUS: redos-vulnerable
|
||||
|
||||
// GOOD - but we spuriously conclude that a rejecting suffix exists (due to not parsing the range `[\s\S]{2,}` when constructing the NFA).
|
||||
_ = try Regex(#"(a+)*([\s\S]{2,}|X)$"#).firstMatch(in: tainted) // $ SPURIOUS: redos-vulnerable=
|
||||
_ = try Regex(#"(a+)*([\s\S]{2,}|X)$"#).firstMatch(in: tainted) // $ regex=(a+)*([\s\S]{2,}|X)$ SPURIOUS: redos-vulnerable
|
||||
|
||||
// GOOD
|
||||
_ = try Regex(#"(a+)*([\s\S]*|X)$"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"(a+)*([\s\S]*|X)$"#).firstMatch(in: tainted) // $ regex=(a+)*([\s\S]*|X)$
|
||||
|
||||
// BAD
|
||||
// attack string: "a" x lots + "!"
|
||||
_ = try Regex(#"((a+)*$|[\s\S]+)"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"((a+)*$|[\s\S]+)"#).firstMatch(in: tainted) // $ redos-vulnerable regex=((a+)*$|[\s\S]+)
|
||||
|
||||
// GOOD - but still flagged. The only change compared to the above is the order of alternatives, which we don't model.
|
||||
_ = try Regex(#"([\s\S]+|(a+)*$)"#).firstMatch(in: tainted) // $ SPURIOUS: redos-vulnerable=
|
||||
_ = try Regex(#"([\s\S]+|(a+)*$)"#).firstMatch(in: tainted) // $ regex=([\s\S]+|(a+)*$) SPURIOUS: redos-vulnerable
|
||||
|
||||
// GOOD
|
||||
_ = try Regex("((;|^)a+)+$").firstMatch(in: tainted)
|
||||
_ = try Regex("((;|^)a+)+$").firstMatch(in: tainted) // $ regex=((;|^)a+)+$
|
||||
|
||||
// BAD (a good prefix test)
|
||||
// attack string: "00000000000000" + "e" x lots + "!"
|
||||
_ = try Regex("(^|;)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(e+)+f").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("(^|;)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(e+)+f").firstMatch(in: tainted) // $ redos-vulnerable regex=(^|;)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(e+)+f
|
||||
|
||||
// BAD
|
||||
// atack string: "ab" + "c" x lots + "!"
|
||||
_ = try Regex("^ab(c+)+$").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("^ab(c+)+$").firstMatch(in: tainted) // $ redos-vulnerable regex=^ab(c+)+$
|
||||
|
||||
// BAD
|
||||
// (no confirmed attack string)
|
||||
_ = try Regex(#"(\d(\s+)*){20}"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"(\d(\s+)*){20}"#).firstMatch(in: tainted) // $ redos-vulnerable regex=(\d(\s+)*){20}
|
||||
|
||||
// GOOD - but we spuriously conclude that a rejecting suffix exists.
|
||||
_ = try Regex(#"(([^/]|X)+)(\/[\s\S]*)*$"#).firstMatch(in: tainted) // $ SPURIOUS: redos-vulnerable=
|
||||
_ = try Regex(#"(([^/]|X)+)(\/[\s\S]*)*$"#).firstMatch(in: tainted) // $ regex=(([^/]|X)+)(\/[\s\S]*)*$ SPURIOUS: redos-vulnerable
|
||||
|
||||
// GOOD - but we spuriously conclude that a rejecting suffix exists.
|
||||
_ = try Regex("^((x([^Y]+)?)*(Y|$))").firstMatch(in: tainted) // $ SPURIOUS: redos-vulnerable=
|
||||
_ = try Regex("^((x([^Y]+)?)*(Y|$))").firstMatch(in: tainted) // $ regex=^((x([^Y]+)?)*(Y|$)) SPURIOUS: redos-vulnerable
|
||||
|
||||
// BAD
|
||||
// (no confirmed attack string)
|
||||
_ = try Regex(#"foo([\w-]*)+bar"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"foo([\w-]*)+bar"#).firstMatch(in: tainted) // $ redos-vulnerable regex=foo([\w-]*)+bar
|
||||
|
||||
// BAD
|
||||
// attack string: "ab" x lots + "!"
|
||||
_ = try Regex("((ab)*)+c").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("((ab)*)+c").firstMatch(in: tainted) // $ redos-vulnerable regex=((ab)*)+c
|
||||
|
||||
// BAD
|
||||
// attack string: "a" x lots + "!"
|
||||
_ = try Regex("(a?a?)*b").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("(a?a?)*b").firstMatch(in: tainted) // $ redos-vulnerable regex=(a?a?)*b
|
||||
|
||||
// GOOD
|
||||
_ = try Regex("(a?)*b").firstMatch(in: tainted)
|
||||
_ = try Regex("(a?)*b").firstMatch(in: tainted) // $ regex=(a?)*b
|
||||
|
||||
// BAD - but not detected
|
||||
// (no confirmed attack string)
|
||||
_ = try Regex("(c?a?)*b").firstMatch(in: tainted) // $ MISSING: redos-vulnerable=
|
||||
_ = try Regex("(c?a?)*b").firstMatch(in: tainted) // $ regex=(c?a?)*b MISSING: redos-vulnerable
|
||||
|
||||
// BAD
|
||||
// attack string: "a" x lots + "!"
|
||||
_ = try Regex("(?:a|a?)+b").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("(?:a|a?)+b").firstMatch(in: tainted) // $ redos-vulnerable regex=(?:a|a?)+b
|
||||
|
||||
// BAD - but not detected.
|
||||
// attack string: "ab" x lots + "!"
|
||||
_ = try Regex("(a?b?)*$").firstMatch(in: tainted) // $ MISSING: redos-vulnerable=
|
||||
_ = try Regex("(a?b?)*$").firstMatch(in: tainted) // $ regex=(a?b?)*$ MISSING: redos-vulnerable
|
||||
|
||||
// BAD
|
||||
// (no confirmed attack string)
|
||||
_ = try Regex("PRE(([a-c]|[c-d])T(e?e?e?e?|X))+(cTcT|cTXcTX$)").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("PRE(([a-c]|[c-d])T(e?e?e?e?|X))+(cTcT|cTXcTX$)").firstMatch(in: tainted) // $ redos-vulnerable regex=PRE(([a-c]|[c-d])T(e?e?e?e?|X))+(cTcT|cTXcTX$)
|
||||
|
||||
// BAD
|
||||
// attack string: "a" x lots + "!"
|
||||
_ = try Regex(#"^((a)+\w)+$"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"^((a)+\w)+$"#).firstMatch(in: tainted) // $ redos-vulnerable regex=^((a)+\w)+$
|
||||
|
||||
// BAD
|
||||
// attack string: "bbbbbbbbbb." x lots + "!"
|
||||
_ = try Regex("^(b+.)+$").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("^(b+.)+$").firstMatch(in: tainted) // $ redos-vulnerable regex=^(b+.)+$
|
||||
|
||||
// BAD - all 4 bad combinations of nested * and +
|
||||
// attack string: "a" x lots + "!"
|
||||
_ = try Regex("(a*)*b").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("(a+)*b").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("(a*)+b").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("(a+)+b").firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex("(a*)*b").firstMatch(in: tainted) // $ redos-vulnerable regex=(a*)*b
|
||||
_ = try Regex("(a+)*b").firstMatch(in: tainted) // $ redos-vulnerable regex=(a+)*b
|
||||
_ = try Regex("(a*)+b").firstMatch(in: tainted) // $ redos-vulnerable regex=(a*)+b
|
||||
_ = try Regex("(a+)+b").firstMatch(in: tainted) // $ redos-vulnerable regex=(a+)+b
|
||||
|
||||
// GOOD
|
||||
_ = try Regex("(a|b)+").firstMatch(in: tainted)
|
||||
_ = try Regex("(a|b)+").firstMatch(in: tainted) // $ regex=(a|b)+
|
||||
|
||||
// GOOD
|
||||
_ = try Regex(#"(?:[\s;,"'<>(){}|\[\]@=+*]|:(?![/\\]))+"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"(?:[\s;,"'<>(){}|\[\]@=+*]|:(?![/\\]))+"#).firstMatch(in: tainted) // $ regex=(?:[\s;,"'<>(){}|\[\]@=+*]|:(?![/\\]))+
|
||||
|
||||
// BAD?
|
||||
// (no confirmed attack string)
|
||||
_ = try Regex(#"^((?:a{|-)|\w\{)+X$"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"^((?:a{0|-)|\w\{\d)+X$"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"^((?:a{0,|-)|\w\{\d,)+X$"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"^((?:a{0,2|-)|\w\{\d,\d)+X$"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"^((?:a{|-)|\w\{)+X$"#).firstMatch(in: tainted) // $ redos-vulnerable regex=^((?:a{|-)|\w\{)+X$
|
||||
_ = try Regex(#"^((?:a{0|-)|\w\{\d)+X$"#).firstMatch(in: tainted) // $ redos-vulnerable regex=^((?:a{0|-)|\w\{\d)+X$
|
||||
_ = try Regex(#"^((?:a{0,|-)|\w\{\d,)+X$"#).firstMatch(in: tainted) // $ redos-vulnerable regex=^((?:a{0,|-)|\w\{\d,)+X$
|
||||
_ = try Regex(#"^((?:a{0,2|-)|\w\{\d,\d)+X$"#).firstMatch(in: tainted) // $ redos-vulnerable regex=^((?:a{0,2|-)|\w\{\d,\d)+X$
|
||||
|
||||
// GOOD
|
||||
_ = try Regex(#"^((?:a{0,2}|-)|\w\{\d,\d\})+X$"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"^((?:a{0,2}|-)|\w\{\d,\d\})+X$"#).firstMatch(in: tainted) // $ regex=^((?:a{0,2}|-)|\w\{\d,\d\})+X$
|
||||
|
||||
// BAD
|
||||
// attack string: "X" + "a" x lots
|
||||
_ = try Regex(#"X(\u0061|a)*Y"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"X(\u0061|a)*Y"#).firstMatch(in: tainted) // $ redos-vulnerable regex=X(\u0061|a)*Y
|
||||
|
||||
// GOOD
|
||||
_ = try Regex(#"X(\u0061|b)+Y"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"X(\u0061|b)+Y"#).firstMatch(in: tainted) // $ regex=X(\u0061|b)+Y
|
||||
|
||||
// BAD
|
||||
// attack string: "X" + "a" x lots
|
||||
_ = try Regex(#"X(\U00000061|a)*Y"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"X(\U00000061|a)*Y"#).firstMatch(in: tainted) // $ redos-vulnerable regex=X(\U00000061|a)*Y
|
||||
|
||||
// GOOD
|
||||
_ = try Regex(#"X(\U00000061|b)+Y"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"X(\U00000061|b)+Y"#).firstMatch(in: tainted) // $ regex=X(\U00000061|b)+Y
|
||||
|
||||
// BAD
|
||||
// attack string: "X" + "a" x lots
|
||||
_ = try Regex(#"X(\x61|a)*Y"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"X(\x61|a)*Y"#).firstMatch(in: tainted) // $ redos-vulnerable regex=X(\x61|a)*Y
|
||||
|
||||
// GOOD
|
||||
_ = try Regex(#"X(\x61|b)+Y"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"X(\x61|b)+Y"#).firstMatch(in: tainted) // $ regex=X(\x61|b)+Y
|
||||
|
||||
// BAD
|
||||
// attack string: "X" + "a" x lots
|
||||
_ = try Regex(#"X(\x{061}|a)*Y"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"X(\x{061}|a)*Y"#).firstMatch(in: tainted) // $ redos-vulnerable regex=X(\x{061}|a)*Y
|
||||
|
||||
// GOOD
|
||||
_ = try Regex(#"X(\x{061}|b)+Y"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"X(\x{061}|b)+Y"#).firstMatch(in: tainted) // $ regex=X(\x{061}|b)+Y
|
||||
|
||||
// BAD
|
||||
// attack string: "X" + "7" x lots
|
||||
_ = try Regex(#"X(\p{Digit}|7)*Y"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"X(\p{Digit}|7)*Y"#).firstMatch(in: tainted) // $ redos-vulnerable regex=X(\p{Digit}|7)*Y
|
||||
|
||||
// GOOD
|
||||
_ = try Regex(#"X(\p{Digit}|b)+Y"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"X(\p{Digit}|b)+Y"#).firstMatch(in: tainted) // $ regex=X(\p{Digit}|b)+Y
|
||||
|
||||
// BAD
|
||||
// attack string: "X" + "b" x lots
|
||||
_ = try Regex(#"X(\P{Digit}|b)*Y"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"X(\P{Digit}|b)*Y"#).firstMatch(in: tainted) // $ redos-vulnerable regex=X(\P{Digit}|b)*Y
|
||||
|
||||
// GOOD
|
||||
_ = try Regex(#"X(\P{Digit}|7)+Y"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"X(\P{Digit}|7)+Y"#).firstMatch(in: tainted) // $ regex=X(\P{Digit}|7)+Y
|
||||
|
||||
// BAD
|
||||
// attack string: "X" + "7" x lots
|
||||
_ = try Regex(#"X(\p{IsDigit}|7)*Y"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"X(\p{IsDigit}|7)*Y"#).firstMatch(in: tainted) // $ redos-vulnerable regex=X(\p{IsDigit}|7)*Y
|
||||
|
||||
// GOOD
|
||||
_ = try Regex(#"X(\p{IsDigit}|b)+Y"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"X(\p{IsDigit}|b)+Y"#).firstMatch(in: tainted) // $ regex=X(\p{IsDigit}|b)+Y
|
||||
|
||||
// BAD - but not detected
|
||||
// attack string: "X" + "a" x lots
|
||||
_ = try Regex(#"X(\p{Alpha}|a)*Y"#).firstMatch(in: tainted) // $ MISSING: redos-vulnerable=
|
||||
_ = try Regex(#"X(\p{Alpha}|a)*Y"#).firstMatch(in: tainted) // $ regex=X(\p{Alpha}|a)*Y MISSING: redos-vulnerable
|
||||
|
||||
// GOOD
|
||||
_ = try Regex(#"X(\p{Alpha}|7)+Y"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"X(\p{Alpha}|7)+Y"#).firstMatch(in: tainted) // $ regex=X(\p{Alpha}|7)+Y
|
||||
|
||||
// GOOD
|
||||
_ = try Regex(#"("[^"]*?"|[^"\s]+)+(?=\s*|\s*$)"#).firstMatch(in: tainted)
|
||||
_ = try Regex(#"("[^"]*?"|[^"\s]+)+(?=\s*|\s*$)"#).firstMatch(in: tainted) // $ regex=("[^"]*?"|[^"\s]+)+(?=\s*|\s*$)
|
||||
// BAD
|
||||
// attack string: "##" x lots + "\na"
|
||||
_ = try Regex(#"("[^"]*?"|[^"\s]+)+(?=\s*|\s*$)"#).wholeMatch(in: tainted) // $ MISSING: redos-vulnerable=
|
||||
_ = try Regex(#"("[^"]*?"|[^"\s]+)+(?=\s*|\s*$)"#).wholeMatch(in: tainted) // $ regex=("[^"]*?"|[^"\s]+)+(?=\s*|\s*$) MISSING: redos-vulnerable
|
||||
|
||||
// BAD
|
||||
// attack string: "/" + "\\/a" x lots
|
||||
_ = try Regex(#"/("[^"]*?"|[^"\s]+)+(?=\s*|\s*$)X"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"/("[^"]*?"|[^"\s]+)+(?=X)"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"/("[^"]*?"|[^"\s]+)+(?=\s*|\s*$)X"#).firstMatch(in: tainted) // $ redos-vulnerable regex=/("[^"]*?"|[^"\s]+)+(?=\s*|\s*$)X
|
||||
_ = try Regex(#"/("[^"]*?"|[^"\s]+)+(?=X)"#).firstMatch(in: tainted) // $ redos-vulnerable regex=/("[^"]*?"|[^"\s]+)+(?=X)
|
||||
|
||||
// BAD
|
||||
// attack string: "0" x lots + "!"
|
||||
_ = try Regex(#"\A(\d|0)*x"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"(\d|0)*\Z"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"\b(\d|0)*x"#).firstMatch(in: tainted) // $ redos-vulnerable=
|
||||
_ = try Regex(#"\A(\d|0)*x"#).firstMatch(in: tainted) // $ redos-vulnerable regex=\A(\d|0)*x
|
||||
_ = try Regex(#"(\d|0)*\Z"#).firstMatch(in: tainted) // $ redos-vulnerable regex=(\d|0)*\Z
|
||||
_ = try Regex(#"\b(\d|0)*x"#).firstMatch(in: tainted) // $ redos-vulnerable regex=\b(\d|0)*x
|
||||
|
||||
// GOOD - possessive quantifiers don't backtrack
|
||||
_ = try Regex("(a*+)*+b").firstMatch(in: tainted) // $ hasParseFailure
|
||||
_ = try Regex("(a*)*+b").firstMatch(in: tainted) // $ hasParseFailure
|
||||
_ = try Regex("(a*+)*b").firstMatch(in: tainted) // $ hasParseFailure
|
||||
_ = try Regex("(a*+)*+b").firstMatch(in: tainted) // $ hasParseFailure regex=(a*+)*+b
|
||||
_ = try Regex("(a*)*+b").firstMatch(in: tainted) // $ hasParseFailure regex=(a*)*+b
|
||||
_ = try Regex("(a*+)*b").firstMatch(in: tainted) // $ hasParseFailure regex=(a*+)*b
|
||||
|
||||
// BAD - but not detected due to the way possessive quantifiers are approximated
|
||||
// attack string: "aab" x lots + "!"
|
||||
_ = try Regex("((aa|a*+)b)*c").firstMatch(in: tainted) // $ hasParseFailure MISSING: redos-vulnerable=
|
||||
_ = try Regex("((aa|a*+)b)*c").firstMatch(in: tainted) // $ hasParseFailure regex=((aa|a*+)b)*c MISSING: redos-vulnerable
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
failures
|
||||
testFailures
|
||||
failures
|
||||
|
||||
@@ -10,7 +10,7 @@ string quote(string s) { if s.matches("% %") then result = "\"" + s + "\"" else
|
||||
|
||||
module RegexTest implements TestSig {
|
||||
string getARelevantTag() {
|
||||
result = ["regex", "input", "redos-vulnerable", "hasParseFailure", "modes"]
|
||||
result = ["regex", "unevaluated-regex", "input", "redos-vulnerable", "hasParseFailure", "modes"]
|
||||
}
|
||||
|
||||
predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
@@ -39,16 +39,6 @@ module RegexTest implements TestSig {
|
||||
value = quote(regex.getFlags()) and
|
||||
value != ""
|
||||
)
|
||||
}
|
||||
|
||||
predicate hasOptionalResult(Location location, string element, string tag, string value) {
|
||||
exists(RegexEval eval, Expr input |
|
||||
eval.getStringInput() = input and
|
||||
location = input.getLocation() and
|
||||
element = input.toString() and
|
||||
tag = "input" and
|
||||
value = quote(input.toString())
|
||||
)
|
||||
or
|
||||
exists(RegexEval eval, RegExp regex |
|
||||
eval.getARegex() = regex and
|
||||
@@ -57,6 +47,25 @@ module RegexTest implements TestSig {
|
||||
tag = "regex" and
|
||||
value = quote(regex.toString().replaceAll("\n", "NEWLINE"))
|
||||
)
|
||||
or
|
||||
exists(RegExp regex |
|
||||
// unevaluated regex
|
||||
not exists(RegexEval eval | eval.getARegex() = regex) and
|
||||
location = regex.getLocation() and
|
||||
element = regex.toString() and
|
||||
tag = "unevaluated-regex" and
|
||||
value = quote(regex.toString().replaceAll("\n", "NEWLINE"))
|
||||
)
|
||||
}
|
||||
|
||||
predicate hasOptionalResult(Location location, string element, string tag, string value) {
|
||||
exists(RegexEval eval, Expr input |
|
||||
eval.getStringInputNode().asExpr() = input and
|
||||
location = input.getLocation() and
|
||||
element = input.toString() and
|
||||
tag = "input" and
|
||||
value = quote(input.toString())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ class NSString : NSObject {
|
||||
|
||||
static var regularExpression: NSString.CompareOptions { get { return CompareOptions(rawValue: 1) } }
|
||||
static var caseInsensitive: NSString.CompareOptions { get { return CompareOptions(rawValue: 2) } }
|
||||
static var literal: NSString.CompareOptions { get { return CompareOptions(rawValue: 4) } }
|
||||
}
|
||||
|
||||
convenience init(string aString: String) { self.init() }
|
||||
@@ -125,8 +126,10 @@ func myRegexpMethodsTests(b: Bool, str_unknown: String) throws {
|
||||
|
||||
// --- StringProtocol ---
|
||||
|
||||
_ = input.range(of: ".*", options: .regularExpression, range: nil, locale: nil) // $ MISSING: regex=.* input=input
|
||||
_ = input.replacingOccurrences(of: ".*", with: "", options: .regularExpression) // $ MISSING: regex=.* input=input
|
||||
_ = input.range(of: ".*", options: .regularExpression, range: nil, locale: nil) // $ regex=.* input=input
|
||||
_ = input.range(of: ".*", options: .literal, range: nil, locale: nil) // (not a regular expression)
|
||||
_ = input.replacingOccurrences(of: ".*", with: "", options: .regularExpression) // $ regex=.* input=input
|
||||
_ = input.replacingOccurrences(of: ".*", with: "", options: .literal) // (not a regular expression)
|
||||
|
||||
// --- NSRegularExpression ---
|
||||
|
||||
@@ -142,8 +145,15 @@ func myRegexpMethodsTests(b: Bool, str_unknown: String) throws {
|
||||
// --- NSString ---
|
||||
|
||||
let inputNS = NSString(string: "abcdef")
|
||||
_ = inputNS.range(of: "*", options: .regularExpression) // $ MISSING: regex=.* input=inputNS
|
||||
_ = inputNS.replacingOccurrences(of: ".*", with: "", options: .regularExpression, range: NSMakeRange(0, inputNS.length)) // $ MISSING: regex=.* input=inputNS
|
||||
let regexOptions = NSString.CompareOptions.regularExpression
|
||||
let regexOptions2 : NSString.CompareOptions = [.regularExpression, .caseInsensitive]
|
||||
_ = inputNS.range(of: ".*", options: .regularExpression) // $ regex=.* input=inputNS
|
||||
_ = inputNS.range(of: ".*", options: [.regularExpression]) // $ regex=.* input=inputNS
|
||||
_ = inputNS.range(of: ".*", options: regexOptions) // $ regex=.* input=inputNS
|
||||
_ = inputNS.range(of: ".*", options: regexOptions2) // $ regex=.* input=inputNS modes=IGNORECASE
|
||||
_ = inputNS.range(of: ".*", options: .literal) // (not a regular expression)
|
||||
_ = inputNS.replacingOccurrences(of: ".*", with: "", options: .regularExpression, range: NSMakeRange(0, inputNS.length)) // $ regex=.* input=inputNS
|
||||
_ = inputNS.replacingOccurrences(of: ".*", with: "", options: .literal, range: NSMakeRange(0, inputNS.length)) // (not a regular expression)
|
||||
|
||||
// --- flow ---
|
||||
|
||||
@@ -202,8 +212,9 @@ func myRegexpMethodsTests(b: Bool, str_unknown: String) throws {
|
||||
_ = try Regex("[:alnum:]").firstMatch(in: input) // $ input=input regex=[:alnum:] SPURIOUS: $hasParseFailure
|
||||
|
||||
// invalid (Swift doesn't like these regexs)
|
||||
_ = try Regex("[]a]").firstMatch(in: input) // this is valid in other regex implementations, and is likely harmless to accept
|
||||
_ = try Regex("[:aaaaa:]").firstMatch(in: input) // $ hasParseFailure
|
||||
_ = try Regex("[]a]").firstMatch(in: input) // $ regex=[]a]
|
||||
// ^ this is valid in other regex implementations, and is likely harmless to accept
|
||||
_ = try Regex("[:aaaaa:]").firstMatch(in: input) // $ regex=[:aaaaa:] hasParseFailure
|
||||
|
||||
// --- parse modes ---
|
||||
|
||||
@@ -237,8 +248,11 @@ func myRegexpMethodsTests(b: Bool, str_unknown: String) throws {
|
||||
// parse modes set through other methods
|
||||
|
||||
let myOptions2 : NSString.CompareOptions = [.regularExpression, .caseInsensitive]
|
||||
_ = input.replacingOccurrences(of: ".*", with: "", options: [.regularExpression, .caseInsensitive]) // $ MISSING: regex=.* input=input modes=IGNORECASE
|
||||
_ = input.replacingOccurrences(of: ".*", with: "", options: myOptions2) // $ MISSING: regex=.* input=input modes=IGNORECASE
|
||||
_ = NSString(string: "abc").replacingOccurrences(of: ".*", with: "", options: [.regularExpression, .caseInsensitive], range: NSMakeRange(0, inputNS.length)) // $ MISSING: regex=.* input=inputNS modes=IGNORECASE
|
||||
_ = NSString(string: "abc").replacingOccurrences(of: ".*", with: "", options: myOptions2, range: NSMakeRange(0, inputNS.length)) // $ MISSING: regex=.* input=inputNS modes=IGNORECASE
|
||||
_ = input.replacingOccurrences(of: ".*", with: "", options: [.regularExpression, .caseInsensitive]) // $ regex=.* input=input modes=IGNORECASE
|
||||
_ = input.replacingOccurrences(of: ".*", with: "", options: myOptions2) // $ regex=.* input=input modes=IGNORECASE
|
||||
_ = NSString(string: "abc").replacingOccurrences(of: ".*", with: "", options: [.regularExpression, .caseInsensitive], range: NSMakeRange(0, inputNS.length)) // $ regex=.* input="call to NSString.init(string:)" modes=IGNORECASE
|
||||
_ = NSString(string: "abc").replacingOccurrences(of: ".*", with: "", options: myOptions2, range: NSMakeRange(0, inputNS.length)) // $ regex=.* input="call to NSString.init(string:)" modes=IGNORECASE
|
||||
|
||||
// Regex created but never evaluated
|
||||
_ = try Regex(".*") // $ unevaluated-regex=.*
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ edges
|
||||
| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:113:24:113:24 | taintedString |
|
||||
| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:114:45:114:45 | taintedString |
|
||||
| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:120:19:120:19 | taintedString |
|
||||
| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:126:40:126:40 | taintedString |
|
||||
| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:131:39:131:39 | taintedString |
|
||||
| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:137:40:137:40 | taintedString |
|
||||
| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:144:16:144:16 | remoteInput |
|
||||
| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:147:39:147:39 | regexStr |
|
||||
| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:162:17:162:17 | taintedString |
|
||||
@@ -30,7 +32,9 @@ nodes
|
||||
| tests.swift:113:24:113:24 | taintedString | semmle.label | taintedString |
|
||||
| tests.swift:114:45:114:45 | taintedString | semmle.label | taintedString |
|
||||
| tests.swift:120:19:120:19 | taintedString | semmle.label | taintedString |
|
||||
| tests.swift:126:40:126:40 | taintedString | semmle.label | taintedString |
|
||||
| tests.swift:131:39:131:39 | taintedString | semmle.label | taintedString |
|
||||
| tests.swift:137:40:137:40 | taintedString | semmle.label | taintedString |
|
||||
| tests.swift:144:16:144:16 | remoteInput | semmle.label | remoteInput |
|
||||
| tests.swift:147:39:147:39 | regexStr | semmle.label | regexStr |
|
||||
| tests.swift:162:17:162:17 | taintedString | semmle.label | taintedString |
|
||||
@@ -53,7 +57,9 @@ subpaths
|
||||
| tests.swift:113:24:113:24 | taintedString | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:113:24:113:24 | taintedString | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value |
|
||||
| tests.swift:114:45:114:45 | taintedString | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:114:45:114:45 | taintedString | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value |
|
||||
| tests.swift:120:19:120:19 | taintedString | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:120:19:120:19 | taintedString | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value |
|
||||
| tests.swift:126:40:126:40 | taintedString | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:126:40:126:40 | taintedString | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value |
|
||||
| tests.swift:131:39:131:39 | taintedString | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:131:39:131:39 | taintedString | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value |
|
||||
| tests.swift:137:40:137:40 | taintedString | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:137:40:137:40 | taintedString | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value |
|
||||
| tests.swift:144:16:144:16 | remoteInput | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:144:16:144:16 | remoteInput | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value |
|
||||
| tests.swift:147:39:147:39 | regexStr | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:147:39:147:39 | regexStr | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value |
|
||||
| tests.swift:162:17:162:17 | taintedString | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:162:17:162:17 | taintedString | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value |
|
||||
|
||||
@@ -123,7 +123,7 @@ func regexInjectionTests(cond: Bool, varString: String, myUrl: URL) throws {
|
||||
// --- StringProtocol ---
|
||||
|
||||
_ = inputVar.replacingOccurrences(of: constString, with: "", options: .regularExpression)
|
||||
_ = inputVar.replacingOccurrences(of: taintedString, with: "", options: .regularExpression) // BAD [NOT DETECTED]
|
||||
_ = inputVar.replacingOccurrences(of: taintedString, with: "", options: .regularExpression) // BAD
|
||||
|
||||
// --- NSRegularExpression ---
|
||||
|
||||
@@ -134,7 +134,7 @@ func regexInjectionTests(cond: Bool, varString: String, myUrl: URL) throws {
|
||||
|
||||
let nsString = NSString(string: varString)
|
||||
_ = nsString.replacingOccurrences(of: constString, with: "", options: .regularExpression, range: NSMakeRange(0, nsString.length))
|
||||
_ = nsString.replacingOccurrences(of: taintedString, with: "", options: .regularExpression, range: NSMakeRange(0, nsString.length)) // BAD [NOT DETECTED]
|
||||
_ = nsString.replacingOccurrences(of: taintedString, with: "", options: .regularExpression, range: NSMakeRange(0, nsString.length)) // BAD
|
||||
|
||||
// --- from the qhelp ---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user