Skip to content

Commit 89bab6a

Browse files
authored
Merge pull request #7097 from erik-krogh/railsReDoS
JS/PY/RB: support a limited number of ranges for ReDoS analysis
2 parents da69886 + f7a63d5 commit 89bab6a

File tree

7 files changed

+210
-25
lines changed

7 files changed

+210
-25
lines changed

javascript/ql/lib/semmle/javascript/security/performance/ReDoSUtil.qll

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,55 @@ private class EdgeLabel extends TInputSymbol {
539539
}
540540
}
541541

542+
/**
543+
* A RegExp term that acts like a plus.
544+
* Either it's a RegExpPlus, or it is a range {1,X} where X is >= 30.
545+
* 30 has been chosen as a threshold because for exponential blowup 2^30 is enough to get a decent DOS attack.
546+
*/
547+
private class EffectivelyPlus extends RegExpTerm {
548+
EffectivelyPlus() {
549+
this instanceof RegExpPlus
550+
or
551+
exists(RegExpRange range |
552+
range.getLowerBound() = 1 and
553+
(range.getUpperBound() >= 30 or not exists(range.getUpperBound()))
554+
|
555+
this = range
556+
)
557+
}
558+
}
559+
560+
/**
561+
* A RegExp term that acts like a star.
562+
* Either it's a RegExpStar, or it is a range {0,X} where X is >= 30.
563+
*/
564+
private class EffectivelyStar extends RegExpTerm {
565+
EffectivelyStar() {
566+
this instanceof RegExpStar
567+
or
568+
exists(RegExpRange range |
569+
range.getLowerBound() = 0 and
570+
(range.getUpperBound() >= 30 or not exists(range.getUpperBound()))
571+
|
572+
this = range
573+
)
574+
}
575+
}
576+
577+
/**
578+
* A RegExp term that acts like a question mark.
579+
* Either it's a RegExpQuestion, or it is a range {0,1}.
580+
*/
581+
private class EffectivelyQuestion extends RegExpTerm {
582+
EffectivelyQuestion() {
583+
this instanceof RegExpOpt
584+
or
585+
exists(RegExpRange range | range.getLowerBound() = 0 and range.getUpperBound() = 1 |
586+
this = range
587+
)
588+
}
589+
}
590+
542591
/**
543592
* Gets the state before matching `t`.
544593
*/
@@ -559,14 +608,14 @@ State after(RegExpTerm t) {
559608
or
560609
exists(RegExpGroup grp | t = grp.getAChild() | result = after(grp))
561610
or
562-
exists(RegExpStar star | t = star.getAChild() | result = before(star))
611+
exists(EffectivelyStar star | t = star.getAChild() | result = before(star))
563612
or
564-
exists(RegExpPlus plus | t = plus.getAChild() |
613+
exists(EffectivelyPlus plus | t = plus.getAChild() |
565614
result = before(plus) or
566615
result = after(plus)
567616
)
568617
or
569-
exists(RegExpOpt opt | t = opt.getAChild() | result = after(opt))
618+
exists(EffectivelyQuestion opt | t = opt.getAChild() | result = after(opt))
570619
or
571620
exists(RegExpRoot root | t = root | result = AcceptAnySuffix(root))
572621
}
@@ -617,15 +666,17 @@ predicate delta(State q1, EdgeLabel lbl, State q2) {
617666
or
618667
exists(RegExpGroup grp | lbl = Epsilon() | q1 = before(grp) and q2 = before(grp.getChild(0)))
619668
or
620-
exists(RegExpStar star | lbl = Epsilon() |
669+
exists(EffectivelyStar star | lbl = Epsilon() |
621670
q1 = before(star) and q2 = before(star.getChild(0))
622671
or
623672
q1 = before(star) and q2 = after(star)
624673
)
625674
or
626-
exists(RegExpPlus plus | lbl = Epsilon() | q1 = before(plus) and q2 = before(plus.getChild(0)))
675+
exists(EffectivelyPlus plus | lbl = Epsilon() |
676+
q1 = before(plus) and q2 = before(plus.getChild(0))
677+
)
627678
or
628-
exists(RegExpOpt opt | lbl = Epsilon() |
679+
exists(EffectivelyQuestion opt | lbl = Epsilon() |
629680
q1 = before(opt) and q2 = before(opt.getChild(0))
630681
or
631682
q1 = before(opt) and q2 = after(opt)

javascript/ql/test/query-tests/Performance/ReDoS/PolynomialBackTracking.expected

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@
201201
| regexplib/markup.js:13:14:13:16 | .+? | Strings starting with '<' and with many repetitions of '!' can start matching anywhere after the start of the preceeding .*? |
202202
| regexplib/markup.js:14:13:14:14 | .* | Strings starting with '<' and with many repetitions of 'a' can start matching anywhere after the start of the preceeding .* |
203203
| regexplib/markup.js:14:24:14:25 | .* | Strings starting with '<>' and with many repetitions of '>a' can start matching anywhere after the start of the preceeding .* |
204+
| regexplib/markup.js:15:16:15:18 | .*? | Strings starting with '<img' and with many repetitions of '<imga' can start matching anywhere after the start of the preceeding <(\\/{0,1})img(.*?)(\\/{0,1})\\> |
204205
| regexplib/markup.js:16:5:16:9 | [^>]* | Strings starting with 'src' and with many repetitions of 'src' can start matching anywhere after the start of the preceeding src[^>]*[^/].(?:jpg\|bmp\|gif)(?:\\"\|\\') |
205206
| regexplib/markup.js:17:8:17:24 | (\\s(\\w*=".*?")?)* | Strings starting with '<a' and with many repetitions of ' =""' can start matching anywhere after the start of the preceeding .*? |
206207
| regexplib/markup.js:17:12:17:14 | \\w* | Strings starting with '<a ' and with many repetitions of '="" a' can start matching anywhere after the start of the preceeding .*? |
@@ -213,6 +214,10 @@
213214
| regexplib/markup.js:20:197:20:198 | "+ | Strings with many repetitions of '""' can start matching anywhere after the start of the preceeding "+ |
214215
| regexplib/markup.js:20:245:20:247 | .*? | Strings with many repetitions of 'color: # IF found THEN move ahead "" # single or double # or no quotes\\t' can start matching anywhere after the start of the preceeding .*? |
215216
| regexplib/markup.js:20:274:20:276 | .*? | Strings starting with '<font # Match start of Font Tag ' and with many repetitions of '<font # Match start of Font Tag a' can start matching anywhere after the start of the preceeding <\\*?font # Match start of Font Tag (?(?=[^>]+color.*>) #IF\\/THEN lookahead color in tag (.*?color\\s*?[=\|:]\\s*?) # IF found THEN move ahead ('+\\#*?[\\w\\s]*'+ # CAPTURE ColorName\\/Hex \|"+\\#*?[\\w\\s]*"+ # single or double \|\\#*\\w*\\b) # or no quotes\t.*?> # & move to end of tag \|.*?> # ELSE move to end of Tag ) # Close the If\\/Then lookahead # Use Multiline and IgnoreCase # Replace the matches from RE with MatchEvaluator below: # if m.Groups(1).Value<>"" then # Return "<font color=" & m.Groups(1).Value & ">" # else # Return "<font>" # end if |
217+
| regexplib/markup.js:24:39:24:41 | \\s+ | Strings starting with '&lt;A' and with many repetitions of ' - != ' can start matching anywhere after the start of the preceeding \\s* |
218+
| regexplib/markup.js:24:43:24:45 | \\S+ | Strings starting with '&lt;A ' and with many repetitions of '- !=' can start matching anywhere after the start of the preceeding \\s* |
219+
| regexplib/markup.js:24:48:24:50 | \\s* | Strings starting with '&lt;A !' and with many repetitions of ' =- ! ' can start matching anywhere after the start of the preceeding \\s+ |
220+
| regexplib/markup.js:24:52:24:54 | \\s* | Strings starting with '&lt;A !=' and with many repetitions of '- !=' can start matching anywhere after the start of the preceeding \\S+ |
216221
| regexplib/markup.js:25:11:25:15 | [^>]* | Strings starting with '<A' and with many repetitions of '<A' can start matching anywhere after the start of the preceeding <[a-zA-Z][^>]*\\son\\w+=(\\w+\|'[^']*'\|"[^"]*")[^>]*> |
217222
| regexplib/markup.js:25:45:25:49 | [^>]* | Strings starting with '<A ona=a' and with many repetitions of '0' can start matching anywhere after the start of the preceeding \\w+ |
218223
| regexplib/markup.js:27:3:27:7 | [^>]* | Strings starting with '<' and with many repetitions of '<' can start matching anywhere after the start of the preceeding <[^>]*name[\\s]*=[\\s]*"?[^\\w_]*"?[^>]*> |
@@ -228,6 +233,10 @@
228233
| regexplib/markup.js:44:3:44:7 | [^>]* | Strings starting with '<' and with many repetitions of '<' can start matching anywhere after the start of the preceeding <[^>]*name[\\s]*=[\\s]*"?[^\\w_]*"?[^>]*> |
229234
| regexplib/markup.js:44:34:44:38 | [^>]* | Strings starting with '<name=' and with many repetitions of '\\t' can start matching anywhere after the start of the preceeding [\\s]* |
230235
| regexplib/markup.js:45:6:45:13 | [\\d\\D]*? | Strings starting with '/*' and with many repetitions of 'a/*' can start matching anywhere after the start of the preceeding \\/\\*[\\d\\D]*?\\*\\/ |
236+
| regexplib/markup.js:47:39:47:41 | \\s+ | Strings starting with '&lt;A' and with many repetitions of ' - != ' can start matching anywhere after the start of the preceeding \\s* |
237+
| regexplib/markup.js:47:43:47:45 | \\S+ | Strings starting with '&lt;A ' and with many repetitions of '- !=' can start matching anywhere after the start of the preceeding \\s* |
238+
| regexplib/markup.js:47:48:47:50 | \\s* | Strings starting with '&lt;A !' and with many repetitions of ' =- ! ' can start matching anywhere after the start of the preceeding \\s+ |
239+
| regexplib/markup.js:47:52:47:54 | \\s* | Strings starting with '&lt;A !=' and with many repetitions of '- !=' can start matching anywhere after the start of the preceeding \\S+ |
231240
| regexplib/markup.js:48:6:48:13 | [\\s\\S]*? | Strings starting with '<!--' and with many repetitions of '<!--' can start matching anywhere after the start of the preceeding <!--[\\s\\S]*?--> |
232241
| regexplib/markup.js:53:15:53:19 | [\\w]* | Strings starting with '[a' and with many repetitions of '0' can start matching anywhere after the start of the preceeding \\w+ |
233242
| regexplib/markup.js:56:23:56:25 | \\w+ | Strings with many repetitions of 'a' can start matching anywhere after the start of the preceeding (\\/?(?<step>\\w+))+ |
@@ -300,6 +309,7 @@
300309
| regexplib/strings.js:14:61:14:63 | \\w* | Strings starting with 'AA' and with many repetitions of 'A' can start matching anywhere after the start of the preceeding \\w* |
301310
| regexplib/strings.js:14:107:14:109 | \\w* | Strings starting with 'AAA' and with many repetitions of 'A' can start matching anywhere after the start of the preceeding \\w* |
302311
| regexplib/strings.js:19:31:19:57 | [a-z&#230;&#248;&#229;0-9]+ | Strings starting with '#@' and with many repetitions of '##' can start matching anywhere after the start of the preceeding [a-z&#230;&#248;&#229;0-9]+ |
312+
| regexplib/strings.js:19:69:19:95 | [a-z&#230;&#248;&#229;0-9]+ | Strings starting with '#@#' and with many repetitions of '##' can start matching anywhere after the start of the preceeding [a-z&#230;&#248;&#229;0-9]+ |
303313
| regexplib/strings.js:20:3:20:20 | ((\\\\")\|[^"(\\\\")])+ | Strings starting with '"' and with many repetitions of '\\\\"' can start matching anywhere after the start of the preceeding "((\\\\")\|[^"(\\\\")])+" |
304314
| regexplib/strings.js:21:3:21:7 | [^>]+ | Strings starting with '<' and with many repetitions of '<' can start matching anywhere after the start of the preceeding <[^>]+> |
305315
| regexplib/strings.js:23:3:23:20 | ((\\\\")\|[^"(\\\\")])+ | Strings starting with '"' and with many repetitions of '\\\\"' can start matching anywhere after the start of the preceeding "((\\\\")\|[^"(\\\\")])+" |
@@ -313,8 +323,10 @@
313323
| regexplib/strings.js:40:3:40:5 | \\w+ | Strings with many repetitions of 'a' can start matching anywhere after the start of the preceeding (\\w+)\\s+\\1 |
314324
| regexplib/strings.js:48:3:48:12 | [^\\.\\?\\!]* | Strings with many repetitions of ' ' can start matching anywhere after the start of the preceeding ([^\\.\\?\\!]*)[\\.\\?\\!] |
315325
| regexplib/strings.js:49:3:49:5 | \\S+ | Strings with many repetitions of '!' can start matching anywhere after the start of the preceeding (\\S+)\\x20{2,}(?=\\S+) |
316-
| regexplib/strings.js:53:25:53:33 | [a-z0-9]+ | Strings with many repetitions of '0' can start matching anywhere after the start of the preceeding [a-z0-9]+ |
317-
| regexplib/strings.js:53:65:53:73 | [a-z0-9]+ | Strings with many repetitions of '0' can start matching anywhere after the start of the preceeding [a-z0-9]+ |
326+
| regexplib/strings.js:53:4:53:12 | [a-z0-9]+ | Strings with many repetitions of '0.00' can start matching anywhere after the start of the preceeding [a-z0-9]+ |
327+
| regexplib/strings.js:53:25:53:33 | [a-z0-9]+ | Strings starting with '0' and with many repetitions of '0' can start matching anywhere after the start of the preceeding [a-z0-9]+ |
328+
| regexplib/strings.js:53:44:53:52 | [a-z0-9]+ | Strings with many repetitions of '00' can start matching anywhere after the start of the preceeding [a-z0-9]+ |
329+
| regexplib/strings.js:53:65:53:73 | [a-z0-9]+ | Strings starting with '0' and with many repetitions of '0' can start matching anywhere after the start of the preceeding [a-z0-9]+ |
318330
| regexplib/strings.js:54:20:54:22 | \\w+ | Strings with many repetitions of 'a' can start matching anywhere after the start of the preceeding (NOT)?(\\s*\\(*)\\s*(\\w+)\\s*(=\|<>\|<\|>\|LIKE\|IN)\\s*(\\(([^\\)]*)\\)\|'([^']*)'\|(-?\\d*\\.?\\d+))(\\s*\\)*\\s*)(AND\|OR)? |
319331
| regexplib/strings.js:56:52:56:53 | .+ | Strings starting with 'PRN.' and with many repetitions of '.' can start matching anywhere after the start of the preceeding .* |
320332
| regexplib/strings.js:57:36:57:38 | .*? | Strings starting with '?se[A' and with many repetitions of '?se[Aa' can start matching anywhere after the start of the preceeding (?s)(?:\\e\\[(?:(\\d+);?)*([A-Za-z])(.*?))(?=\\e\\[\|\\z) |
@@ -519,3 +531,6 @@
519531
| tst.js:399:6:399:12 | (d\|dd)* | Strings with many repetitions of 'd' can start matching anywhere after the start of the preceeding ((c\|cc)*\|(d\|dd)*\|(e\|ee)*)f$ |
520532
| tst.js:400:6:401:1 | (e\|ee)* | Strings with many repetitions of 'e' can start matching anywhere after the start of the preceeding ((c\|cc)*\|(d\|dd)*\|(e\|ee)*)f$ |
521533
| tst.js:404:6:405:7 | (g\|gg)* | Strings with many repetitions of 'g' can start matching anywhere after the start of the preceeding (g\|gg)*h$ |
534+
| tst.js:407:128:407:129 | * | Strings starting with '0/*' and with many repetitions of ' ' can start matching anywhere after the start of the preceeding \\s* |
535+
| tst.js:409:23:409:29 | [\\w.-]* | Strings starting with '//' and with many repetitions of '//' can start matching anywhere after the start of the preceeding (\\/(?:\\/[\\w.-]*)*){0,1}:([\\w.-]+) |
536+
| tst.js:411:15:411:19 | a{1,} | Strings with many repetitions of 'a' can start matching anywhere after the start of the preceeding (a{1,})* |

0 commit comments

Comments
 (0)