Skip to content

Commit c8c15a0

Browse files
authored
Merge pull request #17910 from Napalys/napalys/matchAll-support
JS: Support for matchAll
2 parents 3fdd35c + 00790bf commit c8c15a0

File tree

16 files changed

+179
-10
lines changed

16 files changed

+179
-10
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
Added support for `String.prototype.matchAll`.

javascript/ql/lib/semmle/javascript/MembershipCandidates.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ module MembershipCandidate {
193193
or
194194
// u.match(/re/) or u.match("re")
195195
base = this and
196-
m = "match" and
196+
m = ["match", "matchAll"] and
197197
enumeration = RegExp::getRegExpFromNode(firstArg)
198198
)
199199
}

javascript/ql/lib/semmle/javascript/Regexp.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -938,7 +938,7 @@ private predicate isMatchObjectProperty(string name) {
938938

939939
/** Holds if `call` is a call to `match` whose result is used in a way that is incompatible with Match objects. */
940940
private predicate isUsedAsNonMatchObject(DataFlow::MethodCallNode call) {
941-
call.getMethodName() = "match" and
941+
call.getMethodName() = ["match", "matchAll"] and
942942
call.getNumArgument() = 1 and
943943
(
944944
// Accessing a property that is absent on Match objects
@@ -996,7 +996,7 @@ predicate isInterpretedAsRegExp(DataFlow::Node source) {
996996
not isNativeStringMethod(func, methodName)
997997
)
998998
|
999-
methodName = "match" and
999+
methodName = ["match", "matchAll"] and
10001000
source = mce.getArgument(0) and
10011001
mce.getNumArgument() = 1 and
10021002
not isUsedAsNonMatchObject(mce)

javascript/ql/lib/semmle/javascript/StringOps.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,7 @@ module StringOps {
722722
}
723723

724724
private class MatchCall extends DataFlow::MethodCallNode {
725-
MatchCall() { this.getMethodName() = "match" }
725+
MatchCall() { this.getMethodName() = ["match", "matchAll"] }
726726
}
727727

728728
private class ExecCall extends DataFlow::MethodCallNode {

javascript/ql/lib/semmle/javascript/dataflow/TaintTracking.qll

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,7 @@ module TaintTracking {
716716

717717
pragma[nomagic]
718718
private DataFlow::MethodCallNode matchMethodCall() {
719-
result.getMethodName() = "match" and
719+
result.getMethodName() = ["match", "matchAll"] and
720720
exists(DataFlow::AnalyzedNode analyzed |
721721
pragma[only_bind_into](analyzed) = result.getArgument(0).analyze() and
722722
analyzed.getAType() = TTRegExp()
@@ -904,7 +904,7 @@ module TaintTracking {
904904
*/
905905
private ControlFlowNode getACaptureSetter(DataFlow::Node input) {
906906
exists(DataFlow::MethodCallNode call | result = call.asExpr() |
907-
call.getMethodName() = ["search", "replace", "replaceAll", "match"] and
907+
call.getMethodName() = ["search", "replace", "replaceAll", "match", "matchAll"] and
908908
input = call.getReceiver()
909909
or
910910
call.getMethodName() = ["test", "exec"] and input = call.getArgument(0)
@@ -985,7 +985,7 @@ module TaintTracking {
985985
or
986986
// u.match(/re/) or u.match("re")
987987
base = expr and
988-
m = "match" and
988+
m = ["match", "matchAll"] and
989989
RegExp::isGenericRegExpSanitizer(RegExp::getRegExpFromNode(firstArg.flow()),
990990
sanitizedOutcome)
991991
)

javascript/ql/src/Security/CWE-020/MissingRegExpAnchor.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ private module Impl implements
3535
|
3636
name = "replace"
3737
or
38-
name = "match" and exists(mcn.getAPropertyRead())
38+
name = ["match", "matchAll"] and exists(mcn.getAPropertyRead())
3939
)
4040
)
4141
}

javascript/ql/test/experimental/Security/CWE-918/SSRF.expected

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ nodes
5151
| check-regex.js:41:13:41:43 | "test.c ... tainted |
5252
| check-regex.js:41:27:41:43 | req.query.tainted |
5353
| check-regex.js:41:27:41:43 | req.query.tainted |
54+
| check-regex.js:61:15:61:42 | baseURL ... tainted |
55+
| check-regex.js:61:15:61:42 | baseURL ... tainted |
56+
| check-regex.js:61:25:61:42 | req.params.tainted |
57+
| check-regex.js:61:25:61:42 | req.params.tainted |
5458
| check-validator.js:15:15:15:45 | "test.c ... tainted |
5559
| check-validator.js:15:15:15:45 | "test.c ... tainted |
5660
| check-validator.js:15:29:15:45 | req.query.tainted |
@@ -127,6 +131,10 @@ edges
127131
| check-regex.js:41:27:41:43 | req.query.tainted | check-regex.js:41:13:41:43 | "test.c ... tainted |
128132
| check-regex.js:41:27:41:43 | req.query.tainted | check-regex.js:41:13:41:43 | "test.c ... tainted |
129133
| check-regex.js:41:27:41:43 | req.query.tainted | check-regex.js:41:13:41:43 | "test.c ... tainted |
134+
| check-regex.js:61:25:61:42 | req.params.tainted | check-regex.js:61:15:61:42 | baseURL ... tainted |
135+
| check-regex.js:61:25:61:42 | req.params.tainted | check-regex.js:61:15:61:42 | baseURL ... tainted |
136+
| check-regex.js:61:25:61:42 | req.params.tainted | check-regex.js:61:15:61:42 | baseURL ... tainted |
137+
| check-regex.js:61:25:61:42 | req.params.tainted | check-regex.js:61:15:61:42 | baseURL ... tainted |
130138
| check-validator.js:15:29:15:45 | req.query.tainted | check-validator.js:15:15:15:45 | "test.c ... tainted |
131139
| check-validator.js:15:29:15:45 | req.query.tainted | check-validator.js:15:15:15:45 | "test.c ... tainted |
132140
| check-validator.js:15:29:15:45 | req.query.tainted | check-validator.js:15:15:15:45 | "test.c ... tainted |
@@ -166,6 +174,7 @@ edges
166174
| check-regex.js:31:15:31:45 | "test.c ... tainted | check-regex.js:31:29:31:45 | req.query.tainted | check-regex.js:31:15:31:45 | "test.c ... tainted | The URL of this request depends on a user-provided value. |
167175
| check-regex.js:34:15:34:42 | baseURL ... tainted | check-regex.js:34:25:34:42 | req.params.tainted | check-regex.js:34:15:34:42 | baseURL ... tainted | The URL of this request depends on a user-provided value. |
168176
| check-regex.js:41:13:41:43 | "test.c ... tainted | check-regex.js:41:27:41:43 | req.query.tainted | check-regex.js:41:13:41:43 | "test.c ... tainted | The URL of this request depends on a user-provided value. |
177+
| check-regex.js:61:15:61:42 | baseURL ... tainted | check-regex.js:61:25:61:42 | req.params.tainted | check-regex.js:61:15:61:42 | baseURL ... tainted | The URL of this request depends on a user-provided value. |
169178
| check-validator.js:15:15:15:45 | "test.c ... tainted | check-validator.js:15:29:15:45 | req.query.tainted | check-validator.js:15:15:15:45 | "test.c ... tainted | The URL of this request depends on a user-provided value. |
170179
| check-validator.js:27:15:27:45 | "test.c ... tainted | check-validator.js:27:29:27:45 | req.query.tainted | check-validator.js:27:15:27:45 | "test.c ... tainted | The URL of this request depends on a user-provided value. |
171180
| check-validator.js:50:15:50:45 | "test.c ... tainted | check-validator.js:50:29:50:45 | req.query.tainted | check-validator.js:50:15:50:45 | "test.c ... tainted | The URL of this request depends on a user-provided value. |

javascript/ql/test/experimental/Security/CWE-918/check-regex.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ app.get('/check-with-axios', req => {
2525
} else {
2626
axios.get(baseURL + req.params.tainted); // OK
2727
}
28-
28+
2929
// Blacklists are not safe
3030
if (!req.query.tainted.match(/^[/\.%]+$/)) {
3131
axios.get("test.com/" + req.query.tainted); // SSRF
@@ -39,8 +39,29 @@ app.get('/check-with-axios', req => {
3939
}
4040

4141
axios.get("test.com/" + req.query.tainted); // OK - False Positive
42+
43+
if (req.query.tainted.matchAll(/^[0-9a-z]+$/g)) { // letters and numbers
44+
axios.get("test.com/" + req.query.tainted); // OK
45+
}
46+
if (req.query.tainted.matchAll(/^[0-9a-z\-_]+$/g)) { // letters, numbers, - and _
47+
axios.get("test.com/" + req.query.tainted); // OK
48+
}
4249
});
4350

4451
const isValidPath = path => path.match(/^[0-9a-z]+$/);
4552

4653
const isInBlackList = path => path.match(/^[/\.%]+$/);
54+
55+
app.get('/check-with-axios', req => {
56+
const baseURL = "test.com/"
57+
if (isValidPathMatchAll(req.params.tainted) ) {
58+
axios.get(baseURL + req.params.tainted); // OK
59+
}
60+
if (!isValidPathMatchAll(req.params.tainted) ) {
61+
axios.get(baseURL + req.params.tainted); // NOT OK - SSRF
62+
} else {
63+
axios.get(baseURL + req.params.tainted); // OK
64+
}
65+
});
66+
67+
const isValidPathMatchAll = path => path.matchAll(/^[0-9a-z]+$/g);

javascript/ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegExp/IncompleteHostnameRegExp.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@
2525
| tst-IncompleteHostnameRegExp.js:53:14:53:35 | test.example.com$ | This regular expression has an unescaped '.' before 'example.com', so it might match more hosts than expected. | tst-IncompleteHostnameRegExp.js:53:13:53:36 | 'test.' ... e.com$' | here |
2626
| tst-IncompleteHostnameRegExp.js:55:14:55:38 | ^http://test.example.com | This regular expression has an unescaped '.' before 'example.com', so it might match more hosts than expected. | tst-IncompleteHostnameRegExp.js:55:13:55:39 | '^http: ... le.com' | here |
2727
| tst-IncompleteHostnameRegExp.js:59:5:59:20 | foo.example\\.com | This regular expression has an unescaped '.' before 'example\\.com', so it might match more hosts than expected. | tst-IncompleteHostnameRegExp.js:59:2:59:32 | /^(foo. ... ever)$/ | here |
28+
| tst-IncompleteHostnameRegExp.js:61:18:61:41 | ^http://test.example.com | This regular expression has an unescaped '.' before 'example.com', so it might match more hosts than expected. | tst-IncompleteHostnameRegExp.js:61:17:61:42 | "^http: ... le.com" | here |

javascript/ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegExp/tst-IncompleteHostnameRegExp.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,6 @@
5757
/^http:\/\/(..|...)\.example\.com\/index\.html/; // OK, wildcards are intentional
5858
/^http:\/\/.\.example\.com\/index\.html/; // OK, the wildcard is intentional
5959
/^(foo.example\.com|whatever)$/; // kinda OK - one disjunction doesn't even look like a hostname
60+
61+
if (s.matchAll("^http://test.example.com")) {} // NOT OK
6062
});

0 commit comments

Comments
 (0)