@@ -56,31 +56,62 @@ DangerousPrefix getADangerousMatchedPrefix(EmptyReplaceRegExpTerm t) {
56
56
not exists ( EmptyReplaceRegExpTerm pred | pred = t .getPredecessor + ( ) and not pred .isNullable ( ) )
57
57
}
58
58
59
+ private import semmle.javascript.security.performance.ReDoSUtil as ReDoSUtil
60
+
61
+ /**
62
+ * Gets a char from a dangerous prefix that is matched by `t`.
63
+ */
64
+ pragma [ noinline]
65
+ DangerousPrefixSubstring getADangerousMatchedChar ( EmptyReplaceRegExpTerm t ) {
66
+ t .isNullable ( ) and result = ""
67
+ or
68
+ t .getAMatchedString ( ) = result
69
+ or
70
+ ReDoSUtil:: getCanonicalCharClass ( t ) .( ReDoSUtil:: CharacterClass ) .matches ( result )
71
+ or
72
+ t instanceof RegExpDot and
73
+ result .length ( ) = 1
74
+ or
75
+ (
76
+ t instanceof RegExpOpt or
77
+ t instanceof RegExpStar or
78
+ t instanceof RegExpPlus or
79
+ t instanceof RegExpGroup or
80
+ t instanceof RegExpAlt
81
+ ) and
82
+ result = getADangerousMatchedChar ( t .getAChild ( ) )
83
+ }
84
+
59
85
/**
60
86
* Gets a substring of a dangerous prefix that is in the language starting at `t` (ignoring lookarounds).
61
87
*
62
88
* Note that the language of `t` is slightly restricted as not all RegExpTerm types are supported.
63
89
*/
64
90
DangerousPrefixSubstring getADangerousMatchedPrefixSubstring ( EmptyReplaceRegExpTerm t ) {
65
- exists ( string left |
66
- t .isNullable ( ) and left = ""
67
- or
68
- t .getAMatchedString ( ) = left
69
- or
70
- (
71
- t instanceof RegExpOpt or
72
- t instanceof RegExpStar or
73
- t instanceof RegExpPlus or
74
- t instanceof RegExpGroup or
75
- t instanceof RegExpAlt
76
- ) and
77
- left = getADangerousMatchedPrefixSubstring ( t .getAChild ( ) )
78
- |
79
- result = left + getADangerousMatchedPrefixSubstring ( t .getSuccessor ( ) ) or
80
- result = left
91
+ result = getADangerousMatchedChar ( t ) + getADangerousMatchedPrefixSubstring ( t .getSuccessor ( ) )
92
+ or
93
+ result = getADangerousMatchedChar ( t )
94
+ or
95
+ // loop around for repetitions (only considering alphanumeric characters in the repetition)
96
+ exists ( RepetitionMatcher repetition | t = repetition |
97
+ result = getADangerousMatchedPrefixSubstring ( repetition ) + repetition .getAChar ( )
81
98
)
82
99
}
83
100
101
+ class RepetitionMatcher extends EmptyReplaceRegExpTerm {
102
+ string char ;
103
+
104
+ pragma [ noinline]
105
+ RepetitionMatcher ( ) {
106
+ ( this instanceof RegExpPlus or this instanceof RegExpStar ) and
107
+ char = getADangerousMatchedChar ( this .getAChild ( ) ) and
108
+ char .regexpMatch ( "\\w" )
109
+ }
110
+
111
+ pragma [ noinline]
112
+ string getAChar ( ) { result = char }
113
+ }
114
+
84
115
/**
85
116
* Holds if `t` may match the dangerous `prefix` and some suffix, indicating intent to prevent a vulnerablity of kind `kind`.
86
117
*/
@@ -151,7 +182,7 @@ where
151
182
// skip leading optional elements
152
183
not dangerous .isNullable ( ) and
153
184
// only warn about the longest match (presumably the most descriptive)
154
- prefix = max ( string m | matchesDangerousPrefix ( dangerous , m , kind ) | m order by m .length ( ) ) and
185
+ prefix = max ( string m | matchesDangerousPrefix ( dangerous , m , kind ) | m order by m .length ( ) , m ) and
155
186
// only warn once per kind
156
187
not exists ( EmptyReplaceRegExpTerm other |
157
188
other = dangerous .getAChild + ( ) or other = dangerous .getPredecessor + ( )
0 commit comments