JS: Port String#replace to flow summary

This commit is contained in:
Asger F
2023-10-03 15:52:49 +02:00
parent f0c2afe39e
commit da3a0de814
3 changed files with 93 additions and 11 deletions

View File

@@ -409,18 +409,27 @@ module TaintTracking {
]).getACall() and
pred = c.getArgument(0)
)
)
}
}
/**
* A taint propagating edge for the string `replace` function.
*
* This is a legacy step as it crosses a function boundary, and would thus be converted to a jump step.
*/
private class ReplaceCallbackSteps extends LegacyTaintStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
// In and out of .replace callbacks
exists(StringReplaceCall call |
// Into the callback if the regexp does not sanitize matches
hasWildcardReplaceRegExp(call) and
pred = call.getReceiver() and
succ = call.getReplacementCallback().getParameter(0)
or
// In and out of .replace callbacks
exists(StringReplaceCall call |
// Into the callback if the regexp does not sanitize matches
hasWildcardReplaceRegExp(call) and
pred = call.getReceiver() and
succ = call.getReplacementCallback().getParameter(0)
or
// Out of the callback
pred = call.getReplacementCallback().getReturnNode() and
succ = call
)
// Out of the callback
pred = call.getReplacementCallback().getReturnNode() and
succ = call
)
}
}

View File

@@ -4,3 +4,4 @@ private import AsyncAwait
private import Maps2
private import Promises2
private import Sets2
private import Strings2

View File

@@ -0,0 +1,72 @@
/**
* Contains flow summaries and steps modelling flow through string methods.
*/
private import javascript
private import semmle.javascript.dataflow.FlowSummary
/** Holds if the given call takes a regexp containing a wildcard. */
pragma[noinline]
private predicate hasWildcardReplaceRegExp(StringReplaceCall call) {
RegExp::isWildcardLike(call.getRegExp().getRoot().getAChild*())
}
/**
* Summary for calls to `.replace` or `.replaceAll` (without a regexp pattern containing a wildcard).
*/
private class StringReplaceNoWildcard extends SummarizedCallable {
StringReplaceNoWildcard() {
this = "String#replace / String#replaceAll (without wildcard pattern)"
}
override StringReplaceCall getACall() { not hasWildcardReplaceRegExp(result) }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
preservesValue = false and
(
input = "Argument[this]" and
output = "ReturnValue"
or
input = "Argument[1].ReturnValue" and
output = "ReturnValue"
)
}
}
/**
* Summary for calls to `.replace` or `.replaceAll` (with a regexp pattern containing a wildcard).
*
* In this case, the receiver is considered to flow into the callback.
*/
private class StringReplaceWithWildcard extends SummarizedCallable {
StringReplaceWithWildcard() {
this = "String#replace / String#replaceAll (with wildcard pattern)"
}
override StringReplaceCall getACall() { hasWildcardReplaceRegExp(result) }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
preservesValue = false and
(
input = "Argument[this]" and
output = ["ReturnValue", "Argument[1].Parameter[0]"]
or
input = "Argument[1].ReturnValue" and
output = "ReturnValue"
)
}
}
class StringSplit extends SummarizedCallable {
StringSplit() { this = "String#split" }
override DataFlow::MethodCallNode getACallSimple() {
result.getMethodName() = "split" and result.getNumArgument() = 1
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
preservesValue = false and
input = "Argument[this]" and
output = "ReturnValue.ArrayElement"
}
}