Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* URI template variables of all Spring `RestTemplate` methods are now considered as request forgery sinks. Previously only the `getForObject` method was considered. This may lead to more alerts for the query `java/ssrf`.
Original file line number Diff line number Diff line change
Expand Up @@ -31,50 +31,74 @@ class SpringWebClient extends Interface {
}
}

/** The method `getForObject` on `org.springframework.web.reactive.function.client.RestTemplate`. */
class SpringRestTemplateGetForObjectMethod extends Method {
SpringRestTemplateGetForObjectMethod() {
/**
* A method on `org.springframework.web.reactive.function.client.RestTemplate`
* which has a parameter `uriVariables` (which can have type `Object..` or
* `Map<String, ?>`) which contains variables to be expanded into the URL
* template in parameter 0.
*/
private class SpringRestTemplateMethodWithUriVariablesParameter extends Method {
int pos;

SpringRestTemplateMethodWithUriVariablesParameter() {
this.getDeclaringType() instanceof SpringRestTemplate and
this.hasName("getForObject")
(
this.hasName("delete") and pos = 1
or
this.hasName("exchange") and pos = 4
or
this.hasName("execute") and pos = 4
or
this.hasName("getForEntity") and pos = 2
or
this.hasName("getForObject") and pos = 2
or
this.hasName("headForHeaders") and pos = 1
or
this.hasName("optionsForAllow") and pos = 1
or
this.hasName("patchForObject") and pos = 3
or
this.hasName("postForEntity") and pos = 3
or
this.hasName("postForLocation") and pos = 2
or
this.hasName("postForObject") and pos = 3
or
this.hasName("put") and pos = 2
)
}
}

/** A call to the method `getForObject` on `org.springframework.web.reactive.function.client.RestTemplate`. */
class SpringRestTemplateGetForObjectMethodCall extends MethodCall {
SpringRestTemplateGetForObjectMethodCall() {
this.getMethod() instanceof SpringRestTemplateGetForObjectMethod
}
int getUriVariablesPosition() { result = pos }
}

/** Gets the first argument, if it is a compile time constant. */
CompileTimeConstantExpr getConstantUrl() { result = this.getArgument(0) }
/** Gets the first argument, if it is a compile time constant. */
pragma[inline]
private CompileTimeConstantExpr getConstantUrl(MethodCall mc) { result = mc.getArgument(0) }

/**
* Holds if the first argument is a compile time constant and it has a
* placeholder at offset `offset`, and there are `idx` placeholders that
* appear before it.
*/
predicate urlHasPlaceholderAtOffset(int idx, int offset) {
exists(
this.getConstantUrl()
.getStringValue()
.replaceAll("\\{", " ")
.replaceAll("\\}", " ")
.regexpFind("\\{[^}]*\\}", idx, offset)
)
}
pragma[inline]
private predicate urlHasPlaceholderAtOffset(MethodCall mc, int idx, int offset) {
exists(
getConstantUrl(mc)
.getStringValue()
.replaceAll("\\{", " ")
.replaceAll("\\}", " ")
.regexpFind("\\{[^}]*\\}", idx, offset)
)
}

private class SpringWebClientRestTemplateGetForObject extends RequestForgerySink {
SpringWebClientRestTemplateGetForObject() {
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class name SpringWebClientRestTemplateGetForObject is misleading since it now handles all RestTemplate methods with uriVariables parameters, not just getForObject. Consider renaming to something more general like SpringWebClientRestTemplateUriVariables to better reflect its purpose.

Suggested change
private class SpringWebClientRestTemplateGetForObject extends RequestForgerySink {
SpringWebClientRestTemplateGetForObject() {
private class SpringWebClientRestTemplateUriVariables extends RequestForgerySink {
SpringWebClientRestTemplateUriVariables() {

Copilot uses AI. Check for mistakes.
exists(SpringRestTemplateGetForObjectMethodCall mc, int i |
exists(SpringRestTemplateMethodWithUriVariablesParameter m, MethodCall mc, int i |
// Note that the first argument is modeled as a request forgery sink
// separately. This model is for arguments beyond the first two. There
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment "arguments beyond the first two" is inaccurate for some methods. For example, delete has uriVariables at position 1, making it "arguments beyond the first", and headForHeaders/optionsForAllow also have uriVariables at position 1. Consider updating to "arguments beyond the URL parameter" or a similar generic description.

Suggested change
// separately. This model is for arguments beyond the first two. There
// separately. This model is for arguments beyond the URL parameter (i.e., those corresponding to `uriVariables`). There

Copilot uses AI. Check for mistakes.
// are two relevant overloads, one with third parameter type `Object...`
// and one with third parameter type `Map<String, ?>`. For the latter we
// cannot deal with MapValue content easily but there is a default
// implicit taint read at sinks that will catch it.
mc.getMethod() = m and
i >= 0 and
this.asExpr() = mc.getArgument(i + 2)
this.asExpr() = mc.getArgument(m.getUriVariablesPosition() + i)
|
// If we can determine that part of mc.getArgument(0) is a hostname
// sanitizing prefix, then we count how many placeholders occur before it
Expand All @@ -83,8 +107,8 @@ private class SpringWebClientRestTemplateGetForObject extends RequestForgerySink
// considering the map values as sinks if there is at least one
// placeholder in the URL before the hostname sanitizing prefix.
exists(int offset |
mc.urlHasPlaceholderAtOffset(i, offset) and
offset < mc.getConstantUrl().(HostnameSanitizingPrefix).getOffset()
urlHasPlaceholderAtOffset(mc, i, offset) and
offset < getConstantUrl(mc).(HostnameSanitizingPrefix).getOffset()
)
or
// If we cannot determine that part of mc.getArgument(0) is a hostname
Expand All @@ -94,12 +118,12 @@ private class SpringWebClientRestTemplateGetForObject extends RequestForgerySink
// For the `Map<String, ?>` overload this has the effect of only
// considering the map values as sinks if there is at least one
// placeholder in the URL.
not mc.getConstantUrl() instanceof HostnameSanitizingPrefix and
mc.urlHasPlaceholderAtOffset(i, _)
not getConstantUrl(mc) instanceof HostnameSanitizingPrefix and
urlHasPlaceholderAtOffset(mc, i, _)
or
// If we cannot determine the string value of mc.getArgument(0), then we
// conservatively consider all arguments as sinks.
not exists(mc.getConstantUrl().getStringValue())
not exists(getConstantUrl(mc).getStringValue())
)
}
}
Loading
Loading