Skip to content

Commit c85f817

Browse files
authored
Merge pull request #4579 from erik-krogh/redos
Approved by asgerf
2 parents 2f20486 + 342b6a4 commit c85f817

File tree

6 files changed

+219
-8
lines changed

6 files changed

+219
-8
lines changed

javascript/ql/src/Performance/PolynomialReDoS.ql

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,15 @@
1414

1515
import javascript
1616
import semmle.javascript.security.performance.PolynomialReDoS::PolynomialReDoS
17+
import semmle.javascript.security.performance.SuperlinearBackTracking
1718
import DataFlow::PathGraph
1819

1920
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
20-
where cfg.hasFlowPath(source, sink)
21+
where
22+
cfg.hasFlowPath(source, sink) and
23+
not (
24+
source.getNode().(Source).getKind() = "url" and
25+
sink.getNode().(Sink).getRegExp().(PolynomialBackTrackingTerm).isAtEndLine()
26+
)
2127
select sink.getNode(), source, sink, "This expensive $@ use depends on $@.",
2228
sink.getNode().(Sink).getRegExp(), "regular expression", source.getNode(), "a user-provided value"

javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoSCustomizations.qll

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ module PolynomialReDoS {
1111
/**
1212
* A data flow source node for polynomial regular expression denial-of-service vulnerabilities.
1313
*/
14-
abstract class Source extends DataFlow::Node { }
14+
abstract class Source extends DataFlow::Node {
15+
/**
16+
* Gets the kind of source that is being accesed. See `HTTP::RequestInputAccess::getKind()`.
17+
* Can be one of "parameter", "header", "body", "url", "cookie".
18+
*/
19+
abstract string getKind();
20+
}
1521

1622
/**
1723
* A data flow sink node for polynomial regular expression denial-of-service vulnerabilities.
@@ -31,6 +37,8 @@ module PolynomialReDoS {
3137
*/
3238
class RequestInputAccessAsSource extends Source {
3339
RequestInputAccessAsSource() { this instanceof HTTP::RequestInputAccess }
40+
41+
override string getKind() { result = this.(HTTP::RequestInputAccess).getKind() }
3442
}
3543

3644
/**

javascript/ql/src/semmle/javascript/security/performance/SuperlinearBackTracking.qll

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,20 @@ class PolynomialBackTrackingTerm extends InfiniteRepetitionQuantifier {
124124
forall(RegExpTerm pred | pred = this.getPredecessor+() | matchesEpsilon(pred)) and
125125
reason = "it can start matching anywhere"
126126
or
127-
exists(InfiniteRepetitionQuantifier pred |
128-
pred = getAMatchPredecessor(this.getPredecessor()) and
129-
compatible(pred.getAChild(), this.getAChild())
127+
exists(RegExpTerm pred |
128+
pred instanceof InfiniteRepetitionQuantifier
129+
or
130+
forall(RegExpTerm predpred | predpred = pred.getPredecessor+() | matchesEpsilon(predpred))
130131
|
132+
pred = getAMatchPredecessor(this.getPredecessor()) and
133+
(
134+
// compatible children
135+
compatible(pred.getAChild(), this.getAChild())
136+
or
137+
// or `this` is compatible with everything (and the predecessor is something)
138+
unique( | | this.getAChild()) instanceof RegExpDot and
139+
exists([pred, pred.getAChild()].getAMatchedString())
140+
) and
131141
reason =
132142
"it can start matching anywhere after the start of the preceeding '" + pred.toString() +
133143
"'"
@@ -136,6 +146,15 @@ class PolynomialBackTrackingTerm extends InfiniteRepetitionQuantifier {
136146
not this.getParent*() instanceof RegExpSubPattern // too many corner cases
137147
}
138148

149+
/**
150+
* Holds if all non-empty successors to the polynomial backtracking term matches the end of the line.
151+
*/
152+
predicate isAtEndLine() {
153+
forall(RegExpTerm succ | this.getSuccessor+() = succ and not matchesEpsilon(succ) |
154+
succ instanceof RegExpDollar
155+
)
156+
}
157+
139158
/**
140159
* Gets the reason for the number of match attempts.
141160
*/

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,46 @@
1212
| polynomial-redos.js:22:57:22:59 | \\d+ | it can start matching anywhere after the start of the preceeding '\\d*' |
1313
| polynomial-redos.js:22:57:22:59 | \\d+ | it can start matching anywhere after the start of the preceeding '\\d+' |
1414
| polynomial-redos.js:25:37:25:56 | [a-zA-Z0-9+\\/ \\t\\n]+ | it can start matching anywhere after the start of the preceeding '[ \\t]+' |
15+
| polynomial-redos.js:25:63:25:64 | .* | it can start matching anywhere after the start of the preceeding '[=]*' |
16+
| polynomial-redos.js:25:63:25:64 | .* | it can start matching anywhere after the start of the preceeding '[a-zA-Z0-9+\\/ \\t\\n]+' |
1517
| polynomial-redos.js:27:14:27:22 | [A-Z]{2,} | it can start matching anywhere |
1618
| polynomial-redos.js:30:19:30:22 | [?]+ | it can start matching anywhere |
19+
| polynomial-redos.js:30:23:30:24 | .* | it can start matching anywhere after the start of the preceeding '[?]' |
20+
| polynomial-redos.js:30:23:30:24 | .* | it can start matching anywhere after the start of the preceeding '[?]+' |
1721
| polynomial-redos.js:31:42:31:43 | -+ | it can start matching anywhere |
1822
| polynomial-redos.js:32:45:32:47 | \\n* | it can start matching anywhere |
1923
| polynomial-redos.js:33:17:33:20 | (.)* | it can start matching anywhere |
24+
| polynomial-redos.js:36:18:36:19 | .* | it can start matching anywhere after the start of the preceeding '<' |
25+
| polynomial-redos.js:37:18:37:19 | .* | it can start matching anywhere after the start of the preceeding '<' |
26+
| polynomial-redos.js:38:18:38:19 | .* | it can start matching anywhere after the start of the preceeding '<' |
2027
| polynomial-redos.js:48:22:48:24 | \\s* | it can start matching anywhere |
28+
| polynomial-redos.js:50:4:50:5 | .* | it can start matching anywhere after the start of the preceeding 'Y' |
29+
| polynomial-redos.js:51:11:51:17 | (YH\|J)* | it can start matching anywhere after the start of the preceeding '(YH\|K)' |
30+
| polynomial-redos.js:51:11:51:17 | (YH\|J)* | it can start matching anywhere after the start of the preceeding 'YH\|K' |
31+
| polynomial-redos.js:52:12:52:13 | .* | it can start matching anywhere after the start of the preceeding '(YH\|K)' |
32+
| polynomial-redos.js:52:12:52:13 | .* | it can start matching anywhere after the start of the preceeding 'K' |
33+
| polynomial-redos.js:52:12:52:13 | .* | it can start matching anywhere after the start of the preceeding 'YH' |
34+
| polynomial-redos.js:52:12:52:13 | .* | it can start matching anywhere after the start of the preceeding 'YH\|K' |
35+
| polynomial-redos.js:53:3:53:8 | (B\|Y)+ | it can start matching anywhere |
36+
| polynomial-redos.js:53:9:53:12 | (Y)* | it can start matching anywhere after the start of the preceeding '(B\|Y)' |
37+
| polynomial-redos.js:53:9:53:12 | (Y)* | it can start matching anywhere after the start of the preceeding '(B\|Y)+' |
38+
| polynomial-redos.js:53:9:53:12 | (Y)* | it can start matching anywhere after the start of the preceeding 'B\|Y' |
39+
| polynomial-redos.js:54:4:54:9 | (B\|Y)+ | it can start matching anywhere |
40+
| polynomial-redos.js:55:11:55:14 | (Y)* | it can start matching anywhere after the start of the preceeding '(B\|Y)+' |
41+
| polynomial-redos.js:56:10:56:13 | (Y)* | it can start matching anywhere after the start of the preceeding '(B\|Y)+' |
42+
| polynomial-redos.js:57:11:57:16 | (Y\|K)* | it can start matching anywhere after the start of the preceeding '(B\|Y)+' |
43+
| polynomial-redos.js:58:11:58:12 | .* | it can start matching anywhere after the start of the preceeding '(B\|Y)+' |
44+
| polynomial-redos.js:62:7:62:8 | Y* | it can start matching anywhere after the start of the preceeding 'Y*' |
45+
| polynomial-redos.js:63:11:63:12 | Y* | it can start matching anywhere after the start of the preceeding '(K\|Y)+' |
46+
| polynomial-redos.js:64:14:64:15 | Y* | it can start matching anywhere after the start of the preceeding '(K\|Y)+' |
47+
| polynomial-redos.js:65:14:65:15 | .* | it can start matching anywhere after the start of the preceeding '(K\|Y)+' |
48+
| polynomial-redos.js:66:9:66:10 | .* | it can start matching anywhere after the start of the preceeding '(K\|Y)' |
49+
| polynomial-redos.js:66:9:66:10 | .* | it can start matching anywhere after the start of the preceeding 'K' |
50+
| polynomial-redos.js:66:9:66:10 | .* | it can start matching anywhere after the start of the preceeding 'K\|Y' |
51+
| polynomial-redos.js:66:9:66:10 | .* | it can start matching anywhere after the start of the preceeding 'Y' |
52+
| polynomial-redos.js:67:8:67:9 | .* | it can start matching anywhere after the start of the preceeding '[^Y]' |
53+
| polynomial-redos.js:68:8:68:9 | .* | it can start matching anywhere after the start of the preceeding '[^Y]' |
54+
| polynomial-redos.js:69:8:69:9 | .* | it can start matching anywhere after the start of the preceeding '[^Y]' |
2155
| regexplib/address.js:18:26:18:31 | [ \\w]* | it can start matching anywhere after the start of the preceeding '[ \\w]{3,}' |
2256
| regexplib/address.js:20:144:20:147 | [ ]+ | it can start matching anywhere after the start of the preceeding '[a-zA-Z0-9 \\-.]{6,}' |
2357
| regexplib/address.js:24:26:24:31 | [ \\w]* | it can start matching anywhere after the start of the preceeding '[ \\w]{3,}' |
@@ -36,7 +70,11 @@
3670
| regexplib/address.js:75:631:75:635 | \\x20* | it can start matching anywhere after the start of the preceeding '\\x20*' |
3771
| regexplib/address.js:75:796:75:798 | \\s+ | it can start matching anywhere after the start of the preceeding '\\s+' |
3872
| regexplib/address.js:85:15:85:49 | ([0-9]\|[ ]\|[-]\|[\\(]\|[\\)]\|ext.\|[,])+ | it can start matching anywhere |
73+
| regexplib/address.js:85:51:85:67 | ([ ]\|[:]\|\\t\|[-])* | it can start matching anywhere after the start of the preceeding '([0-9]\|[ ]\|[-]\|[\\(]\|[\\)]\|ext.\|[,])' |
3974
| regexplib/address.js:85:51:85:67 | ([ ]\|[:]\|\\t\|[-])* | it can start matching anywhere after the start of the preceeding '([0-9]\|[ ]\|[-]\|[\\(]\|[\\)]\|ext.\|[,])+' |
75+
| regexplib/address.js:85:51:85:67 | ([ ]\|[:]\|\\t\|[-])* | it can start matching anywhere after the start of the preceeding '[0-9]\|[ ]\|[-]\|[\\(]\|[\\)]\|ext.\|[,]' |
76+
| regexplib/address.js:85:51:85:67 | ([ ]\|[:]\|\\t\|[-])* | it can start matching anywhere after the start of the preceeding '[ ]' |
77+
| regexplib/address.js:85:51:85:67 | ([ ]\|[:]\|\\t\|[-])* | it can start matching anywhere after the start of the preceeding '[-]' |
4078
| regexplib/address.js:93:3:93:5 | \\s* | it can start matching anywhere |
4179
| regexplib/address.js:93:48:93:50 | \\s* | it can start matching anywhere |
4280
| regexplib/address.js:93:93:93:95 | \\s* | it can start matching anywhere |
@@ -50,19 +88,28 @@
5088
| regexplib/email.js:28:73:28:87 | [0-9a-zA-Z'\\.]+ | it can start matching anywhere |
5189
| regexplib/email.js:28:125:28:139 | [0-9a-zA-Z'\\.]+ | it can start matching anywhere |
5290
| regexplib/email.js:29:2:29:7 | [\\w-]+ | it can start matching anywhere |
91+
| regexplib/markup.js:1:11:1:12 | .* | it can start matching anywhere after the start of the preceeding '<' |
5392
| regexplib/markup.js:6:99:6:113 | [\\s\\w\\d\\)\\(\\,]* | it can start matching anywhere after the start of the preceeding '[\\d\\w]+' |
93+
| regexplib/markup.js:11:6:11:8 | .*? | it can start matching anywhere after the start of the preceeding '<!--' |
94+
| regexplib/markup.js:13:14:13:16 | .+? | it can start matching anywhere after the start of the preceeding '<' |
95+
| regexplib/markup.js:14:10:14:11 | .* | it can start matching anywhere after the start of the preceeding '<' |
96+
| regexplib/markup.js:14:13:14:14 | .* | it can start matching anywhere after the start of the preceeding '<' |
5497
| regexplib/markup.js:19:2:19:12 | (<meta\\s+)* | it can start matching anywhere |
5598
| regexplib/markup.js:20:155:20:156 | '+ | it can start matching anywhere after the start of the preceeding ''+' |
5699
| regexplib/markup.js:20:197:20:198 | "+ | it can start matching anywhere after the start of the preceeding '"+' |
57100
| regexplib/markup.js:37:15:37:19 | [\\w]* | it can start matching anywhere after the start of the preceeding '\\w+' |
58101
| regexplib/markup.js:53:15:53:19 | [\\w]* | it can start matching anywhere after the start of the preceeding '\\w+' |
102+
| regexplib/markup.js:62:35:62:37 | .*? | it can start matching anywhere after the start of the preceeding '[\\s\\"\\']+' |
59103
| regexplib/markup.js:62:39:62:45 | [\\"\\']+ | it can start matching anywhere after the start of the preceeding '[\\s\\"\\']+' |
104+
| regexplib/markup.js:62:46:62:48 | .*? | it can start matching anywhere after the start of the preceeding '[\\"\\']+' |
60105
| regexplib/misc.js:76:2:76:27 | (AUX\|PRN\|NUL\|COM\\d\|LPT\\d)+ | it can start matching anywhere |
61106
| regexplib/misc.js:83:15:83:17 | \\d* | it can start matching anywhere after the start of the preceeding '\\d*' |
62107
| regexplib/misc.js:83:69:83:71 | \\d* | it can start matching anywhere after the start of the preceeding '\\d*' |
63108
| regexplib/misc.js:93:3:93:4 | .* | it can start matching anywhere |
109+
| regexplib/misc.js:95:25:95:26 | .+ | it can start matching anywhere after the start of the preceeding 'at\\s' |
64110
| regexplib/misc.js:112:3:112:5 | \\s* | it can start matching anywhere |
65111
| regexplib/misc.js:112:32:112:34 | \\s* | it can start matching anywhere |
112+
| regexplib/misc.js:116:3:116:4 | .* | it can start matching anywhere after the start of the preceeding '{' |
66113
| regexplib/misc.js:119:9:119:11 | \\s* | it can start matching anywhere |
67114
| regexplib/misc.js:119:12:119:14 | \\(* | it can start matching anywhere |
68115
| regexplib/misc.js:119:16:119:18 | \\s* | it can start matching anywhere |
@@ -114,13 +161,17 @@
114161
| regexplib/strings.js:91:2:91:7 | (\\S*)+ | it can start matching anywhere |
115162
| regexplib/strings.js:91:3:91:5 | \\S* | it can start matching anywhere |
116163
| regexplib/uri.js:2:45:2:66 | [\\w\\-\\.,@?^=%&:/~\\+#]* | it can start matching anywhere after the start of the preceeding '[\\w\\-_]+' |
164+
| regexplib/uri.js:5:42:5:43 | .* | it can start matching anywhere after the start of the preceeding '[\\w ]*' |
117165
| regexplib/uri.js:13:69:13:102 | [a-zA-Z0-9\\-\\.\\?\\,\\'\\/\\\\\\+&%\\$#_]* | it can start matching anywhere after the start of the preceeding '[a-zA-Z0-9\\-\\._]+' |
166+
| regexplib/uri.js:17:42:17:43 | .* | it can start matching anywhere after the start of the preceeding '[\\w ]*' |
118167
| regexplib/uri.js:18:47:18:96 | ([ A-Za-z0-9'~` !@#$%&^_+=\\(\\){},\\-\\[\\];]\|([.]))*? | it can start matching anywhere after the start of the preceeding '([A-Za-z0-9'~`!@#$%&^_+=\\(\\){},\\-\\[\\]\\;])+?' |
119168
| regexplib/uri.js:18:148:18:189 | ([A-Za-z0-9'~`!@#$%&^_+=\\(\\){},\\-\\[ \\];])+ | it can start matching anywhere after the start of the preceeding '[ A-Za-z0-9'~`!@#$ %&^_+=\\(\\){},\\-\\[\\]\\;]*?' |
120169
| regexplib/uri.js:23:2:23:74 | (((file\|gopher\|news\|nntp\|telnet\|http\|ftp\|https\|ftps\|sftp):\\/\\/)\|(www\\.))+ | it can start matching anywhere |
170+
| regexplib/uri.js:23:77:23:92 | [a-zA-Z0-9\\._-]+ | it can start matching anywhere after the start of the preceeding 'www\\.' |
121171
| regexplib/uri.js:28:2:28:13 | [a-zA-Z]{3,} | it can start matching anywhere |
122172
| regexplib/uri.js:29:2:29:45 | ((http\\:\\/\\/\|https\\:\\/\\/\|ftp\\:\\/\\/)\|(www.))+ | it can start matching anywhere |
123173
| regexplib/uri.js:34:3:34:9 | [^\\=&]+ | it can start matching anywhere |
174+
| regexplib/uri.js:39:7:39:9 | .*? | it can start matching anywhere after the start of the preceeding '<a' |
124175
| regexplib/uri.js:44:2:44:4 | .*? | it can start matching anywhere |
125176
| regexplib/uri.js:53:3:53:9 | [^\\=&]+ | it can start matching anywhere |
126177
| regexplib/uri.js:58:2:58:45 | ((http\\:\\/\\/\|https\\:\\/\\/\|ftp\\:\\/\\/)\|(www.))+ | it can start matching anywhere |
@@ -129,8 +180,12 @@
129180
| tst.js:14:13:14:18 | (.*,)+ | it can start matching anywhere |
130181
| tst.js:14:14:14:15 | .* | it can start matching anywhere |
131182
| tst.js:47:15:47:37 | (?:[^"']\|".*?"\|'.*?')*? | it can start matching anywhere |
183+
| tst.js:47:25:47:27 | .*? | it can start matching anywhere after the start of the preceeding '"' |
184+
| tst.js:47:31:47:33 | .*? | it can start matching anywhere after the start of the preceeding ''' |
132185
| tst.js:66:15:66:44 | ([\\w#:.~>+()\\s-]+\|\\*\|\\[.*?\\])+ | it can start matching anywhere |
133186
| tst.js:66:16:66:31 | [\\w#:.~>+()\\s-]+ | it can start matching anywhere |
187+
| tst.js:66:38:66:40 | .*? | it can start matching anywhere after the start of the preceeding '\\[' |
188+
| tst.js:66:46:66:48 | \\s* | it can start matching anywhere after the start of the preceeding '[\\w#:.~>+()\\s-]' |
134189
| tst.js:66:46:66:48 | \\s* | it can start matching anywhere after the start of the preceeding '[\\w#:.~>+()\\s-]+' |
135190
| tst.js:74:14:74:21 | (b\|a?b)* | it can start matching anywhere |
136191
| tst.js:77:14:77:21 | (a\|aa?)* | it can start matching anywhere |

0 commit comments

Comments
 (0)