@@ -56,31 +56,62 @@ DangerousPrefix getADangerousMatchedPrefix(EmptyReplaceRegExpTerm t) {
5656 not exists ( EmptyReplaceRegExpTerm pred | pred = t .getPredecessor + ( ) and not pred .isNullable ( ) )
5757}
5858
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+
5985/**
6086 * Gets a substring of a dangerous prefix that is in the language starting at `t` (ignoring lookarounds).
6187 *
6288 * Note that the language of `t` is slightly restricted as not all RegExpTerm types are supported.
6389 */
6490DangerousPrefixSubstring 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 ( )
8198 )
8299}
83100
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+
84115/**
85116 * Holds if `t` may match the dangerous `prefix` and some suffix, indicating intent to prevent a vulnerablity of kind `kind`.
86117 */
@@ -151,7 +182,7 @@ where
151182 // skip leading optional elements
152183 not dangerous .isNullable ( ) and
153184 // 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
155186 // only warn once per kind
156187 not exists ( EmptyReplaceRegExpTerm other |
157188 other = dangerous .getAChild + ( ) or other = dangerous .getPredecessor + ( )
0 commit comments