Skip to content

Commit ec55364

Browse files
committed
fix: Replaces testing http client by raw jetty in Apache Http Client instrumentation
Now that :dd-java-agent:testing shadows jetty, the jetty instrumentation was not applied.
1 parent d8b8cb5 commit ec55364

File tree

3 files changed

+85
-42
lines changed

3 files changed

+85
-42
lines changed

dd-java-agent/instrumentation/apache-httpclient/apache-httpclient-4.0/build.gradle

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,12 @@ dependencies {
3939
// to instrument the integration test
4040
iastIntegrationTestImplementation project(':dd-java-agent:agent-iast:iast-test-fixtures')
4141
iastIntegrationTestImplementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.0'
42-
iastIntegrationTestRuntimeOnly(project(':dd-java-agent:instrumentation:jetty:jetty-server:jetty-server-9.0'))
42+
// Provide real (non-shadowed) jetty for the test server bootstrap
43+
iastIntegrationTestImplementation group: 'org.eclipse.jetty', name: 'jetty-server', version: '9.4.56.v20240826'
44+
iastIntegrationTestImplementation group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '9.4.56.v20240826'
4345
iastIntegrationTestRuntimeOnly(project(':dd-java-agent:instrumentation:apache-httpcore:apache-httpcore-4.0'))
4446
iastIntegrationTestRuntimeOnly(project(':dd-java-agent:instrumentation:servlet:javax-servlet:javax-servlet-common'))
47+
iastIntegrationTestRuntimeOnly(project(':dd-java-agent:instrumentation:jetty:jetty-server:jetty-server-9.0'))
4548
iastIntegrationTestRuntimeOnly(project(':dd-java-agent:instrumentation:java:java-lang:java-lang-1.8'))
4649
iastIntegrationTestRuntimeOnly(project(':dd-java-agent:instrumentation:java:java-net:java-net-1.8'))
4750
iastIntegrationTestRuntimeOnly project(':dd-java-agent:instrumentation:iast-instrumenter')

dd-java-agent/instrumentation/apache-httpclient/apache-httpclient-4.0/src/iastIntegrationTest/groovy/IastHttpClientIntegrationTest.groovy

Lines changed: 74 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,16 @@ import datadog.trace.agent.test.base.HttpServer
55
import foo.bar.VulnerableUrlBuilder
66
import okhttp3.HttpUrl
77
import okhttp3.Request
8+
import org.eclipse.jetty.server.Server
9+
import org.eclipse.jetty.server.ServerConnector
10+
import org.eclipse.jetty.servlet.ServletContextHandler
11+
import org.eclipse.jetty.servlet.ServletHolder
812

9-
import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer
13+
import javax.servlet.http.HttpServlet
14+
import javax.servlet.http.HttpServletRequest
15+
import javax.servlet.http.HttpServletResponse
1016

11-
class IastHttpClientIntegrationTest extends IastHttpServerTest<HttpServer> {
17+
class IastHttpClientIntegrationTest extends IastHttpServerTest<Server> {
1218

1319
static final CLIENTS = [
1420
'org.apache.http.impl.client.AutoRetryHttpClient',
@@ -20,28 +26,58 @@ class IastHttpClientIntegrationTest extends IastHttpServerTest<HttpServer> {
2026
@Override
2127
HttpServer server() {
2228
final controller = new SsrfController()
23-
return httpServer {
24-
handlers {
25-
prefix('/ssrf/execute') {
26-
final msg = controller.apacheSsrf(
27-
VulnerableUrlBuilder.url(request),
28-
(String) request.getParameter('clientClassName'),
29-
(String) request.getParameter('method'),
30-
(String) request.getParameter('requestType'),
31-
(String) request.getParameter('scheme')
32-
)
33-
response.status(200).send(msg)
34-
}
29+
30+
// Use _raw_ jetty API, so the jetty instrumentation is applied
31+
// (the :dd-java-agent:testing has a shadowed jetty server.)
32+
return new HttpServer() {
33+
Server jettyServer
34+
int port
35+
36+
@Override
37+
void start() {
38+
jettyServer = new Server(0) // 0 = random port
39+
40+
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS)
41+
context.setContextPath('/')
42+
jettyServer.setHandler(context)
43+
44+
// Add servlet for the /ssrf/execute endpoint
45+
context.addServlet(new ServletHolder(new HttpServlet() {
46+
@Override
47+
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
48+
final msg = controller.apacheSsrf(
49+
VulnerableUrlBuilder.url(req),
50+
req.getParameter('clientClassName'),
51+
req.getParameter('method'),
52+
req.getParameter('requestType'),
53+
req.getParameter('scheme')
54+
)
55+
resp.setStatus(200)
56+
resp.writer.write(msg)
57+
}
58+
}), '/ssrf/execute')
59+
60+
jettyServer.start()
61+
port = ((ServerConnector) jettyServer.connectors[0]).localPort
3562
}
36-
}.asHttpServer()
63+
64+
@Override
65+
void stop() {
66+
jettyServer?.stop()
67+
}
68+
69+
@Override
70+
URI address() {
71+
return new URI("http://localhost:${port}/")
72+
}
73+
}
3774
}
3875

3976
void 'ssrf is present: #suite'() {
4077
setup:
4178
final url = suite.url(address)
4279
final request = new Request.Builder().url(url).get().build()
4380

44-
4581
when:
4682
def response = client.newCall(request).execute()
4783

@@ -52,6 +88,9 @@ class IastHttpClientIntegrationTest extends IastHttpServerTest<HttpServer> {
5288
assert to != null
5389
hasVulnerability(vul -> vul.type == VulnerabilityType.SSRF && suite.evidenceMatches(vul.evidence))
5490

91+
cleanup:
92+
response?.close()
93+
5594
where:
5695
suite << createTestSuite()
5796
}
@@ -73,12 +112,12 @@ class IastHttpClientIntegrationTest extends IastHttpServerTest<HttpServer> {
73112
private List<TestSuite> createTestSuite(client, method, request, scheme) {
74113
return TaintedTarget.values().collect {
75114
new TestSuite(
76-
description: "Tainted ${it.name()} with ${client} client and ${method} method with ${request} and ${scheme} scheme",
77-
executedMethod: method.name(),
78-
clientImplementation: client,
79-
requestType: request.name(),
80-
scheme: scheme,
81-
tainted: it
115+
description: "Tainted ${it.name()} with ${client} client and ${method} method with ${request} and ${scheme} scheme",
116+
executedMethod: method.name(),
117+
clientImplementation: client,
118+
requestType: request.name(),
119+
scheme: scheme,
120+
tainted: it
82121
)
83122
}
84123
}
@@ -106,14 +145,14 @@ class IastHttpClientIntegrationTest extends IastHttpServerTest<HttpServer> {
106145

107146
HttpUrl url(final URI address) {
108147
final builder = new HttpUrl.Builder()
109-
.scheme(address.getScheme())
110-
.host(address.getHost())
111-
.port(address.getPort())
112-
.addPathSegments('ssrf/execute')
113-
.addQueryParameter('clientClassName', clientImplementation)
114-
.addQueryParameter('method', executedMethod)
115-
.addQueryParameter('requestType', requestType)
116-
.addQueryParameter('scheme', scheme)
148+
.scheme(address.getScheme())
149+
.host(address.getHost())
150+
.port(address.getPort())
151+
.addPathSegments('ssrf/execute')
152+
.addQueryParameter('clientClassName', clientImplementation)
153+
.addQueryParameter('method', executedMethod)
154+
.addQueryParameter('requestType', requestType)
155+
.addQueryParameter('scheme', scheme)
117156
tainted.addTainted(builder, this)
118157
return builder.build()
119158
}
@@ -129,7 +168,7 @@ class IastHttpClientIntegrationTest extends IastHttpServerTest<HttpServer> {
129168
}
130169

131170
private static enum TaintedTarget {
132-
URL{
171+
URL {
133172
void addTainted(HttpUrl.Builder builder, TestSuite suite) {
134173
builder.addQueryParameter('url', suite.scheme + '://inexistent/test?1=1')
135174
}
@@ -138,7 +177,7 @@ class IastHttpClientIntegrationTest extends IastHttpServerTest<HttpServer> {
138177
return assertTainted(evidence, 'url', suite.scheme + '://inexistent/test?1=1')
139178
}
140179
},
141-
SCHEME{
180+
SCHEME {
142181
void addTainted(HttpUrl.Builder builder, TestSuite suite) {
143182
// already added by the test
144183
}
@@ -147,7 +186,7 @@ class IastHttpClientIntegrationTest extends IastHttpServerTest<HttpServer> {
147186
return assertTainted(evidence, 'scheme', suite.scheme)
148187
}
149188
},
150-
HOST{
189+
HOST {
151190
void addTainted(HttpUrl.Builder builder, TestSuite suite) {
152191
builder.addQueryParameter('host', 'inexistent')
153192
}
@@ -156,7 +195,7 @@ class IastHttpClientIntegrationTest extends IastHttpServerTest<HttpServer> {
156195
return assertTainted(evidence, 'host', 'inexistent')
157196
}
158197
},
159-
PATH{
198+
PATH {
160199
void addTainted(HttpUrl.Builder builder, TestSuite suite) {
161200
builder.addQueryParameter('path', '/test')
162201
}
@@ -165,7 +204,7 @@ class IastHttpClientIntegrationTest extends IastHttpServerTest<HttpServer> {
165204
return assertTainted(evidence, 'path', '/test')
166205
}
167206
},
168-
QUERY{
207+
QUERY {
169208
void addTainted(HttpUrl.Builder builder, TestSuite suite) {
170209
builder.addQueryParameter('query', '?1=1')
171210
}

dd-java-agent/instrumentation/apache-httpclient/apache-httpclient-4.0/src/iastIntegrationTest/java/foo/bar/VulnerableUrlBuilder.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,29 @@
11
package foo.bar;
22

33
import datadog.trace.agent.test.server.http.TestHttpServer.HandlerApi.RequestApi;
4+
import javax.servlet.http.HttpServletRequest;
45

56
/** Class to be instrumented by IAST call sites containing methods to work with urls */
67
public abstract class VulnerableUrlBuilder {
78

89
private VulnerableUrlBuilder() {}
910

10-
public static String url(RequestApi request) {
11-
final String url = (String) request.getParameter("url");
11+
public static String url(HttpServletRequest request) {
12+
final String url = request.getParameter("url");
1213
if (url != null) {
1314
return url;
1415
}
15-
final String scheme = (String) request.getParameter("scheme");
16+
final String scheme = request.getParameter("scheme");
1617
final boolean https = "https".equals(scheme);
17-
final String host = (String) request.getParameter("host");
18+
final String host = request.getParameter("host");
1819
if (host != null) {
1920
return (https ? "https://" : "http://") + host + "/test?1=1";
2021
}
21-
final String path = (String) request.getParameter("path");
22+
final String path = request.getParameter("path");
2223
if (path != null) {
2324
return (https ? "https://inexistent/" : "http://inexistent/") + path + "?1=1";
2425
}
25-
final String query = (String) request.getParameter("query");
26+
final String query = request.getParameter("query");
2627
if (query != null) {
2728
return (https ? "https://inexistent/test" : "http://inexistent/test") + query;
2829
}

0 commit comments

Comments
 (0)