Skip to content

Commit 7648d39

Browse files
committed
Improve model to remove some false positives
1 parent 617f4f1 commit 7648d39

File tree

2 files changed

+54
-9
lines changed

2 files changed

+54
-9
lines changed

java/ql/lib/semmle/code/java/frameworks/spring/SpringWebClient.qll

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,58 @@ private class SpringWebClientRestTemplateGetForObject extends RequestForgerySink
3535
exists(Method m, MethodCall mc, int i |
3636
m.getDeclaringType() instanceof SpringRestTemplate and
3737
m.hasName("getForObject") and
38-
mc.getMethod() = m
38+
mc.getMethod() = m and
39+
// Note that mc.getArgument(0) is modeled separately. This model is for
40+
// arguments beyond the first two. There are two relevant overloads, one
41+
// with third parameter type `Object...` and one with third parameter
42+
// type `Map<String, ?>`. For the latter we cannot deal with mapvalue
43+
// content easily but there is a default implicit taint read at sinks
44+
// that will catch it.
45+
this.asExpr() = mc.getArgument(i + 2) and
46+
i >= 0
3947
|
40-
// Deal with two overloads, with third parameter type `Object...` and
41-
// `Map<String, ?>`. We cannot deal with mapvalue content easily but
42-
// there is a default implicit taint read at sinks that will catch it.
43-
this.asExpr() = mc.getArgument(i) and
44-
i >= 2
48+
// If we can determine that part of mc.getArgument(0) is a hostname
49+
// sanitizing prefix, then we count how many placeholders occur before it
50+
// and only consider that many arguments beyond the first two as sinks.
51+
// For the `Map<String, ?>` overload this has the effect of only
52+
// considering the map values as sinks if there is at least one
53+
// placeholder in the URL before the hostname sanitizing prefix.
54+
exists(HostnameSanitizingPrefix hsp |
55+
hsp = mc.getArgument(0) and
56+
i <=
57+
max(int occurrenceIndex, int occurrenceOffset |
58+
exists(
59+
hsp.getStringValue().regexpFind("\\{[^}]*\\}", occurrenceIndex, occurrenceOffset)
60+
) and
61+
occurrenceOffset < hsp.getOffset()
62+
|
63+
occurrenceIndex
64+
)
65+
)
66+
or
67+
// If we cannot determine that part of mc.getArgument(0) is a hostname
68+
// sanitizing prefix, but it is a compile time constant and we can get
69+
// its string value, then we count how many placeholders occur in it
70+
// and only consider that many arguments beyond the first two as sinks.
71+
// For the `Map<String, ?>` overload this has the effect of only
72+
// considering the map values as sinks if there is at least one
73+
// placeholder in the URL.
74+
not mc.getArgument(0) instanceof HostnameSanitizingPrefix and
75+
i <=
76+
max(int occurrenceIndex |
77+
exists(
78+
mc.getArgument(0)
79+
.(CompileTimeConstantExpr)
80+
.getStringValue()
81+
.regexpFind("\\{[^}]*\\}", occurrenceIndex, _)
82+
)
83+
|
84+
occurrenceIndex
85+
)
86+
or
87+
// If we cannot determine the string value of mc.getArgument(0), then we
88+
// conservatively consider all arguments as sinks.
89+
not exists(mc.getArgument(0).(CompileTimeConstantExpr).getStringValue())
4590
)
4691
}
4792
}

java/ql/test/query-tests/security/CWE-918/SpringSSRF.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ protected void doGet(HttpServletRequest request2, HttpServletResponse response2)
3535
restTemplate.getForObject(fooResourceUrl, String.class, "test"); // $ SSRF
3636
restTemplate.getForObject("http://{foo}", String.class, fooResourceUrl); // $ SSRF
3737
restTemplate.getForObject("http://{foo}/a/b", String.class, fooResourceUrl); // $ SSRF
38-
restTemplate.getForObject("http://safe.com/{foo}", String.class, fooResourceUrl); // $ SPURIOUS: SSRF // not bad - the tainted value does not affect the host
39-
restTemplate.getForObject("http://{foo}", String.class, "safe.com", fooResourceUrl); // $ SPURIOUS: SSRF // not bad - the tainted value is unused
38+
restTemplate.getForObject("http://safe.com/{foo}", String.class, fooResourceUrl); // not bad - the tainted value does not affect the host
39+
restTemplate.getForObject("http://{foo}", String.class, "safe.com", fooResourceUrl); // not bad - the tainted value is unused
4040
restTemplate.getForObject("http://{foo}", String.class, Map.of("foo", fooResourceUrl)); // $ SSRF
41-
restTemplate.getForObject("http://safe.com/{foo}", String.class, Map.of("foo", fooResourceUrl)); // $ SPURIOUS: SSRF // not bad - the tainted value does not affect the host
41+
restTemplate.getForObject("http://safe.com/{foo}", String.class, Map.of("foo", fooResourceUrl)); // not bad - the tainted value does not affect the host
4242
restTemplate.getForObject("http://{foo}", String.class, Map.of("foo", "safe.com", "unused", fooResourceUrl)); // $ SPURIOUS: SSRF // not bad - the key for the tainted value is unused
4343
restTemplate.getForObject("http://{foo}", String.class, Map.of("foo", "safe.com", fooResourceUrl, "unused")); // not bad - the tainted value is in a map key
4444
restTemplate.patchForObject(fooResourceUrl, new String("object"), String.class, "hi"); // $ SSRF

0 commit comments

Comments
 (0)