Skip to content

Commit b090153

Browse files
committed
add support for String.prototype.replaceAll
1 parent 0dbdbfa commit b090153

File tree

17 files changed

+41
-36
lines changed

17 files changed

+41
-36
lines changed

javascript/ql/src/Expressions/StringInsteadOfRegex.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ predicate isStringSplitOrReplace(MethodCallExpr mce) {
3131
mce.getMethodName() = name and
3232
mce.getNumArgument() = arity
3333
|
34-
name = "replace" and arity = 2
34+
name = ["replace", "replaceAll"] and arity = 2
3535
or
3636
name = "split" and
3737
(arity = 1 or arity = 2)

javascript/ql/src/RegExp/IdentityReplacement.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ predicate regExpMatchesString(RegExpTerm t, string s) {
7070

7171
from MethodCallExpr repl, string s, string friendly
7272
where
73-
repl.getMethodName() = "replace" and
73+
repl.getMethodName() = ["replace", "replaceAll"] and
7474
matchesString(repl.getArgument(0), s) and
7575
repl.getArgument(1).getStringValue() = s and
7676
(if s = "" then friendly = "the empty string" else friendly = "'" + s + "'")

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ DataFlow::Node schemeCheck(DataFlow::Node nd, DangerousScheme scheme) {
7575
exists(DataFlow::MethodCallNode stringop |
7676
stringop.getMethodName().matches("trim%") or
7777
stringop.getMethodName().matches("to%Case") or
78-
stringop.getMethodName() = "replace"
78+
stringop.getMethodName() = ["replace", "replaceAll"]
7979
|
8080
result = schemeCheck(stringop, scheme) and
8181
nd = stringop.getReceiver()

javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ predicate allBackslashesEscaped(DataFlow::Node nd) {
7979
// flow through string methods
8080
exists(DataFlow::MethodCallNode mc, string m |
8181
m = "replace" or
82+
m = "replaceAll" or
8283
m = "slice" or
8384
m = "substr" or
8485
m = "substring" or

javascript/ql/src/semmle/javascript/StandardLibrary.qll

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ private class IteratorExceptionStep extends DataFlow::MethodCallNode, DataFlow::
102102
*/
103103
class StringReplaceCall extends DataFlow::MethodCallNode {
104104
StringReplaceCall() {
105-
getMethodName() = "replace" and
105+
getMethodName() = ["replace", "replaceAll"] and
106106
(getNumArgument() = 2 or getReceiver().mayHaveStringValue(_))
107107
}
108108

@@ -128,9 +128,9 @@ class StringReplaceCall extends DataFlow::MethodCallNode {
128128

129129
/**
130130
* Holds if this is a global replacement, that is, the first argument is a regular expression
131-
* with the `g` flag.
131+
* with the `g` flag, or this is a call to `.replaceAll()`.
132132
*/
133-
predicate isGlobal() { getRegExp().isGlobal() }
133+
predicate isGlobal() { getRegExp().isGlobal() or getMethodName() = "replaceAll" }
134134

135135
/**
136136
* Holds if this call to `replace` replaces `old` with `new`.

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ module TaintTracking {
427427
name = "padStart" or
428428
name = "repeat" or
429429
name = "replace" or
430+
name = "replaceAll" or
430431
name = "slice" or
431432
name = "small" or
432433
name = "split" or
@@ -452,7 +453,7 @@ module TaintTracking {
452453
exists(int i | pred.asExpr() = succ.getAstNode().(MethodCallExpr).getArgument(i) |
453454
name = "concat"
454455
or
455-
name = "replace" and i = 1
456+
name = ["replace", "replaceAll"] and i = 1
456457
)
457458
)
458459
or
@@ -707,7 +708,8 @@ module TaintTracking {
707708
*/
708709
private ControlFlowNode getACaptureSetter(DataFlow::Node input) {
709710
exists(DataFlow::MethodCallNode call | result = call.asExpr() |
710-
call.getMethodName() = ["search", "replace", "match"] and input = call.getReceiver()
711+
call.getMethodName() = ["search", "replace", "replaceAll", "match"] and
712+
input = call.getReceiver()
711713
or
712714
call.getMethodName() = ["test", "exec"] and input = call.getArgument(0)
713715
)

javascript/ql/src/semmle/javascript/heuristics/AdditionalTaintSteps.qll

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@ abstract class HeuristicAdditionalTaintStep extends DataFlow::ValueNode { }
1515
* A call to `tainted.replace(x, y)` that preserves taint.
1616
*/
1717
private class HeuristicStringManipulationTaintStep extends HeuristicAdditionalTaintStep,
18-
TaintTracking::AdditionalTaintStep, DataFlow::MethodCallNode {
19-
HeuristicStringManipulationTaintStep() { getMethodName() = "replace" }
20-
18+
TaintTracking::AdditionalTaintStep, StringReplaceCall {
2119
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
2220
pred = getReceiver() and succ = this
2321
}

javascript/ql/src/semmle/javascript/security/dataflow/CleartextLoggingCustomizations.qll

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,11 @@ module CleartextLogging {
3434
/**
3535
* A call to `.replace()` that seems to mask sensitive information.
3636
*/
37-
class MaskingReplacer extends Barrier, DataFlow::MethodCallNode {
37+
class MaskingReplacer extends Barrier, StringReplaceCall {
3838
MaskingReplacer() {
39-
this.getCalleeName() = "replace" and
40-
exists(RegExpLiteral reg |
41-
reg = this.getArgument(0).getALocalSource().asExpr() and
42-
reg.isGlobal() and
43-
any(RegExpDot term).getLiteral() = reg
44-
) and
45-
exists(this.getArgument(1).getStringValue())
39+
this.isGlobal() and
40+
exists(this.getRawReplacement().getStringValue()) and
41+
any(RegExpDot term).getLiteral() = getRegExp().asExpr()
4642
}
4743
}
4844

javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -218,12 +218,12 @@ module TaintedPath {
218218
output = this
219219
or
220220
// non-global replace or replace of something other than /\.\./g, /[/]/g, or /[\.]/g.
221-
this.getCalleeName() = "replace" and
221+
this instanceof StringReplaceCall and
222222
input = getReceiver() and
223223
output = this and
224224
not exists(RegExpLiteral literal, RegExpTerm term |
225-
getArgument(0).getALocalSource().asExpr() = literal and
226-
literal.isGlobal() and
225+
this.(StringReplaceCall).getRegExp().asExpr() = literal and
226+
this.(StringReplaceCall).isGlobal() and
227227
literal.getRoot() = term
228228
|
229229
term.getAMatchedString() = "/" or
@@ -247,16 +247,15 @@ module TaintedPath {
247247
/**
248248
* A call that removes all instances of "../" in the prefix of the string.
249249
*/
250-
class DotDotSlashPrefixRemovingReplace extends DataFlow::CallNode {
250+
class DotDotSlashPrefixRemovingReplace extends StringReplaceCall {
251251
DataFlow::Node input;
252252
DataFlow::Node output;
253253

254254
DotDotSlashPrefixRemovingReplace() {
255-
this.getCalleeName() = "replace" and
256255
input = getReceiver() and
257256
output = this and
258257
exists(RegExpLiteral literal, RegExpTerm term |
259-
getArgument(0).getALocalSource().asExpr() = literal and
258+
getRegExp().asExpr() = literal and
260259
(term instanceof RegExpStar or term instanceof RegExpPlus) and
261260
term.getChild(0) = getADotDotSlashMatcher()
262261
|
@@ -298,17 +297,16 @@ module TaintedPath {
298297
/**
299298
* A call that removes all "." or ".." from a path, without also removing all forward slashes.
300299
*/
301-
class DotRemovingReplaceCall extends DataFlow::CallNode {
300+
class DotRemovingReplaceCall extends StringReplaceCall {
302301
DataFlow::Node input;
303302
DataFlow::Node output;
304303

305304
DotRemovingReplaceCall() {
306-
this.getCalleeName() = "replace" and
307305
input = getReceiver() and
308306
output = this and
307+
isGlobal() and
309308
exists(RegExpLiteral literal, RegExpTerm term |
310-
getArgument(0).getALocalSource().asExpr() = literal and
311-
literal.isGlobal() and
309+
getRegExp().asExpr() = literal and
312310
literal.getRoot() = term and
313311
not term.getAMatchedString() = "/"
314312
|

javascript/ql/src/semmle/javascript/security/dataflow/UnsafeJQueryPlugin.qll

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,9 @@ module UnsafeJQueryPlugin {
3939
StringConcatenation::taintStep(pred, succ, _, any(int i | i >= 1))
4040
or
4141
// prefixing through a poor-mans templating system:
42-
exists(DataFlow::MethodCallNode replace |
42+
exists(StringReplaceCall replace |
4343
replace = succ and
44-
pred = replace.getArgument(1) and
45-
replace.getMethodName() = "replace"
44+
pred = replace.getRawReplacement()
4645
)
4746
}
4847

0 commit comments

Comments
 (0)