Skip to content

Commit 8b7db8a

Browse files
authored
Merge pull request github#5408 from p0wn4j/urlclassloader-webclient-ssrf-sinks
Java: Add URLClassLoader, WebClient SSRF sinks
2 parents 05842dc + e0a7f6e commit 8b7db8a

File tree

9 files changed

+296
-2
lines changed

9 files changed

+296
-2
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* Added support for two new APIs susceptible to server-side request forgery (SSRF): using a `URLClassLoader`, and using Spring Web Reactive's `WebClient`.

java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,12 @@ private predicate sinkModelCsv(string row) {
218218
"java.net;URL;false;openStream;;;Argument[-1];open-url",
219219
"java.net.http;HttpRequest;false;newBuilder;;;Argument[0];open-url",
220220
"java.net.http;HttpRequest$Builder;false;uri;;;Argument[0];open-url",
221+
"java.net;URLClassLoader;false;URLClassLoader;(URL[]);;Argument[0];open-url",
222+
"java.net;URLClassLoader;false;URLClassLoader;(URL[],ClassLoader);;Argument[0];open-url",
223+
"java.net;URLClassLoader;false;URLClassLoader;(URL[],ClassLoader,URLStreamHandlerFactory);;Argument[0];open-url",
224+
"java.net;URLClassLoader;false;URLClassLoader;(String,URL[],ClassLoader);;Argument[1];open-url",
225+
"java.net;URLClassLoader;false;URLClassLoader;(String,URL[],ClassLoader,URLStreamHandlerFactory);;Argument[1];open-url",
226+
"java.net;URLClassLoader;false;newInstance;;;Argument[0];open-url",
221227
// Create file
222228
"java.io;FileOutputStream;false;FileOutputStream;;;Argument[0];create-file",
223229
"java.io;RandomAccessFile;false;RandomAccessFile;;;Argument[0];create-file",

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ private class UrlOpenSink extends SinkModelCsv {
4545
"org.springframework.web.client;RestTemplate;false;postForEntity;;;Argument[0];open-url",
4646
"org.springframework.web.client;RestTemplate;false;postForLocation;;;Argument[0];open-url",
4747
"org.springframework.web.client;RestTemplate;false;postForObject;;;Argument[0];open-url",
48-
"org.springframework.web.client;RestTemplate;false;put;;;Argument[0];open-url"
48+
"org.springframework.web.client;RestTemplate;false;put;;;Argument[0];open-url",
49+
"org.springframework.web.reactive.function.client;WebClient;false;create;;;Argument[0];open-url",
50+
"org.springframework.web.reactive.function.client;WebClient$Builder;false;baseUrl;;;Argument[0];open-url"
4951
]
5052
}
5153
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import org.springframework.http.HttpHeaders;
2+
import org.springframework.web.reactive.function.client.WebClient;
3+
import reactor.core.publisher.Mono;
4+
5+
import javax.servlet.ServletException;
6+
import javax.servlet.http.HttpServlet;
7+
import javax.servlet.http.HttpServletRequest;
8+
import javax.servlet.http.HttpServletResponse;
9+
import java.io.IOException;
10+
11+
public class ReactiveWebClientSSRF extends HttpServlet {
12+
protected void doGet(HttpServletRequest request, HttpServletResponse response)
13+
throws ServletException, IOException {
14+
try {
15+
String url = request.getParameter("uri");
16+
WebClient webClient = WebClient.create(url); // $ SSRF
17+
18+
Mono<String> result = webClient.get()
19+
.uri("/")
20+
.retrieve()
21+
.bodyToMono(String.class);
22+
23+
result.block();
24+
} catch (Exception e) {
25+
// Ignore
26+
}
27+
}
28+
29+
protected void doPost(HttpServletRequest request, HttpServletResponse response)
30+
throws ServletException, IOException {
31+
try {
32+
String url = request.getParameter("uri");
33+
WebClient webClient = WebClient.builder()
34+
.defaultHeader("User-Agent", "Java")
35+
.baseUrl(url) // $ SSRF
36+
.build();
37+
38+
39+
Mono<String> result = webClient.get()
40+
.uri("/")
41+
.retrieve()
42+
.bodyToMono(String.class);
43+
44+
result.block();
45+
} catch (Exception e) {
46+
// Ignore
47+
}
48+
}
49+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import javax.servlet.ServletException;
2+
import javax.servlet.http.HttpServlet;
3+
import javax.servlet.http.HttpServletRequest;
4+
import javax.servlet.http.HttpServletResponse;
5+
import java.io.IOException;
6+
import java.net.URI;
7+
import java.net.URL;
8+
import java.net.URLClassLoader;
9+
import java.net.URLStreamHandlerFactory;
10+
11+
public class URLClassLoaderSSRF extends HttpServlet {
12+
13+
protected void doGet(HttpServletRequest request, HttpServletResponse response)
14+
throws ServletException, IOException {
15+
try {
16+
String url = request.getParameter("uri");
17+
URI uri = new URI(url);
18+
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{uri.toURL()}); // $ SSRF
19+
Class<?> test = urlClassLoader.loadClass("test");
20+
} catch (Exception e) {
21+
// Ignore
22+
}
23+
}
24+
25+
protected void doPost(HttpServletRequest request, HttpServletResponse response)
26+
throws ServletException, IOException {
27+
try {
28+
String url = request.getParameter("uri");
29+
URI uri = new URI(url);
30+
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{uri.toURL()}, URLClassLoaderSSRF.class.getClassLoader()); // $ SSRF
31+
Class<?> test = urlClassLoader.loadClass("test");
32+
} catch (Exception e) {
33+
// Ignore
34+
}
35+
}
36+
37+
protected void doPut(HttpServletRequest request, HttpServletResponse response)
38+
throws ServletException, IOException {
39+
try {
40+
String url = request.getParameter("uri");
41+
URI uri = new URI(url);
42+
43+
URLStreamHandlerFactory urlStreamHandlerFactory = null;
44+
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{uri.toURL()}, URLClassLoaderSSRF.class.getClassLoader(), urlStreamHandlerFactory); // $ SSRF
45+
urlClassLoader.findResource("test");
46+
} catch (Exception e) {
47+
// Ignore
48+
}
49+
}
50+
51+
protected void doDelete(HttpServletRequest request, HttpServletResponse response)
52+
throws ServletException, IOException {
53+
try {
54+
String url = request.getParameter("uri");
55+
URI uri = new URI(url);
56+
URLClassLoader urlClassLoader = URLClassLoader.newInstance(new URL[]{uri.toURL()}); // $ SSRF
57+
urlClassLoader.getResourceAsStream("test");
58+
} catch (Exception e) {
59+
// Ignore
60+
}
61+
}
62+
63+
protected void doOptions(HttpServletRequest request, HttpServletResponse response)
64+
throws ServletException, IOException {
65+
try {
66+
String url = request.getParameter("uri");
67+
URI uri = new URI(url);
68+
URLClassLoader urlClassLoader =
69+
new URLClassLoader("testClassLoader",
70+
new URL[]{uri.toURL()}, // $ SSRF
71+
URLClassLoaderSSRF.class.getClassLoader()
72+
);
73+
74+
Class<?> rceTest = urlClassLoader.loadClass("RCETest");
75+
} catch (Exception e) {
76+
// Ignore
77+
}
78+
}
79+
80+
protected void doTrace(HttpServletRequest request, HttpServletResponse response)
81+
throws ServletException, IOException {
82+
try {
83+
String url = request.getParameter("uri");
84+
URI uri = new URI(url);
85+
URLStreamHandlerFactory urlStreamHandlerFactory = null;
86+
87+
URLClassLoader urlClassLoader =
88+
new URLClassLoader("testClassLoader",
89+
new URL[]{uri.toURL()}, // $ SSRF
90+
URLClassLoaderSSRF.class.getClassLoader(),
91+
urlStreamHandlerFactory
92+
);
93+
94+
Class<?> rceTest = urlClassLoader.loadClass("RCETest");
95+
} catch (Exception e) {
96+
// Ignore
97+
}
98+
}
99+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
//semmle-extractor-options: --javac-args -source 11 -target 11 -cp ${testdir}/../../../stubs/springframework-5.3.8:${testdir}/../../../stubs/javax-ws-rs-api-2.1.1:${testdir}/../../../stubs/javax-ws-rs-api-3.0.0:${testdir}/../../../stubs/apache-http-4.4.13/:${testdir}/../../../stubs/servlet-api-2.4/
1+
//semmle-extractor-options: --javac-args -source 11 -target 11 -cp ${testdir}/../../../stubs/springframework-5.3.8:${testdir}/../../../stubs/javax-ws-rs-api-2.1.1:${testdir}/../../../stubs/javax-ws-rs-api-3.0.0:${testdir}/../../../stubs/apache-http-4.4.13/:${testdir}/../../../stubs/servlet-api-2.4/:${testdir}/../../../stubs/projectreactor-3.4.3/
2+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright (c) 2011-Present VMware Inc. or its affiliates, All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package reactor.core.publisher;
17+
18+
public abstract class Mono<T> {
19+
public T block() {
20+
return null;
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2002-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.reactive.function.client;
18+
19+
final class DefaultWebClientBuilder implements WebClient.Builder {
20+
21+
public DefaultWebClientBuilder() {
22+
}
23+
24+
@Override
25+
public WebClient.Builder baseUrl(String baseUrl) {
26+
return this;
27+
}
28+
29+
@Override
30+
public WebClient.Builder defaultHeader(String header, String... values) {
31+
return this;
32+
}
33+
34+
35+
@Override
36+
public WebClient build() {
37+
return null;
38+
}
39+
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2002-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.reactive.function.client;
18+
19+
import reactor.core.publisher.Mono;
20+
21+
/**
22+
Spring Reactor WebClient interface stub
23+
*/
24+
public interface WebClient {
25+
26+
RequestHeadersUriSpec<?> get();
27+
RequestHeadersUriSpec<?> head();
28+
RequestBodyUriSpec post();
29+
RequestBodyUriSpec put();
30+
RequestBodyUriSpec patch();
31+
RequestHeadersUriSpec<?> delete();
32+
RequestHeadersUriSpec<?> options();
33+
34+
static WebClient create(String baseUrl) {
35+
return null;
36+
}
37+
38+
static WebClient create() {
39+
return null;
40+
}
41+
42+
static WebClient.Builder builder() {
43+
return null;
44+
}
45+
46+
interface Builder {
47+
Builder baseUrl(String baseUrl);
48+
Builder defaultHeader(String header, String... values);
49+
WebClient build();
50+
}
51+
52+
interface UriSpec<S extends RequestHeadersSpec<?>> {
53+
S uri(String uri, Object... uriVariables);
54+
}
55+
56+
interface RequestBodySpec extends RequestHeadersSpec<RequestBodySpec> {
57+
}
58+
59+
interface RequestBodyUriSpec extends RequestBodySpec, RequestHeadersUriSpec<RequestBodySpec> {
60+
}
61+
62+
interface ResponseSpec {
63+
<T> Mono<T> bodyToMono(Class<T> elementClass);
64+
}
65+
66+
interface RequestHeadersUriSpec<S extends RequestHeadersSpec<S>>
67+
extends UriSpec<S>, RequestHeadersSpec<S> {
68+
}
69+
70+
interface RequestHeadersSpec<S extends RequestHeadersSpec<S>> {
71+
ResponseSpec retrieve();
72+
}
73+
}

0 commit comments

Comments
 (0)