From 09d100c2b7ebb978cd7d1595f32d343c78f51b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Vidal=20Dom=C3=ADnguez?= Date: Fri, 8 Nov 2024 12:18:04 +0100 Subject: [PATCH 01/14] Draft status commit --- .../trace/agent/tooling/AgentInstaller.java | 53 + .../apache-httpclient-4/build.gradle | 2 +- .../ApacheHttpClientDecorator.java | 9 + .../apache-httpcore-5/build.gradle | 20 + .../apachehttpcore5/CtorAdvice.java | 14 + .../HttpRequestInstrumentation.java | 147 ++ .../HttpRequestInstrumentationTest.groovy | 26 + .../build.gradle | 0 .../gradle.lockfile | 0 .../IastHttpHostInstrumentation.java | 0 .../IastHttpHostInstrumentationTest.groovy | 0 .../iastinstrumenter/iast_exclusion.trie | 2 + dd-smoke-tests/iast-util/build.gradle | 1 + .../springboot/controller/SsrfController.java | 23 + .../AbstractIastSpringBootTest.groovy | 2148 +++++++++-------- .../spring-boot-2.6-webmvc/build.gradle | 1 + dd-smoke-tests/springboot/build.gradle | 1 + settings.gradle | 3 +- 18 files changed, 1376 insertions(+), 1074 deletions(-) create mode 100644 dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/build.gradle create mode 100644 dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/CtorAdvice.java create mode 100644 dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/HttpRequestInstrumentation.java create mode 100644 dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/HttpRequestInstrumentationTest.groovy rename dd-java-agent/instrumentation/{apache-httpcore-4 => apache-httpcore}/build.gradle (100%) rename dd-java-agent/instrumentation/{apache-httpcore-4 => apache-httpcore}/gradle.lockfile (100%) rename dd-java-agent/instrumentation/{apache-httpcore-4 => apache-httpcore}/src/main/java/datadog/trace/instrumentation/apachehttpcore/IastHttpHostInstrumentation.java (100%) rename dd-java-agent/instrumentation/{apache-httpcore-4 => apache-httpcore}/src/test/groovy/datadog/trace/instrumentation/apachehttpcore/IastHttpHostInstrumentationTest.groovy (100%) diff --git a/dd-java-agent/agent-builder/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java b/dd-java-agent/agent-builder/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java index 6fef809fef7..2afea75d8f5 100644 --- a/dd-java-agent/agent-builder/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java +++ b/dd-java-agent/agent-builder/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java @@ -20,6 +20,7 @@ import datadog.trace.bootstrap.instrumentation.java.concurrent.ExcludeFilter; import datadog.trace.util.AgentTaskScheduler; import de.thetaphi.forbiddenapis.SuppressForbidden; +import java.io.File; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.util.Collections; @@ -42,6 +43,7 @@ import net.bytebuddy.dynamic.scaffold.MethodGraph; import net.bytebuddy.matcher.LatentMatcher; import net.bytebuddy.utility.JavaModule; +import net.bytebuddy.utility.nullability.MaybeNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -153,6 +155,57 @@ public static ClassFileTransformer installBytebuddyAgent( .with(AgentStrategies.bufferStrategy()) .with(AgentStrategies.typeStrategy()) .with(new ClassLoadListener()) + .with( + new AgentBuilder.Listener.Adapter() { + @Override + public void onTransformation( + TypeDescription typeDescription, + ClassLoader classLoader, + JavaModule module, + boolean loaded, + DynamicType dynamicType) { + try { + if (typeDescription + .getName() + .equals("org.apache.hc.core5.http.message.LineFormatter")) { + // + // Utils.getInstrumentation().retransformClasses(Class.forName("org.apache.hc.core5.http.message.BasicHttpRequest", true, classLoader)); + } + dynamicType.saveIn(new File("/Users/mario.vidal/Documents/instrumentations")); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + @Override + public void onDiscovery( + String typeName, + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + boolean loaded) { + super.onDiscovery(typeName, classLoader, module, loaded); + } + + @Override + public void onIgnored( + TypeDescription typeDescription, + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + boolean loaded) { + super.onIgnored(typeDescription, classLoader, module, loaded); + } + + @Override + public void onError( + String typeName, + ClassLoader classLoader, + JavaModule module, + boolean loaded, + Throwable throwable) { + super.onError(typeName, classLoader, module, loaded, throwable); + } + }) + // FIXME: we cannot enable it yet due to BB/JVM bug, see // https://github.com/raphw/byte-buddy/issues/558 // .with(AgentBuilder.LambdaInstrumentationStrategy.ENABLED) diff --git a/dd-java-agent/instrumentation/apache-httpclient-4/build.gradle b/dd-java-agent/instrumentation/apache-httpclient-4/build.gradle index 4c06d6f1bda..87e28bb9a42 100644 --- a/dd-java-agent/instrumentation/apache-httpclient-4/build.gradle +++ b/dd-java-agent/instrumentation/apache-httpclient-4/build.gradle @@ -39,7 +39,7 @@ dependencies { iastIntegrationTestImplementation(testFixtures(project(':dd-java-agent:agent-iast'))) iastIntegrationTestImplementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.0' iastIntegrationTestRuntimeOnly(project(':dd-java-agent:instrumentation:jetty-9')) - iastIntegrationTestRuntimeOnly(project(':dd-java-agent:instrumentation:apache-httpcore-4')) + iastIntegrationTestRuntimeOnly(project(':dd-java-agent:instrumentation:apache-httpcore')) iastIntegrationTestRuntimeOnly(project(':dd-java-agent:instrumentation:servlet')) iastIntegrationTestRuntimeOnly(project(':dd-java-agent:instrumentation:java-lang')) iastIntegrationTestRuntimeOnly(project(':dd-java-agent:instrumentation:java-net')) diff --git a/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/ApacheHttpClientDecorator.java b/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/ApacheHttpClientDecorator.java index b1988a51de6..96517cf21d2 100644 --- a/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/ApacheHttpClientDecorator.java +++ b/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/ApacheHttpClientDecorator.java @@ -42,6 +42,15 @@ protected URI url(final HttpRequest request) throws URISyntaxException { return request.getUri(); } + @Override + protected Object sourceUrl(final HttpRequest request) { + try { + return request.getUri(); + } catch (URISyntaxException e) { + return null; + } + } + @Override protected int status(final HttpResponse httpResponse) { return httpResponse.getCode(); diff --git a/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/build.gradle b/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/build.gradle new file mode 100644 index 00000000000..60002ac1043 --- /dev/null +++ b/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/build.gradle @@ -0,0 +1,20 @@ +muzzle { + pass { + group = "org.apache.httpcomponents.core5" + module = "httpcore5" + versions = "[5.0,)" + assertInverse = true + } +} + +apply from: "$rootDir/gradle/java.gradle" + +addTestSuiteForDir('latestDepTest', 'test') + +dependencies { + compileOnly group: 'org.apache.httpcomponents.core5', name: 'httpcore5', version: '5.0' + + testImplementation group: 'org.apache.httpcomponents.core5', name: 'httpcore5', version: '5.0' + + latestDepTestImplementation group: 'org.apache.httpcomponents.core5', name: 'httpcore5', version: '+' +} diff --git a/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/CtorAdvice.java b/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/CtorAdvice.java new file mode 100644 index 00000000000..12b09811407 --- /dev/null +++ b/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/CtorAdvice.java @@ -0,0 +1,14 @@ +package datadog.trace.instrumentation.apachehttpcore5; + +import net.bytebuddy.asm.Advice; + +public class CtorAdvice { + @Advice.OnMethodExit() + public static void afterCtor() { + System.out.println("CtorAdvice.afterCtor"); + // final PropagationModule module = InstrumentationBridge.PROPAGATION; + // if (module != null) { + // module.taintObjectIfTainted(self, argument); + // } + } +} diff --git a/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/HttpRequestInstrumentation.java b/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/HttpRequestInstrumentation.java new file mode 100644 index 00000000000..57bd6847445 --- /dev/null +++ b/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/HttpRequestInstrumentation.java @@ -0,0 +1,147 @@ +package datadog.trace.instrumentation.apachehttpcore5; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; + +@AutoService(InstrumenterModule.class) +public class HttpRequestInstrumentation extends InstrumenterModule.Iast + implements Instrumenter.ForSingleType { + + public HttpRequestInstrumentation() { + super("testApache"); + } + + @Override + public String instrumentedType() { + return "org.apache.hc.core5.http.message.BasicHttpRequest"; + } + + // @Override + // public String[] getClassNamesToBePreloaded() { + // try { + // ClassLoader cl = ClassLoader.getSystemClassLoader(); + // Object loadedClass = cl.loadClass(instrumentedType()); + // System.out.println(loadedClass != null); + // } catch (Throwable e) { + // System.out.println(e); + // } + // return new String[] {instrumentedType()}; + // } + + // @Override + // public void typeAdvice(TypeTransformer transformer) { + // transformer.applyAdvice(new TaintableVisitor(instrumentedType())); + // } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".CtorAdvice", + }; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + // transformer.applyAdvice( + // isConstructor().and(takesArguments(String.class, String.class)), + // HttpRequestInstrumentation.class.getName() + "$CtorAdvice"); + // transformer.applyAdvice( + // isConstructor().and(takesArguments(String.class, URI.class)), + // HttpRequestInstrumentation.class.getName() + "$CtorAdvice"); + // transformer.applyAdvice( + // isConstructor().and(takesArguments(Method.class, String.class)), + // HttpRequestInstrumentation.class.getName() + "$CtorAdvice"); + // transformer.applyAdvice( + // isConstructor().and(takesArguments(Method.class, URI.class)), + // HttpRequestInstrumentation.class.getName() + "$CtorAdvice"); + + transformer.applyAdvice( + // isConstructor().and(takesArguments(2)).and(takesArgument(1, String.class)), + any(), packageName + ".CtorAdvice"); + // transformer.applyAdvice( + // isConstructor().and(takesArguments(2)).and(takesArgument(1, URI.class)), + // CtorAdvice.class.getName()); + + // transformer.applyAdvice( + // isConstructor().and(takesArguments(3)).and(takesArgument(1, + // named("org.apache.hc.core5.http.HttpHost"))), + // CtorAdviceHost.class.getName()); + + // transformer.applyAdvice( + // isConstructor().and(takesArguments(String.class, HttpHost.class, String.class)), + // HttpRequestInstrumentation.class.getName() + "$CtorAdviceHost"); + // transformer.applyAdvice( + // isConstructor().and(takesArguments(Method.class, HttpHost.class, String.class)), + // HttpRequestInstrumentation.class.getName() + "$CtorAdviceHost"); + } + + // public static class CtorAdvice { + // @Advice.OnMethodExit() + // @Propagation + // public static void afterCtor(@Advice.This final Object self, @Advice.Argument(1) Object + // argument) { + // final PropagationModule module = InstrumentationBridge.PROPAGATION; + // if (module != null) { + // module.taintObjectIfTainted(self, argument); + // } + // } + // } + + // public static class CtorAdviceHost { + // @Advice.OnMethodExit() + // @Propagation + // public static void afterCtor(@Advice.This final Object self, @Advice.Argument(2) String + // path) { + // final PropagationModule module = InstrumentationBridge.PROPAGATION; + // if (module != null) { + // module.taintObjectIfTainted(self, path); + // } + // } + // } + + // @Override + // public String hierarchyMarkerType() { + // return "org.apache.hc.core5.http.message.BasicHttpRequest"; + // } + // + // @Override + // public ElementMatcher hierarchyMatcher() { + // return extendsClass(named(hierarchyMarkerType())); + // } + // + // @Override + // public void methodAdvice(MethodTransformer transformer) { + // transformer.applyAdvice( + // named("getUri").and(isMethod()).and(takesArguments(0)), + // HttpRequestInstrumentation.class.getName() + "$GetUriAdvice"); + // transformer.applyAdvice( + // named("setUri").and(isMethod()).and(takesArguments(URI.class)), + // HttpRequestInstrumentation.class.getName() + "$SetUriAdvice"); + // } + // + // public static class SetUriAdvice { + // @Advice.OnMethodExit() + // @Propagation + // public static void methodExit(@Advice.This HttpRequest self, @Advice.Argument(0) final + // String path) { + // final PropagationModule propagationModule = InstrumentationBridge.PROPAGATION; + // if (propagationModule != null) { + // propagationModule.taintObjectIfTainted(self, path); + // } + // } + // } + // + // public static class GetUriAdvice { + // @Advice.OnMethodExit() + // @Propagation + // public static void methodExit(@Advice.This HttpRequest self, @Advice.Return URI result) { + // final PropagationModule propagationModule = InstrumentationBridge.PROPAGATION; + // if (propagationModule != null) { + // propagationModule.taintObjectIfTainted(result, self); + // } + // } + // } +} diff --git a/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/HttpRequestInstrumentationTest.groovy b/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/HttpRequestInstrumentationTest.groovy new file mode 100644 index 00000000000..d427eccfe82 --- /dev/null +++ b/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/HttpRequestInstrumentationTest.groovy @@ -0,0 +1,26 @@ +package datadog.trace.instrumentation.apachehttpcore5 + +import datadog.trace.agent.test.AgentTestRunner +import org.apache.hc.core5.http.message.BasicHttpRequest + +class HttpRequestInstrumentationTest extends AgentTestRunner { + + @Override + protected void configurePreAgent() { + injectSysConfig('dd.iast.enabled', 'true') + } + + void 'test constructor'(){ + given: + + when: + BasicHttpRequest.newInstance(*args) + + then: + 0 * _ + + where: + args | _ + ["GET", 'http://localhost.com'] | _ + } +} diff --git a/dd-java-agent/instrumentation/apache-httpcore-4/build.gradle b/dd-java-agent/instrumentation/apache-httpcore/build.gradle similarity index 100% rename from dd-java-agent/instrumentation/apache-httpcore-4/build.gradle rename to dd-java-agent/instrumentation/apache-httpcore/build.gradle diff --git a/dd-java-agent/instrumentation/apache-httpcore-4/gradle.lockfile b/dd-java-agent/instrumentation/apache-httpcore/gradle.lockfile similarity index 100% rename from dd-java-agent/instrumentation/apache-httpcore-4/gradle.lockfile rename to dd-java-agent/instrumentation/apache-httpcore/gradle.lockfile diff --git a/dd-java-agent/instrumentation/apache-httpcore-4/src/main/java/datadog/trace/instrumentation/apachehttpcore/IastHttpHostInstrumentation.java b/dd-java-agent/instrumentation/apache-httpcore/src/main/java/datadog/trace/instrumentation/apachehttpcore/IastHttpHostInstrumentation.java similarity index 100% rename from dd-java-agent/instrumentation/apache-httpcore-4/src/main/java/datadog/trace/instrumentation/apachehttpcore/IastHttpHostInstrumentation.java rename to dd-java-agent/instrumentation/apache-httpcore/src/main/java/datadog/trace/instrumentation/apachehttpcore/IastHttpHostInstrumentation.java diff --git a/dd-java-agent/instrumentation/apache-httpcore-4/src/test/groovy/datadog/trace/instrumentation/apachehttpcore/IastHttpHostInstrumentationTest.groovy b/dd-java-agent/instrumentation/apache-httpcore/src/test/groovy/datadog/trace/instrumentation/apachehttpcore/IastHttpHostInstrumentationTest.groovy similarity index 100% rename from dd-java-agent/instrumentation/apache-httpcore-4/src/test/groovy/datadog/trace/instrumentation/apachehttpcore/IastHttpHostInstrumentationTest.groovy rename to dd-java-agent/instrumentation/apache-httpcore/src/test/groovy/datadog/trace/instrumentation/apachehttpcore/IastHttpHostInstrumentationTest.groovy diff --git a/dd-java-agent/instrumentation/iast-instrumenter/src/main/resources/datadog/trace/instrumentation/iastinstrumenter/iast_exclusion.trie b/dd-java-agent/instrumentation/iast-instrumenter/src/main/resources/datadog/trace/instrumentation/iastinstrumenter/iast_exclusion.trie index f3a05cf60ab..4fe880e6082 100644 --- a/dd-java-agent/instrumentation/iast-instrumenter/src/main/resources/datadog/trace/instrumentation/iastinstrumenter/iast_exclusion.trie +++ b/dd-java-agent/instrumentation/iast-instrumenter/src/main/resources/datadog/trace/instrumentation/iastinstrumenter/iast_exclusion.trie @@ -195,6 +195,8 @@ 1 org.apache.* #apache httpClient needs URI propagation 0 org.apache.http.client.methods.* +0 org.apache.hc.client5.http.classic.methods.* +0 org.apache.hc.core5.http.message.* # apache compiled jsps 0 org.apache.jsp.* 1 org.apiguardian.* diff --git a/dd-smoke-tests/iast-util/build.gradle b/dd-smoke-tests/iast-util/build.gradle index 6a207cff971..2324b1fd420 100644 --- a/dd-smoke-tests/iast-util/build.gradle +++ b/dd-smoke-tests/iast-util/build.gradle @@ -17,4 +17,5 @@ dependencies { compileOnly group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.0' compileOnly group: 'com.squareup.okhttp', name: 'okhttp', version: '2.2.0' compileOnly group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.0.0' + compileOnly group: 'org.apache.httpcomponents.client5', name: 'httpclient5', version: '5.0' } diff --git a/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/SsrfController.java b/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/SsrfController.java index b8da4ff8c65..cad2bde3890 100644 --- a/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/SsrfController.java +++ b/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/SsrfController.java @@ -7,6 +7,8 @@ import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.http.HttpHost; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; @@ -89,4 +91,25 @@ public String okHttp3(@RequestParam(value = "url") final String url) { client.connectionPool().evictAll(); return "ok"; } + + @PostMapping("/apache-httpclient5") + public String apacheHttpClient5( + @RequestParam(value = "url", required = false) final String url, + @RequestParam(value = "host", required = false) final String host) { + CloseableHttpClient client = HttpClients.createDefault(); + org.apache.hc.client5.http.classic.methods.HttpGet request = + new org.apache.hc.client5.http.classic.methods.HttpGet(url); + try { + if (host != null) { + // final HttpHost httpHost = new HttpHost(host); + // final BasicHttpRequest request = new BasicHttpRequest("GET", "/"); + // client.execute(httpHost, request); + } else if (url != null) { + client.execute(request); + } + client.close(); + } catch (Exception e) { + } + return "ok"; + } } diff --git a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy index a81e41bd476..a247faaaacb 100644 --- a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy +++ b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy @@ -21,6 +21,9 @@ abstract class AbstractIastSpringBootTest extends AbstractIastServerSmokeTest { List command = [] command.add(javaPath()) + // command.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005") + // command.add("-Ddd.trace.debug=true") + command.add("-verbose:class") command.addAll(defaultJavaProperties) command.addAll(iastJvmOpts()) command.addAll((String[]) ['-jar', springBootShadowJar, "--server.port=${httpPort}"]) @@ -39,656 +42,656 @@ abstract class AbstractIastSpringBootTest extends AbstractIastServerSmokeTest { ] } - void 'IAST subsystem starts'() { - given: 'an initial request has succeeded' - String url = "http://localhost:${httpPort}/greeting" - def request = new Request.Builder().url(url).get().build() - client.newCall(request).execute() - - when: 'logs are read' - String startMsg = null - String errorMsg = null - checkLogPostExit { - if (it.contains('Not starting IAST subsystem')) { - errorMsg = it - } - if (it.contains('IAST is starting')) { - startMsg = it - } - // Check that there's no logged exception about missing classes from Datadog. - // We had this problem before with JDK9StackWalker. - if (it.contains('java.lang.ClassNotFoundException: datadog/')) { - errorMsg = it - } - } - - then: 'there are no errors in the log and IAST has started' - errorMsg == null - startMsg != null - !logHasErrors - } - - void 'default home page without errors'() { - setup: - String url = "http://localhost:${httpPort}/greeting" - def request = new Request.Builder().url(url).get().build() - - when: - def response = client.newCall(request).execute() - - then: - def responseBodyStr = response.body().string() - responseBodyStr != null - responseBodyStr.contains('Sup Dawg') - response.body().contentType().toString().contains('text/plain') - response.code() == 200 - - checkLogPostExit() - !logHasErrors - } - - void 'Multipart Request parameters'() { - given: - String url = "http://localhost:${httpPort}/multipart" - - RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) - .addFormDataPart("theFile", "theFileName", - RequestBody.create(MediaType.parse("text/plain"), "FILE_CONTENT")) - .addFormDataPart("param1", "param1Value") - .build() - - Request request = new Request.Builder() - .url(url) - .post(requestBody) - .build() - when: - final retValue = client.newCall(request).execute().body().string() - - then: - retValue == "fileName: theFile" - hasTainted { tainted -> - tainted.value == 'theFile' && - tainted.ranges[0].source.name == 'Content-Disposition' && - tainted.ranges[0].source.origin == 'http.request.multipart.parameter' - } - - } - - void 'Multipart Request original file name'() { - given: - String url = "http://localhost:${httpPort}/multipart" - - RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) - .addFormDataPart("theFile", "theFileName", - RequestBody.create(MediaType.parse("text/plain"), "FILE_CONTENT")) - .addFormDataPart("param1", "param1Value") - .build() - - Request request = new Request.Builder() - .url(url) - .post(requestBody) - .build() - when: - final retValue = client.newCall(request).execute().body().string() - - then: - retValue == "fileName: theFile" - hasTainted { tainted -> - tainted.value == 'theFileName' && - tainted.ranges[0].source.name == 'filename' && - tainted.ranges[0].source.origin == 'http.request.multipart.parameter' - } - - } - - - void 'iast.enabled tag is present'() { - setup: - String url = "http://localhost:${httpPort}/greeting" - def request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasMetric('_dd.iast.enabled', 1) - } - - void 'vulnerabilities have stacktrace'(){ - setup: - final url = "http://localhost:${httpPort}/cmdi/runtime?cmd=ls" - final request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerabilityStack() - } - - void 'weak hash vulnerability is present'() { - setup: - String url = "http://localhost:${httpPort}/weakhash" - def request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> - vul.type == 'WEAK_HASH' && - vul.evidence.value == 'MD5' - } - } - - void 'weak cipher vulnerability is present when calling key generator'() { - setup: - String url = "http://localhost:${httpPort}/weak_key_generator" - def request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> - vul.type == 'WEAK_CIPHER' && - vul.evidence.value == 'DES' - } - } - - void 'weak cipher vulnerability is present when calling key generator with provider'() { - setup: - String url = "http://localhost:${httpPort}/weak_key_generator_with_provider" - def request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> - vul.type == 'WEAK_CIPHER' && - vul.evidence.value == 'DES' - } - } - - - void 'insecure cookie vulnerability is present'() { - setup: - String url = "http://localhost:${httpPort}/insecure_cookie" - def request = new Request.Builder().url(url).get().build() - - when: - def response = client.newCall(request).execute() - - then: - response.isSuccessful() - response.header('Set-Cookie').contains('user-id') - hasVulnerability { vul -> - vul.type == 'INSECURE_COOKIE' && - vul.evidence.value == 'user-id' - } - } - - void 'hsts header missing vulnerability is present'() { - setup: - String url = "http://localhost:${httpPort}/hstsmissing" - def request = new Request.Builder().url(url).header("X-Forwarded-Proto", "https").get().build() - - when: - def response = client.newCall(request).execute() - - then: - response.isSuccessful() - hasVulnerability { vul -> - vul.type == 'HSTS_HEADER_MISSING' - } - } - - void 'X content type options missing header vulnerability is present'() { - setup: - String url = "http://localhost:${httpPort}/xcontenttypeoptionsmissing" - def request = new Request.Builder().url(url).get().build() - when: - def response = client.newCall(request).execute() - then: - response.isSuccessful() - hasVulnerability { vul -> - vul.type == 'XCONTENTTYPE_HEADER_MISSING' - } - } - - void 'X content type options missing header vulnerability is absent'() { - setup: - String url = "http://localhost:${httpPort}/xcontenttypeoptionsecure" - def request = new Request.Builder().url(url).get().build() - when: - def response = client.newCall(request).execute() - then: - response.isSuccessful() - noVulnerability { vul -> - vul.type == 'XCONTENTTYPE_HEADER_MISSING' - } - } - - - void 'no HttpOnly cookie vulnerability is present'() { - setup: - String url = "http://localhost:${httpPort}/insecure_cookie" - def request = new Request.Builder().url(url).get().build() - - when: - def response = client.newCall(request).execute() - - then: - response.isSuccessful() - response.header('Set-Cookie').contains('user-id') - hasVulnerability { vul -> - vul.type == 'NO_HTTPONLY_COOKIE' && - vul.evidence.value == 'user-id' - } - } - - void 'no SameSite cookie vulnerability is present'() { - setup: - String url = "http://localhost:${httpPort}/insecure_cookie" - def request = new Request.Builder().url(url).get().build() - - when: - def response = client.newCall(request).execute() - - then: - response.isSuccessful() - response.header('Set-Cookie').contains('user-id') - hasVulnerability { vul -> - vul.type == 'NO_SAMESITE_COOKIE' && - vul.evidence.value == 'user-id' - } - } - - void 'insecure cookie vulnerability from addheader is present'() { - setup: - String url = "http://localhost:${httpPort}/insecure_cookie_from_header" - def request = new Request.Builder().url(url).get().build() - - when: - def response = client.newCall(request).execute() - - then: - response.isSuccessful() - response.header('Set-Cookie').contains('user-id') - hasVulnerability { vul -> - vul.type == 'INSECURE_COOKIE' && - vul.evidence.value == 'user-id' - } - } - - - void 'weak hash vulnerability is present on boot'() { - setup: - String url = "http://localhost:${httpPort}/greeting" - def request = new Request.Builder().url(url).get().build() - - when: 'ensure the controller is loaded' - client.newCall(request).execute() - - then: 'a vulnerability pops in the logs (startup traces might not always be available)' - hasVulnerabilityInLogs { vul -> - vul.type == 'WEAK_HASH' && - vul.evidence.value == 'SHA1' && - vul.location.spanId > 0 - } - } - - void 'weak hash vulnerability is present on thread'() { - setup: - String url = "http://localhost:${httpPort}/async_weakhash" - def request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> - vul.type == 'WEAK_HASH' && - vul.evidence.value == 'MD4' && - vul.location.spanId > 0 - } - } - - void 'getParameter taints string'() { - setup: - String url = "http://localhost:${httpPort}/getparameter?param=A" - def request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasTainted { tainted -> - tainted.value == 'A' && - tainted.ranges[0].source.name == 'param' && - tainted.ranges[0].source.origin == 'http.request.parameter' - } - } - - void 'command injection is present with runtime'() { - setup: - final url = "http://localhost:${httpPort}/cmdi/runtime?cmd=ls" - final request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> vul.type == 'COMMAND_INJECTION' } - } - - void 'command injection is present with process builder'() { - setup: - final url = "http://localhost:${httpPort}/cmdi/process_builder?cmd=ls" - final request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> vul.type == 'COMMAND_INJECTION' } - } - - void 'xpath injection is present when compile expression'() { - setup: - final url = "http://localhost:${httpPort}/xpathi/compile?expression=%2Fbookstore%2Fbook%2Ftitle" - final request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> vul.type == 'XPATH_INJECTION' } - } - - - void 'xpath injection is present when evaluate expression'() { - setup: - final url = "http://localhost:${httpPort}/xpathi/evaluate?expression=%2Fbookstore%2Fbook%2Ftitle" - final request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> vul.type == 'XPATH_INJECTION' } - } - - void 'trust boundary violation is present'() { - setup: - final url = "http://localhost:${httpPort}/trust_boundary_violation?paramValue=test" - final request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> vul.type == 'TRUST_BOUNDARY_VIOLATION' } - } - - void 'xss is present'() { - setup: - final url = "http://localhost:${httpPort}/xss/${method}?string=${param}" - final request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> vul.type == 'XSS' && vul.location.method == method } - - where: - method | param - 'write' | 'test' - 'write2' | 'test' - 'write3' | 'test' - 'write4' | 'test' - 'print' | 'test' - 'print2' | 'test' - 'println' | 'test' - 'println2' | 'test' - 'printf' | 'Formatted%20like%3A%20%251%24s%20and%20%252%24s.' - 'printf2' | 'test' - 'printf3' | 'Formatted%20like%3A%20%251%24s%20and%20%252%24s.' - 'printf4' | 'test' - 'format' | 'Formatted%20like%3A%20%251%24s%20and%20%252%24s.' - 'format2' | 'test' - 'format3' | 'Formatted%20like%3A%20%251%24s%20and%20%252%24s.' - 'format4' | 'test' - 'responseBody' | 'test' - } - - void 'trust boundary violation with cookie propagation'() { - setup: - final url = "http://localhost:${httpPort}/trust_boundary_violation_for_cookie" - final request = new Request.Builder().url(url).get().addHeader("Cookie", "https%3A%2F%2Fuser-id2=https%3A%2F%2Fkkk").build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> vul.type == 'TRUST_BOUNDARY_VIOLATION' } - hasTainted { tainted -> - tainted.value == 'https%3A%2F%2Fuser-id2' && - tainted.ranges[0].source.origin == 'http.request.cookie.name' - } - hasTainted { tainted -> - tainted.value == 'https://kkk' && - tainted.ranges[0].source.origin == 'http.request.cookie.value' - } - } - - - void 'path traversal is present with file'() { - setup: - final url = "http://localhost:${httpPort}/path_traversal/file?path=test" - final request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> vul.type == 'PATH_TRAVERSAL' } - } - - void 'path traversal is present with paths'() { - setup: - final url = "http://localhost:${httpPort}/path_traversal/paths?path=test" - final request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> vul.type == 'PATH_TRAVERSAL' } - } - - void 'path traversal is present with path'() { - setup: - final url = "http://localhost:${httpPort}/path_traversal/path?path=test" - final request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> vul.type == 'PATH_TRAVERSAL' } - } - - void 'parameter binding taints bean strings'() { - setup: - String url = "http://localhost:${httpPort}/param_binding/test?name=parameter&value=binding" - def request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasTainted { tainted -> - tainted.value == 'binding' && - tainted.ranges[0].source.name == 'value' && - tainted.ranges[0].source.origin == 'http.request.parameter' - } - } - - void 'getRequestURL taints its output'() { - setup: - String url = "http://localhost:${httpPort}/getrequesturl" - def request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasTainted { tainted -> - tainted.value == url && - tainted.ranges[0].source.origin == 'http.request.uri' - } - } - - void 'request header taint string'() { - setup: - String url = "http://localhost:${httpPort}/request_header/test" - def request = new Request.Builder().url(url).header("test-header", "test").get().build() - - when: - client.newCall(request).execute() - - then: - hasTainted { tainted -> - tainted.value == 'test' && - tainted.ranges[0].source.name == 'test-header' && - tainted.ranges[0].source.origin == 'http.request.header' - } - } - - void 'path param taint string'() { - setup: - String url = "http://localhost:${httpPort}/path_param?param=test" - def request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasTainted { tainted -> - tainted.value == 'test' && - tainted.ranges[0].source.name == 'param' && - tainted.ranges[0].source.origin == 'http.request.parameter' - } - } - - void 'request body taint json'() { - setup: - String url = "http://localhost:${httpPort}/request_body/test" - def request = new Request.Builder().url(url).post(RequestBody.create(JSON, '{"name": "nameTest", "value" : "valueTest"}')).build() - - when: - client.newCall(request).execute() - - then: - hasTainted { tainted -> - tainted.value == 'nameTest' && - tainted.ranges[0].source.origin == 'http.request.body' - } - } - - void 'request query string'() { - given: - final url = "http://localhost:${httpPort}/query_string?key=value" - final request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasTainted { tainted -> - tainted.value == 'key=value' && - tainted.ranges[0].source.origin == 'http.request.query' - } - } - - void 'request cookie propagation'() { - given: - final url = "http://localhost:${httpPort}/cookie" - final request = new Request.Builder().url(url).header('Cookie', 'name=value').get().build() - - when: - client.newCall(request).execute() - - then: - hasTainted { tainted -> - tainted.value == 'name' && - tainted.ranges[0].source.origin == 'http.request.cookie.name' - } - hasTainted { tainted -> - tainted.value == 'value' && - tainted.ranges[0].source.name == 'name' && - tainted.ranges[0].source.origin == 'http.request.cookie.value' - } - } - - void 'tainting of path variables — simple variant'() { - given: - String url = "http://localhost:${httpPort}/simple/foobar" - def request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasTainted { - it.value == 'foobar' && - it.ranges[0].source.origin == 'http.request.path.parameter' && - it.ranges[0].source.name == 'var1' - } - } - - @SuppressWarnings('CyclomaticComplexity') - void 'tainting of path variables — RequestMappingInfoHandlerMapping variant'() { - given: - String url = "http://localhost:${httpPort}/matrix/value1;xxx=aaa,bbb;yyy=ccc/value2;zzz=ddd" - def request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasTainted { tainted -> - final firstRange = tainted.ranges[0] - tainted.value == 'value1' && - firstRange?.source?.origin == 'http.request.path.parameter' && - firstRange?.source?.name == 'var1' - } - ['xxx', 'aaa', 'bbb', 'yyy', 'ccc'].each { - hasTainted { tainted -> - final firstRange = tainted.ranges[0] - tainted.value == it && - firstRange?.source?.origin == 'http.request.matrix.parameter' && - firstRange?.source?.name == 'var1' - } - } - hasTainted { tainted -> - final firstRange = tainted.ranges[0] - tainted.value == 'value2' && - firstRange?.source?.origin == 'http.request.path.parameter' && - firstRange?.source?.name == 'var2' - } - ['zzz', 'ddd'].each { - hasTainted { tainted -> - final firstRange = tainted.ranges[0] - tainted.value = it && - firstRange?.source?.origin == 'http.request.matrix.parameter' && - firstRange?.source?.name == 'var2' - } - } - } + // void 'IAST subsystem starts'() { + // given: 'an initial request has succeeded' + // String url = "http://localhost:${httpPort}/greeting" + // def request = new Request.Builder().url(url).get().build() + // client.newCall(request).execute() + // + // when: 'logs are read' + // String startMsg = null + // String errorMsg = null + // checkLogPostExit { + // if (it.contains('Not starting IAST subsystem')) { + // errorMsg = it + // } + // if (it.contains('IAST is starting')) { + // startMsg = it + // } + // // Check that there's no logged exception about missing classes from Datadog. + // // We had this problem before with JDK9StackWalker. + // if (it.contains('java.lang.ClassNotFoundException: datadog/')) { + // errorMsg = it + // } + // } + // + // then: 'there are no errors in the log and IAST has started' + // errorMsg == null + // startMsg != null + // !logHasErrors + // } + // + // void 'default home page without errors'() { + // setup: + // String url = "http://localhost:${httpPort}/greeting" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // def response = client.newCall(request).execute() + // + // then: + // def responseBodyStr = response.body().string() + // responseBodyStr != null + // responseBodyStr.contains('Sup Dawg') + // response.body().contentType().toString().contains('text/plain') + // response.code() == 200 + // + // checkLogPostExit() + // !logHasErrors + // } + // + // void 'Multipart Request parameters'() { + // given: + // String url = "http://localhost:${httpPort}/multipart" + // + // RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) + // .addFormDataPart("theFile", "theFileName", + // RequestBody.create(MediaType.parse("text/plain"), "FILE_CONTENT")) + // .addFormDataPart("param1", "param1Value") + // .build() + // + // Request request = new Request.Builder() + // .url(url) + // .post(requestBody) + // .build() + // when: + // final retValue = client.newCall(request).execute().body().string() + // + // then: + // retValue == "fileName: theFile" + // hasTainted { tainted -> + // tainted.value == 'theFile' && + // tainted.ranges[0].source.name == 'Content-Disposition' && + // tainted.ranges[0].source.origin == 'http.request.multipart.parameter' + // } + // + // } + // + // void 'Multipart Request original file name'() { + // given: + // String url = "http://localhost:${httpPort}/multipart" + // + // RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) + // .addFormDataPart("theFile", "theFileName", + // RequestBody.create(MediaType.parse("text/plain"), "FILE_CONTENT")) + // .addFormDataPart("param1", "param1Value") + // .build() + // + // Request request = new Request.Builder() + // .url(url) + // .post(requestBody) + // .build() + // when: + // final retValue = client.newCall(request).execute().body().string() + // + // then: + // retValue == "fileName: theFile" + // hasTainted { tainted -> + // tainted.value == 'theFileName' && + // tainted.ranges[0].source.name == 'filename' && + // tainted.ranges[0].source.origin == 'http.request.multipart.parameter' + // } + // + // } + // + // + // void 'iast.enabled tag is present'() { + // setup: + // String url = "http://localhost:${httpPort}/greeting" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasMetric('_dd.iast.enabled', 1) + // } + // + // void 'vulnerabilities have stacktrace'(){ + // setup: + // final url = "http://localhost:${httpPort}/cmdi/runtime?cmd=ls" + // final request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerabilityStack() + // } + // + // void 'weak hash vulnerability is present'() { + // setup: + // String url = "http://localhost:${httpPort}/weakhash" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> + // vul.type == 'WEAK_HASH' && + // vul.evidence.value == 'MD5' + // } + // } + // + // void 'weak cipher vulnerability is present when calling key generator'() { + // setup: + // String url = "http://localhost:${httpPort}/weak_key_generator" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> + // vul.type == 'WEAK_CIPHER' && + // vul.evidence.value == 'DES' + // } + // } + // + // void 'weak cipher vulnerability is present when calling key generator with provider'() { + // setup: + // String url = "http://localhost:${httpPort}/weak_key_generator_with_provider" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> + // vul.type == 'WEAK_CIPHER' && + // vul.evidence.value == 'DES' + // } + // } + // + // + // void 'insecure cookie vulnerability is present'() { + // setup: + // String url = "http://localhost:${httpPort}/insecure_cookie" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // def response = client.newCall(request).execute() + // + // then: + // response.isSuccessful() + // response.header('Set-Cookie').contains('user-id') + // hasVulnerability { vul -> + // vul.type == 'INSECURE_COOKIE' && + // vul.evidence.value == 'user-id' + // } + // } + // + // void 'hsts header missing vulnerability is present'() { + // setup: + // String url = "http://localhost:${httpPort}/hstsmissing" + // def request = new Request.Builder().url(url).header("X-Forwarded-Proto", "https").get().build() + // + // when: + // def response = client.newCall(request).execute() + // + // then: + // response.isSuccessful() + // hasVulnerability { vul -> + // vul.type == 'HSTS_HEADER_MISSING' + // } + // } + // + // void 'X content type options missing header vulnerability is present'() { + // setup: + // String url = "http://localhost:${httpPort}/xcontenttypeoptionsmissing" + // def request = new Request.Builder().url(url).get().build() + // when: + // def response = client.newCall(request).execute() + // then: + // response.isSuccessful() + // hasVulnerability { vul -> + // vul.type == 'XCONTENTTYPE_HEADER_MISSING' + // } + // } + // + // void 'X content type options missing header vulnerability is absent'() { + // setup: + // String url = "http://localhost:${httpPort}/xcontenttypeoptionsecure" + // def request = new Request.Builder().url(url).get().build() + // when: + // def response = client.newCall(request).execute() + // then: + // response.isSuccessful() + // noVulnerability { vul -> + // vul.type == 'XCONTENTTYPE_HEADER_MISSING' + // } + // } + // + // + // void 'no HttpOnly cookie vulnerability is present'() { + // setup: + // String url = "http://localhost:${httpPort}/insecure_cookie" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // def response = client.newCall(request).execute() + // + // then: + // response.isSuccessful() + // response.header('Set-Cookie').contains('user-id') + // hasVulnerability { vul -> + // vul.type == 'NO_HTTPONLY_COOKIE' && + // vul.evidence.value == 'user-id' + // } + // } + // + // void 'no SameSite cookie vulnerability is present'() { + // setup: + // String url = "http://localhost:${httpPort}/insecure_cookie" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // def response = client.newCall(request).execute() + // + // then: + // response.isSuccessful() + // response.header('Set-Cookie').contains('user-id') + // hasVulnerability { vul -> + // vul.type == 'NO_SAMESITE_COOKIE' && + // vul.evidence.value == 'user-id' + // } + // } + // + // void 'insecure cookie vulnerability from addheader is present'() { + // setup: + // String url = "http://localhost:${httpPort}/insecure_cookie_from_header" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // def response = client.newCall(request).execute() + // + // then: + // response.isSuccessful() + // response.header('Set-Cookie').contains('user-id') + // hasVulnerability { vul -> + // vul.type == 'INSECURE_COOKIE' && + // vul.evidence.value == 'user-id' + // } + // } + // + // + // void 'weak hash vulnerability is present on boot'() { + // setup: + // String url = "http://localhost:${httpPort}/greeting" + // def request = new Request.Builder().url(url).get().build() + // + // when: 'ensure the controller is loaded' + // client.newCall(request).execute() + // + // then: 'a vulnerability pops in the logs (startup traces might not always be available)' + // hasVulnerabilityInLogs { vul -> + // vul.type == 'WEAK_HASH' && + // vul.evidence.value == 'SHA1' && + // vul.location.spanId > 0 + // } + // } + // + // void 'weak hash vulnerability is present on thread'() { + // setup: + // String url = "http://localhost:${httpPort}/async_weakhash" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> + // vul.type == 'WEAK_HASH' && + // vul.evidence.value == 'MD4' && + // vul.location.spanId > 0 + // } + // } + // + // void 'getParameter taints string'() { + // setup: + // String url = "http://localhost:${httpPort}/getparameter?param=A" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasTainted { tainted -> + // tainted.value == 'A' && + // tainted.ranges[0].source.name == 'param' && + // tainted.ranges[0].source.origin == 'http.request.parameter' + // } + // } + // + // void 'command injection is present with runtime'() { + // setup: + // final url = "http://localhost:${httpPort}/cmdi/runtime?cmd=ls" + // final request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> vul.type == 'COMMAND_INJECTION' } + // } + // + // void 'command injection is present with process builder'() { + // setup: + // final url = "http://localhost:${httpPort}/cmdi/process_builder?cmd=ls" + // final request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> vul.type == 'COMMAND_INJECTION' } + // } + // + // void 'xpath injection is present when compile expression'() { + // setup: + // final url = "http://localhost:${httpPort}/xpathi/compile?expression=%2Fbookstore%2Fbook%2Ftitle" + // final request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> vul.type == 'XPATH_INJECTION' } + // } + // + // + // void 'xpath injection is present when evaluate expression'() { + // setup: + // final url = "http://localhost:${httpPort}/xpathi/evaluate?expression=%2Fbookstore%2Fbook%2Ftitle" + // final request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> vul.type == 'XPATH_INJECTION' } + // } + // + // void 'trust boundary violation is present'() { + // setup: + // final url = "http://localhost:${httpPort}/trust_boundary_violation?paramValue=test" + // final request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> vul.type == 'TRUST_BOUNDARY_VIOLATION' } + // } + // + // void 'xss is present'() { + // setup: + // final url = "http://localhost:${httpPort}/xss/${method}?string=${param}" + // final request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> vul.type == 'XSS' && vul.location.method == method } + // + // where: + // method | param + // 'write' | 'test' + // 'write2' | 'test' + // 'write3' | 'test' + // 'write4' | 'test' + // 'print' | 'test' + // 'print2' | 'test' + // 'println' | 'test' + // 'println2' | 'test' + // 'printf' | 'Formatted%20like%3A%20%251%24s%20and%20%252%24s.' + // 'printf2' | 'test' + // 'printf3' | 'Formatted%20like%3A%20%251%24s%20and%20%252%24s.' + // 'printf4' | 'test' + // 'format' | 'Formatted%20like%3A%20%251%24s%20and%20%252%24s.' + // 'format2' | 'test' + // 'format3' | 'Formatted%20like%3A%20%251%24s%20and%20%252%24s.' + // 'format4' | 'test' + // 'responseBody' | 'test' + // } + // + // void 'trust boundary violation with cookie propagation'() { + // setup: + // final url = "http://localhost:${httpPort}/trust_boundary_violation_for_cookie" + // final request = new Request.Builder().url(url).get().addHeader("Cookie", "https%3A%2F%2Fuser-id2=https%3A%2F%2Fkkk").build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> vul.type == 'TRUST_BOUNDARY_VIOLATION' } + // hasTainted { tainted -> + // tainted.value == 'https%3A%2F%2Fuser-id2' && + // tainted.ranges[0].source.origin == 'http.request.cookie.name' + // } + // hasTainted { tainted -> + // tainted.value == 'https://kkk' && + // tainted.ranges[0].source.origin == 'http.request.cookie.value' + // } + // } + // + // + // void 'path traversal is present with file'() { + // setup: + // final url = "http://localhost:${httpPort}/path_traversal/file?path=test" + // final request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> vul.type == 'PATH_TRAVERSAL' } + // } + // + // void 'path traversal is present with paths'() { + // setup: + // final url = "http://localhost:${httpPort}/path_traversal/paths?path=test" + // final request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> vul.type == 'PATH_TRAVERSAL' } + // } + // + // void 'path traversal is present with path'() { + // setup: + // final url = "http://localhost:${httpPort}/path_traversal/path?path=test" + // final request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> vul.type == 'PATH_TRAVERSAL' } + // } + // + // void 'parameter binding taints bean strings'() { + // setup: + // String url = "http://localhost:${httpPort}/param_binding/test?name=parameter&value=binding" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasTainted { tainted -> + // tainted.value == 'binding' && + // tainted.ranges[0].source.name == 'value' && + // tainted.ranges[0].source.origin == 'http.request.parameter' + // } + // } + // + // void 'getRequestURL taints its output'() { + // setup: + // String url = "http://localhost:${httpPort}/getrequesturl" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasTainted { tainted -> + // tainted.value == url && + // tainted.ranges[0].source.origin == 'http.request.uri' + // } + // } + // + // void 'request header taint string'() { + // setup: + // String url = "http://localhost:${httpPort}/request_header/test" + // def request = new Request.Builder().url(url).header("test-header", "test").get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasTainted { tainted -> + // tainted.value == 'test' && + // tainted.ranges[0].source.name == 'test-header' && + // tainted.ranges[0].source.origin == 'http.request.header' + // } + // } + // + // void 'path param taint string'() { + // setup: + // String url = "http://localhost:${httpPort}/path_param?param=test" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasTainted { tainted -> + // tainted.value == 'test' && + // tainted.ranges[0].source.name == 'param' && + // tainted.ranges[0].source.origin == 'http.request.parameter' + // } + // } + // + // void 'request body taint json'() { + // setup: + // String url = "http://localhost:${httpPort}/request_body/test" + // def request = new Request.Builder().url(url).post(RequestBody.create(JSON, '{"name": "nameTest", "value" : "valueTest"}')).build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasTainted { tainted -> + // tainted.value == 'nameTest' && + // tainted.ranges[0].source.origin == 'http.request.body' + // } + // } + // + // void 'request query string'() { + // given: + // final url = "http://localhost:${httpPort}/query_string?key=value" + // final request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasTainted { tainted -> + // tainted.value == 'key=value' && + // tainted.ranges[0].source.origin == 'http.request.query' + // } + // } + // + // void 'request cookie propagation'() { + // given: + // final url = "http://localhost:${httpPort}/cookie" + // final request = new Request.Builder().url(url).header('Cookie', 'name=value').get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasTainted { tainted -> + // tainted.value == 'name' && + // tainted.ranges[0].source.origin == 'http.request.cookie.name' + // } + // hasTainted { tainted -> + // tainted.value == 'value' && + // tainted.ranges[0].source.name == 'name' && + // tainted.ranges[0].source.origin == 'http.request.cookie.value' + // } + // } + // + // void 'tainting of path variables — simple variant'() { + // given: + // String url = "http://localhost:${httpPort}/simple/foobar" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasTainted { + // it.value == 'foobar' && + // it.ranges[0].source.origin == 'http.request.path.parameter' && + // it.ranges[0].source.name == 'var1' + // } + // } + // + // @SuppressWarnings('CyclomaticComplexity') + // void 'tainting of path variables — RequestMappingInfoHandlerMapping variant'() { + // given: + // String url = "http://localhost:${httpPort}/matrix/value1;xxx=aaa,bbb;yyy=ccc/value2;zzz=ddd" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasTainted { tainted -> + // final firstRange = tainted.ranges[0] + // tainted.value == 'value1' && + // firstRange?.source?.origin == 'http.request.path.parameter' && + // firstRange?.source?.name == 'var1' + // } + // ['xxx', 'aaa', 'bbb', 'yyy', 'ccc'].each { + // hasTainted { tainted -> + // final firstRange = tainted.ranges[0] + // tainted.value == it && + // firstRange?.source?.origin == 'http.request.matrix.parameter' && + // firstRange?.source?.name == 'var1' + // } + // } + // hasTainted { tainted -> + // final firstRange = tainted.ranges[0] + // tainted.value == 'value2' && + // firstRange?.source?.origin == 'http.request.path.parameter' && + // firstRange?.source?.name == 'var2' + // } + // ['zzz', 'ddd'].each { + // hasTainted { tainted -> + // final firstRange = tainted.ranges[0] + // tainted.value = it && + // firstRange?.source?.origin == 'http.request.matrix.parameter' && + // firstRange?.source?.name == 'var2' + // } + // } + // } void 'ssrf is present'() { setup: @@ -756,433 +759,434 @@ abstract class AbstractIastSpringBootTest extends AbstractIastServerSmokeTest { path | parameter | value | method | protocolSecure "apache-httpclient4" | "url" | "https://dd.datad0g.com/" | "apacheHttpClient4" | false "apache-httpclient4" | "host" | "dd.datad0g.com" | "apacheHttpClient4" | false + "apache-httpclient5" | "url" | "https://dd.datad0g.com/" | "apacheHttpClient5" | false "commons-httpclient2" | "url" | "https://dd.datad0g.com/" | "commonsHttpClient2" | false "okHttp2" | "url" | "https://dd.datad0g.com/" | "okHttp2" | false "okHttp3" | "url" | "https://dd.datad0g.com/" | "okHttp3" | false } - void 'test iast metrics stored in spans'() { - setup: - final url = "http://localhost:${httpPort}/cmdi/runtime?cmd=ls" - final request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasMetric('_dd.iast.telemetry.executed.sink.command_injection', 1) - } - - void 'weak randomness is present in #evidence'() { - setup: - final url = "http://localhost:${httpPort}/weak_randomness?mode=${evidence}" - final request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> vul.type == 'WEAK_RANDOMNESS' && vul.evidence.value == evidence } - - where: - evidence | _ - 'java.util.Random' | _ - 'java.util.concurrent.ThreadLocalRandom' | _ - 'java.lang.Math' | _ - } - - def "unvalidated redirect from addheader is present"() { - setup: - String url = "http://localhost:${httpPort}/unvalidated_redirect_from_header?param=redirected" - def request = new Request.Builder().url(url).get().build() - - when: - def response = client.newCall(request).execute() - - then: - response.isRedirect() - response.header("Location").contains("redirected") - hasVulnerability { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromHeader' } - } - - def "unvalidated redirect from sendRedirect is present"() { - setup: - String url = "http://localhost:${httpPort}/unvalidated_redirect_from_send_redirect?param=redirected" - def request = new Request.Builder().url(url).get().build() - - when: - def response = client.newCall(request).execute() - - then: - response.isRedirect() - // TODO: span deserialization fails when checking the vulnerability - // === Failure during message v0.4 decoding === - //org.msgpack.core.MessageTypeException: Expected String, but got Nil (c0) - hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromSendRedirect' } - } - - def "unvalidated redirect from forward is present"() { - setup: - String url = "http://localhost:${httpPort}/unvalidated_redirect_from_forward?param=redirected" - def request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromForward' } - } - - def "unvalidated redirect from RedirectView is present"() { - setup: - String url = "http://localhost:${httpPort}/unvalidated_redirect_from_redirect_view?param=redirected" - def request = new Request.Builder().url(url).get().build() - - when: - def response = client.newCall(request).execute() - - then: - response.isRedirect() - hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromRedirectView' } - } - - def "unvalidated redirect from ModelAndView is present"() { - setup: - String url = "http://localhost:${httpPort}/unvalidated_redirect_from_model_and_view?param=redirected" - def request = new Request.Builder().url(url).get().build() - - when: - def response = client.newCall(request).execute() - - then: - response.isRedirect() - hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromModelAndView' } - } - - - def "unvalidated redirect forward from ModelAndView is present"() { - setup: - String url = "http://localhost:${httpPort}/unvalidated_redirect_forward_from_model_and_view?param=redirected" - def request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectForwardFromModelAndView' } - } - - - def "unvalidated redirect from string"() { - setup: - String url = "http://localhost:${httpPort}/unvalidated_redirect_from_string?param=redirected" - def request = new Request.Builder().url(url).get().build() - - when: - def response = client.newCall(request).execute() - - then: - response.isRedirect() - hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromString' } - } - - def "unvalidated redirect forward from string"() { - setup: - String url = "http://localhost:${httpPort}/unvalidated_redirect_forward_from_string?param=redirected" - def request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectForwardFromString' } - } - - def "get View from tainted string"() { - setup: - String url = "http://localhost:${httpPort}/get_view_from_tainted_string?param=redirected" - def request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'getViewfromTaintedString' } - } - - void 'getRequestURI taints its output'() { - setup: - final url = "http://localhost:${httpPort}/getrequesturi" - final request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasTainted { tainted -> - tainted.value == '/getrequesturi' && - tainted.ranges[0].source.origin == 'http.request.path' - } - } - - void 'header injection'() { - setup: - final url = "http://localhost:${httpPort}/header_injection?param=test" - final request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> vul.type == 'HEADER_INJECTION' } - } - - void 'header injection exclusion'() { - setup: - final url = "http://localhost:${httpPort}/header_injection_exclusion?param=testExclusion" - final request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - noVulnerability { vul -> vul.type == 'HEADER_INJECTION' } - } - - void 'header injection redaction'() { - setup: - String bearer = URLEncoder.encode("Authorization: bearer 12345644", "UTF-8") - final url = "http://localhost:${httpPort}/header_injection_redaction?param=" + bearer - final request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> vul.type == 'HEADER_INJECTION' && vul.evidence.valueParts[1].redacted == true } - } - - void 'Insecure Auth Protocol vulnerability is present'() { - setup: - String url = "http://localhost:${httpPort}/insecureAuthProtocol" - def request = new Request.Builder().url(url).header("Authorization", "Basic YWxhZGRpbjpvcGVuc2VzYW1l").get().build() - when: - def response = client.newCall(request).execute() - then: - response.isSuccessful() - hasVulnerability { vul -> - vul.type == 'INSECURE_AUTH_PROTOCOL' - } - } - - void "Check reflection injection forName"() { - setup: - String url = "http://localhost:${httpPort}/reflection_injection/class?param=java.lang.String" - def request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> - vul.type == 'REFLECTION_INJECTION' - && vul.location.method == 'reflectionInjectionClass' - && vul.evidence.valueParts[0].value == "java.lang.String" - } - } - - void "Check reflection injection getMethod"() { - setup: - String url = "http://localhost:${httpPort}/reflection_injection/method?param=isEmpty" - def request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> - vul.type == 'REFLECTION_INJECTION' - && vul.location.method == 'reflectionInjectionMethod' - && vul.evidence.valueParts[0].value == "java.lang.String#" - && vul.evidence.valueParts[1].value == "isEmpty" - && vul.evidence.valueParts[2].value == "()" - } - } - - void "Check reflection injection getField"() { - setup: - String url = "http://localhost:${httpPort}/reflection_injection/field?param=hash" - def request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> - vul.type == 'REFLECTION_INJECTION' - && vul.location.method == 'reflectionInjectionField' - && vul.evidence.valueParts[0].value == "java.lang.String#" - && vul.evidence.valueParts[1].value == "hash" - } - } - - void "Check reflection injection lookup"() { - setup: - String url = "http://localhost:${httpPort}/reflection_injection/lookup?param=hash" - def request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> - vul.type == 'REFLECTION_INJECTION' - && vul.location.method == 'reflectionInjectionLookup' - && vul.evidence.valueParts[0].value == "java.lang.String#" - && vul.evidence.valueParts[1].value == "hash" - } - } - - void 'find session rewriting'() { - given: - String url = "http://localhost:${httpPort}/greeting" - - when: - Response response = client.newCall(new Request.Builder().url(url).get().build()).execute() - - then: - response.successful - hasVulnerabilityInLogs { vul -> - vul.type == 'SESSION_REWRITING' - } - } - - void 'untrusted deserialization for an input stream'() { - setup: - final url = "http://localhost:${httpPort}/untrusted_deserialization" - ByteArrayOutputStream baos = new ByteArrayOutputStream() - ObjectOutputStream oos = new ObjectOutputStream(baos) - oos.writeObject("This is a test object.") - RequestBody requestBody = RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray()) - final request = new Request.Builder().url(url).post(requestBody).build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } - } - - void 'untrusted deserialization for a servlet file upload which calls parseRequest'() { - setup: - final url = "http://localhost:${httpPort}/untrusted_deserialization/parse_request" - ByteArrayOutputStream baos = new ByteArrayOutputStream() - ObjectOutputStream oos = new ObjectOutputStream(baos) - oos.writeObject("This is a test object.") - RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) - .addFormDataPart("file", "test.txt", - RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray())) - .build() - final request = new Request.Builder().url(url).post(requestBody).build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } - } - - void 'untrusted deserialization for a servlet file upload which calls parseParameterMap'() { - setup: - final url = "http://localhost:${httpPort}/untrusted_deserialization/parse_parameter_map" - ByteArrayOutputStream baos = new ByteArrayOutputStream() - ObjectOutputStream oos = new ObjectOutputStream(baos) - oos.writeObject("This is a test object.") - RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) - .addFormDataPart("file", "test.txt", - RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray())) - .build() - final request = new Request.Builder().url(url).post(requestBody).build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } - } - - void 'untrusted deserialization for a servlet file upload which calls getItemIterator'() { - setup: - final url = "http://localhost:${httpPort}/untrusted_deserialization/get_item_iterator" - ByteArrayOutputStream baos = new ByteArrayOutputStream() - ObjectOutputStream oos = new ObjectOutputStream(baos) - oos.writeObject("This is a test object.") - RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) - .addFormDataPart("file", "test.txt", - RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray())) - .build() - final request = new Request.Builder().url(url) - .post(requestBody).build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } - } - - void 'untrusted deserialization for a multipart file'() { - setup: - final url = "http://localhost:${httpPort}/untrusted_deserialization/multipart" - ByteArrayOutputStream baos = new ByteArrayOutputStream() - ObjectOutputStream oos = new ObjectOutputStream(baos) - oos.writeObject("This is a test object.") - RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) - .addFormDataPart("file", "test.txt", - RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray())) - .build() - final request = new Request.Builder().url(url) - .post(requestBody).build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } - } - - void 'untrusted deserialization for a part'() { - setup: - final url = "http://localhost:${httpPort}/untrusted_deserialization/part" - ByteArrayOutputStream baos = new ByteArrayOutputStream() - ObjectOutputStream oos = new ObjectOutputStream(baos) - oos.writeObject("This is a test object.") - RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) - .addFormDataPart("file", "test.txt", - RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray())) - .build() - final request = new Request.Builder().url(url) - .post(requestBody).build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } - } - - void 'untrusted deserialization for snakeyaml with a string'() { - setup: - final String yaml = "test" - final url = "http://localhost:${httpPort}/untrusted_deserialization/snakeyaml?yaml=${yaml}" - final request = new Request.Builder().url(url).get().build() - - when: - client.newCall(request).execute() - - then: - hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } - } + // void 'test iast metrics stored in spans'() { + // setup: + // final url = "http://localhost:${httpPort}/cmdi/runtime?cmd=ls" + // final request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasMetric('_dd.iast.telemetry.executed.sink.command_injection', 1) + // } + // + // void 'weak randomness is present in #evidence'() { + // setup: + // final url = "http://localhost:${httpPort}/weak_randomness?mode=${evidence}" + // final request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> vul.type == 'WEAK_RANDOMNESS' && vul.evidence.value == evidence } + // + // where: + // evidence | _ + // 'java.util.Random' | _ + // 'java.util.concurrent.ThreadLocalRandom' | _ + // 'java.lang.Math' | _ + // } + // + // def "unvalidated redirect from addheader is present"() { + // setup: + // String url = "http://localhost:${httpPort}/unvalidated_redirect_from_header?param=redirected" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // def response = client.newCall(request).execute() + // + // then: + // response.isRedirect() + // response.header("Location").contains("redirected") + // hasVulnerability { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromHeader' } + // } + // + // def "unvalidated redirect from sendRedirect is present"() { + // setup: + // String url = "http://localhost:${httpPort}/unvalidated_redirect_from_send_redirect?param=redirected" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // def response = client.newCall(request).execute() + // + // then: + // response.isRedirect() + // // TODO: span deserialization fails when checking the vulnerability + // // === Failure during message v0.4 decoding === + // //org.msgpack.core.MessageTypeException: Expected String, but got Nil (c0) + // hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromSendRedirect' } + // } + // + // def "unvalidated redirect from forward is present"() { + // setup: + // String url = "http://localhost:${httpPort}/unvalidated_redirect_from_forward?param=redirected" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromForward' } + // } + // + // def "unvalidated redirect from RedirectView is present"() { + // setup: + // String url = "http://localhost:${httpPort}/unvalidated_redirect_from_redirect_view?param=redirected" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // def response = client.newCall(request).execute() + // + // then: + // response.isRedirect() + // hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromRedirectView' } + // } + // + // def "unvalidated redirect from ModelAndView is present"() { + // setup: + // String url = "http://localhost:${httpPort}/unvalidated_redirect_from_model_and_view?param=redirected" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // def response = client.newCall(request).execute() + // + // then: + // response.isRedirect() + // hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromModelAndView' } + // } + // + // + // def "unvalidated redirect forward from ModelAndView is present"() { + // setup: + // String url = "http://localhost:${httpPort}/unvalidated_redirect_forward_from_model_and_view?param=redirected" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectForwardFromModelAndView' } + // } + // + // + // def "unvalidated redirect from string"() { + // setup: + // String url = "http://localhost:${httpPort}/unvalidated_redirect_from_string?param=redirected" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // def response = client.newCall(request).execute() + // + // then: + // response.isRedirect() + // hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromString' } + // } + // + // def "unvalidated redirect forward from string"() { + // setup: + // String url = "http://localhost:${httpPort}/unvalidated_redirect_forward_from_string?param=redirected" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectForwardFromString' } + // } + // + // def "get View from tainted string"() { + // setup: + // String url = "http://localhost:${httpPort}/get_view_from_tainted_string?param=redirected" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'getViewfromTaintedString' } + // } + // + // void 'getRequestURI taints its output'() { + // setup: + // final url = "http://localhost:${httpPort}/getrequesturi" + // final request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasTainted { tainted -> + // tainted.value == '/getrequesturi' && + // tainted.ranges[0].source.origin == 'http.request.path' + // } + // } + // + // void 'header injection'() { + // setup: + // final url = "http://localhost:${httpPort}/header_injection?param=test" + // final request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> vul.type == 'HEADER_INJECTION' } + // } + // + // void 'header injection exclusion'() { + // setup: + // final url = "http://localhost:${httpPort}/header_injection_exclusion?param=testExclusion" + // final request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // noVulnerability { vul -> vul.type == 'HEADER_INJECTION' } + // } + // + // void 'header injection redaction'() { + // setup: + // String bearer = URLEncoder.encode("Authorization: bearer 12345644", "UTF-8") + // final url = "http://localhost:${httpPort}/header_injection_redaction?param=" + bearer + // final request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> vul.type == 'HEADER_INJECTION' && vul.evidence.valueParts[1].redacted == true } + // } + // + // void 'Insecure Auth Protocol vulnerability is present'() { + // setup: + // String url = "http://localhost:${httpPort}/insecureAuthProtocol" + // def request = new Request.Builder().url(url).header("Authorization", "Basic YWxhZGRpbjpvcGVuc2VzYW1l").get().build() + // when: + // def response = client.newCall(request).execute() + // then: + // response.isSuccessful() + // hasVulnerability { vul -> + // vul.type == 'INSECURE_AUTH_PROTOCOL' + // } + // } + // + // void "Check reflection injection forName"() { + // setup: + // String url = "http://localhost:${httpPort}/reflection_injection/class?param=java.lang.String" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> + // vul.type == 'REFLECTION_INJECTION' + // && vul.location.method == 'reflectionInjectionClass' + // && vul.evidence.valueParts[0].value == "java.lang.String" + // } + // } + // + // void "Check reflection injection getMethod"() { + // setup: + // String url = "http://localhost:${httpPort}/reflection_injection/method?param=isEmpty" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> + // vul.type == 'REFLECTION_INJECTION' + // && vul.location.method == 'reflectionInjectionMethod' + // && vul.evidence.valueParts[0].value == "java.lang.String#" + // && vul.evidence.valueParts[1].value == "isEmpty" + // && vul.evidence.valueParts[2].value == "()" + // } + // } + // + // void "Check reflection injection getField"() { + // setup: + // String url = "http://localhost:${httpPort}/reflection_injection/field?param=hash" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> + // vul.type == 'REFLECTION_INJECTION' + // && vul.location.method == 'reflectionInjectionField' + // && vul.evidence.valueParts[0].value == "java.lang.String#" + // && vul.evidence.valueParts[1].value == "hash" + // } + // } + // + // void "Check reflection injection lookup"() { + // setup: + // String url = "http://localhost:${httpPort}/reflection_injection/lookup?param=hash" + // def request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> + // vul.type == 'REFLECTION_INJECTION' + // && vul.location.method == 'reflectionInjectionLookup' + // && vul.evidence.valueParts[0].value == "java.lang.String#" + // && vul.evidence.valueParts[1].value == "hash" + // } + // } + // + // void 'find session rewriting'() { + // given: + // String url = "http://localhost:${httpPort}/greeting" + // + // when: + // Response response = client.newCall(new Request.Builder().url(url).get().build()).execute() + // + // then: + // response.successful + // hasVulnerabilityInLogs { vul -> + // vul.type == 'SESSION_REWRITING' + // } + // } + // + // void 'untrusted deserialization for an input stream'() { + // setup: + // final url = "http://localhost:${httpPort}/untrusted_deserialization" + // ByteArrayOutputStream baos = new ByteArrayOutputStream() + // ObjectOutputStream oos = new ObjectOutputStream(baos) + // oos.writeObject("This is a test object.") + // RequestBody requestBody = RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray()) + // final request = new Request.Builder().url(url).post(requestBody).build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } + // } + // + // void 'untrusted deserialization for a servlet file upload which calls parseRequest'() { + // setup: + // final url = "http://localhost:${httpPort}/untrusted_deserialization/parse_request" + // ByteArrayOutputStream baos = new ByteArrayOutputStream() + // ObjectOutputStream oos = new ObjectOutputStream(baos) + // oos.writeObject("This is a test object.") + // RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) + // .addFormDataPart("file", "test.txt", + // RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray())) + // .build() + // final request = new Request.Builder().url(url).post(requestBody).build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } + // } + // + // void 'untrusted deserialization for a servlet file upload which calls parseParameterMap'() { + // setup: + // final url = "http://localhost:${httpPort}/untrusted_deserialization/parse_parameter_map" + // ByteArrayOutputStream baos = new ByteArrayOutputStream() + // ObjectOutputStream oos = new ObjectOutputStream(baos) + // oos.writeObject("This is a test object.") + // RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) + // .addFormDataPart("file", "test.txt", + // RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray())) + // .build() + // final request = new Request.Builder().url(url).post(requestBody).build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } + // } + // + // void 'untrusted deserialization for a servlet file upload which calls getItemIterator'() { + // setup: + // final url = "http://localhost:${httpPort}/untrusted_deserialization/get_item_iterator" + // ByteArrayOutputStream baos = new ByteArrayOutputStream() + // ObjectOutputStream oos = new ObjectOutputStream(baos) + // oos.writeObject("This is a test object.") + // RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) + // .addFormDataPart("file", "test.txt", + // RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray())) + // .build() + // final request = new Request.Builder().url(url) + // .post(requestBody).build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } + // } + // + // void 'untrusted deserialization for a multipart file'() { + // setup: + // final url = "http://localhost:${httpPort}/untrusted_deserialization/multipart" + // ByteArrayOutputStream baos = new ByteArrayOutputStream() + // ObjectOutputStream oos = new ObjectOutputStream(baos) + // oos.writeObject("This is a test object.") + // RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) + // .addFormDataPart("file", "test.txt", + // RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray())) + // .build() + // final request = new Request.Builder().url(url) + // .post(requestBody).build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } + // } + // + // void 'untrusted deserialization for a part'() { + // setup: + // final url = "http://localhost:${httpPort}/untrusted_deserialization/part" + // ByteArrayOutputStream baos = new ByteArrayOutputStream() + // ObjectOutputStream oos = new ObjectOutputStream(baos) + // oos.writeObject("This is a test object.") + // RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) + // .addFormDataPart("file", "test.txt", + // RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray())) + // .build() + // final request = new Request.Builder().url(url) + // .post(requestBody).build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } + // } + // + // void 'untrusted deserialization for snakeyaml with a string'() { + // setup: + // final String yaml = "test" + // final url = "http://localhost:${httpPort}/untrusted_deserialization/snakeyaml?yaml=${yaml}" + // final request = new Request.Builder().url(url).get().build() + // + // when: + // client.newCall(request).execute() + // + // then: + // hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } + // } } diff --git a/dd-smoke-tests/spring-boot-2.6-webmvc/build.gradle b/dd-smoke-tests/spring-boot-2.6-webmvc/build.gradle index d3e0161f508..d2d76846e80 100644 --- a/dd-smoke-tests/spring-boot-2.6-webmvc/build.gradle +++ b/dd-smoke-tests/spring-boot-2.6-webmvc/build.gradle @@ -42,6 +42,7 @@ dependencies { implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.0' implementation group: 'com.squareup.okhttp', name: 'okhttp', version: '2.2.0' implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.0.0' + implementation group: 'org.apache.httpcomponents.client5', name: 'httpclient5', version: '5.0' testImplementation project(':dd-smoke-tests') implementation project(':dd-smoke-tests:iast-util') diff --git a/dd-smoke-tests/springboot/build.gradle b/dd-smoke-tests/springboot/build.gradle index 059950fa4ba..3f0f5d34d89 100644 --- a/dd-smoke-tests/springboot/build.gradle +++ b/dd-smoke-tests/springboot/build.gradle @@ -33,6 +33,7 @@ dependencies { implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.0' implementation group: 'com.squareup.okhttp', name: 'okhttp', version: '2.2.0' implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.0.0' + implementation group: 'org.apache.httpcomponents.client5', name: 'httpclient5', version: '5.0' testImplementation project(':dd-smoke-tests') diff --git a/settings.gradle b/settings.gradle index 6e6bb98a524..3db11039509 100644 --- a/settings.gradle +++ b/settings.gradle @@ -176,7 +176,8 @@ include ':dd-java-agent:instrumentation:akka-init' include ':dd-java-agent:instrumentation:apache-httpasyncclient-4' include ':dd-java-agent:instrumentation:apache-httpclient-4' include ':dd-java-agent:instrumentation:apache-httpclient-5' -include ':dd-java-agent:instrumentation:apache-httpcore-4' +include ':dd-java-agent:instrumentation:apache-httpcore' +include ':dd-java-agent:instrumentation:apache-httpcore:apache-httpcore-5' include ':dd-java-agent:instrumentation:armeria-grpc' include ':dd-java-agent:instrumentation:armeria-jetty' include ':dd-java-agent:instrumentation:avro' From 821f857074556704c0890e8c6a7d88473b1b2d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Vidal=20Dom=C3=ADnguez?= Date: Fri, 8 Nov 2024 15:28:25 +0100 Subject: [PATCH 02/14] Remove comments and leave minimal changes --- .../BasicHttpRequestInstrumentation.java | 34 + .../apachehttpcore5/CtorAdvice.java | 14 - .../HttpRequestInstrumentation.java | 147 -- ...asicHttpRequestInstrumentationTest.groovy} | 2 +- .../AbstractIastSpringBootTest.groovy | 2144 ++++++++--------- 5 files changed, 1107 insertions(+), 1234 deletions(-) create mode 100644 dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentation.java delete mode 100644 dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/CtorAdvice.java delete mode 100644 dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/HttpRequestInstrumentation.java rename dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/{HttpRequestInstrumentationTest.groovy => BasicHttpRequestInstrumentationTest.groovy} (86%) diff --git a/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentation.java b/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentation.java new file mode 100644 index 00000000000..ae6dcbd2d0c --- /dev/null +++ b/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentation.java @@ -0,0 +1,34 @@ +package datadog.trace.instrumentation.apachehttpcore5; + +import static net.bytebuddy.matcher.ElementMatchers.any; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import net.bytebuddy.asm.Advice; + +@AutoService(InstrumenterModule.class) +public class BasicHttpRequestInstrumentation extends InstrumenterModule.Iast + implements Instrumenter.ForSingleType { + + public BasicHttpRequestInstrumentation() { + super("testApache"); + } + + @Override + public String instrumentedType() { + return "org.apache.hc.core5.http.message.BasicHttpRequest"; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice(any(), BasicHttpRequestInstrumentation.class.getName() + "$CtorAdvice"); + } + + public static class CtorAdvice { + @Advice.OnMethodExit() + public static void afterCtor() { + System.out.println("CtorAdvice.afterCtor"); + } + } +} diff --git a/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/CtorAdvice.java b/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/CtorAdvice.java deleted file mode 100644 index 12b09811407..00000000000 --- a/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/CtorAdvice.java +++ /dev/null @@ -1,14 +0,0 @@ -package datadog.trace.instrumentation.apachehttpcore5; - -import net.bytebuddy.asm.Advice; - -public class CtorAdvice { - @Advice.OnMethodExit() - public static void afterCtor() { - System.out.println("CtorAdvice.afterCtor"); - // final PropagationModule module = InstrumentationBridge.PROPAGATION; - // if (module != null) { - // module.taintObjectIfTainted(self, argument); - // } - } -} diff --git a/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/HttpRequestInstrumentation.java b/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/HttpRequestInstrumentation.java deleted file mode 100644 index 57bd6847445..00000000000 --- a/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/HttpRequestInstrumentation.java +++ /dev/null @@ -1,147 +0,0 @@ -package datadog.trace.instrumentation.apachehttpcore5; - -import static net.bytebuddy.matcher.ElementMatchers.any; - -import com.google.auto.service.AutoService; -import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.agent.tooling.InstrumenterModule; - -@AutoService(InstrumenterModule.class) -public class HttpRequestInstrumentation extends InstrumenterModule.Iast - implements Instrumenter.ForSingleType { - - public HttpRequestInstrumentation() { - super("testApache"); - } - - @Override - public String instrumentedType() { - return "org.apache.hc.core5.http.message.BasicHttpRequest"; - } - - // @Override - // public String[] getClassNamesToBePreloaded() { - // try { - // ClassLoader cl = ClassLoader.getSystemClassLoader(); - // Object loadedClass = cl.loadClass(instrumentedType()); - // System.out.println(loadedClass != null); - // } catch (Throwable e) { - // System.out.println(e); - // } - // return new String[] {instrumentedType()}; - // } - - // @Override - // public void typeAdvice(TypeTransformer transformer) { - // transformer.applyAdvice(new TaintableVisitor(instrumentedType())); - // } - - @Override - public String[] helperClassNames() { - return new String[] { - packageName + ".CtorAdvice", - }; - } - - @Override - public void methodAdvice(MethodTransformer transformer) { - // transformer.applyAdvice( - // isConstructor().and(takesArguments(String.class, String.class)), - // HttpRequestInstrumentation.class.getName() + "$CtorAdvice"); - // transformer.applyAdvice( - // isConstructor().and(takesArguments(String.class, URI.class)), - // HttpRequestInstrumentation.class.getName() + "$CtorAdvice"); - // transformer.applyAdvice( - // isConstructor().and(takesArguments(Method.class, String.class)), - // HttpRequestInstrumentation.class.getName() + "$CtorAdvice"); - // transformer.applyAdvice( - // isConstructor().and(takesArguments(Method.class, URI.class)), - // HttpRequestInstrumentation.class.getName() + "$CtorAdvice"); - - transformer.applyAdvice( - // isConstructor().and(takesArguments(2)).and(takesArgument(1, String.class)), - any(), packageName + ".CtorAdvice"); - // transformer.applyAdvice( - // isConstructor().and(takesArguments(2)).and(takesArgument(1, URI.class)), - // CtorAdvice.class.getName()); - - // transformer.applyAdvice( - // isConstructor().and(takesArguments(3)).and(takesArgument(1, - // named("org.apache.hc.core5.http.HttpHost"))), - // CtorAdviceHost.class.getName()); - - // transformer.applyAdvice( - // isConstructor().and(takesArguments(String.class, HttpHost.class, String.class)), - // HttpRequestInstrumentation.class.getName() + "$CtorAdviceHost"); - // transformer.applyAdvice( - // isConstructor().and(takesArguments(Method.class, HttpHost.class, String.class)), - // HttpRequestInstrumentation.class.getName() + "$CtorAdviceHost"); - } - - // public static class CtorAdvice { - // @Advice.OnMethodExit() - // @Propagation - // public static void afterCtor(@Advice.This final Object self, @Advice.Argument(1) Object - // argument) { - // final PropagationModule module = InstrumentationBridge.PROPAGATION; - // if (module != null) { - // module.taintObjectIfTainted(self, argument); - // } - // } - // } - - // public static class CtorAdviceHost { - // @Advice.OnMethodExit() - // @Propagation - // public static void afterCtor(@Advice.This final Object self, @Advice.Argument(2) String - // path) { - // final PropagationModule module = InstrumentationBridge.PROPAGATION; - // if (module != null) { - // module.taintObjectIfTainted(self, path); - // } - // } - // } - - // @Override - // public String hierarchyMarkerType() { - // return "org.apache.hc.core5.http.message.BasicHttpRequest"; - // } - // - // @Override - // public ElementMatcher hierarchyMatcher() { - // return extendsClass(named(hierarchyMarkerType())); - // } - // - // @Override - // public void methodAdvice(MethodTransformer transformer) { - // transformer.applyAdvice( - // named("getUri").and(isMethod()).and(takesArguments(0)), - // HttpRequestInstrumentation.class.getName() + "$GetUriAdvice"); - // transformer.applyAdvice( - // named("setUri").and(isMethod()).and(takesArguments(URI.class)), - // HttpRequestInstrumentation.class.getName() + "$SetUriAdvice"); - // } - // - // public static class SetUriAdvice { - // @Advice.OnMethodExit() - // @Propagation - // public static void methodExit(@Advice.This HttpRequest self, @Advice.Argument(0) final - // String path) { - // final PropagationModule propagationModule = InstrumentationBridge.PROPAGATION; - // if (propagationModule != null) { - // propagationModule.taintObjectIfTainted(self, path); - // } - // } - // } - // - // public static class GetUriAdvice { - // @Advice.OnMethodExit() - // @Propagation - // public static void methodExit(@Advice.This HttpRequest self, @Advice.Return URI result) { - // final PropagationModule propagationModule = InstrumentationBridge.PROPAGATION; - // if (propagationModule != null) { - // propagationModule.taintObjectIfTainted(result, self); - // } - // } - // } -} diff --git a/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/HttpRequestInstrumentationTest.groovy b/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentationTest.groovy similarity index 86% rename from dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/HttpRequestInstrumentationTest.groovy rename to dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentationTest.groovy index d427eccfe82..91a129ea13b 100644 --- a/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/HttpRequestInstrumentationTest.groovy +++ b/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentationTest.groovy @@ -3,7 +3,7 @@ package datadog.trace.instrumentation.apachehttpcore5 import datadog.trace.agent.test.AgentTestRunner import org.apache.hc.core5.http.message.BasicHttpRequest -class HttpRequestInstrumentationTest extends AgentTestRunner { +class BasicHttpRequestInstrumentationTest extends AgentTestRunner { @Override protected void configurePreAgent() { diff --git a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy index a247faaaacb..673f9ef225b 100644 --- a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy +++ b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy @@ -42,656 +42,656 @@ abstract class AbstractIastSpringBootTest extends AbstractIastServerSmokeTest { ] } - // void 'IAST subsystem starts'() { - // given: 'an initial request has succeeded' - // String url = "http://localhost:${httpPort}/greeting" - // def request = new Request.Builder().url(url).get().build() - // client.newCall(request).execute() - // - // when: 'logs are read' - // String startMsg = null - // String errorMsg = null - // checkLogPostExit { - // if (it.contains('Not starting IAST subsystem')) { - // errorMsg = it - // } - // if (it.contains('IAST is starting')) { - // startMsg = it - // } - // // Check that there's no logged exception about missing classes from Datadog. - // // We had this problem before with JDK9StackWalker. - // if (it.contains('java.lang.ClassNotFoundException: datadog/')) { - // errorMsg = it - // } - // } - // - // then: 'there are no errors in the log and IAST has started' - // errorMsg == null - // startMsg != null - // !logHasErrors - // } - // - // void 'default home page without errors'() { - // setup: - // String url = "http://localhost:${httpPort}/greeting" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // def response = client.newCall(request).execute() - // - // then: - // def responseBodyStr = response.body().string() - // responseBodyStr != null - // responseBodyStr.contains('Sup Dawg') - // response.body().contentType().toString().contains('text/plain') - // response.code() == 200 - // - // checkLogPostExit() - // !logHasErrors - // } - // - // void 'Multipart Request parameters'() { - // given: - // String url = "http://localhost:${httpPort}/multipart" - // - // RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) - // .addFormDataPart("theFile", "theFileName", - // RequestBody.create(MediaType.parse("text/plain"), "FILE_CONTENT")) - // .addFormDataPart("param1", "param1Value") - // .build() - // - // Request request = new Request.Builder() - // .url(url) - // .post(requestBody) - // .build() - // when: - // final retValue = client.newCall(request).execute().body().string() - // - // then: - // retValue == "fileName: theFile" - // hasTainted { tainted -> - // tainted.value == 'theFile' && - // tainted.ranges[0].source.name == 'Content-Disposition' && - // tainted.ranges[0].source.origin == 'http.request.multipart.parameter' - // } - // - // } - // - // void 'Multipart Request original file name'() { - // given: - // String url = "http://localhost:${httpPort}/multipart" - // - // RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) - // .addFormDataPart("theFile", "theFileName", - // RequestBody.create(MediaType.parse("text/plain"), "FILE_CONTENT")) - // .addFormDataPart("param1", "param1Value") - // .build() - // - // Request request = new Request.Builder() - // .url(url) - // .post(requestBody) - // .build() - // when: - // final retValue = client.newCall(request).execute().body().string() - // - // then: - // retValue == "fileName: theFile" - // hasTainted { tainted -> - // tainted.value == 'theFileName' && - // tainted.ranges[0].source.name == 'filename' && - // tainted.ranges[0].source.origin == 'http.request.multipart.parameter' - // } - // - // } - // - // - // void 'iast.enabled tag is present'() { - // setup: - // String url = "http://localhost:${httpPort}/greeting" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasMetric('_dd.iast.enabled', 1) - // } - // - // void 'vulnerabilities have stacktrace'(){ - // setup: - // final url = "http://localhost:${httpPort}/cmdi/runtime?cmd=ls" - // final request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerabilityStack() - // } - // - // void 'weak hash vulnerability is present'() { - // setup: - // String url = "http://localhost:${httpPort}/weakhash" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> - // vul.type == 'WEAK_HASH' && - // vul.evidence.value == 'MD5' - // } - // } - // - // void 'weak cipher vulnerability is present when calling key generator'() { - // setup: - // String url = "http://localhost:${httpPort}/weak_key_generator" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> - // vul.type == 'WEAK_CIPHER' && - // vul.evidence.value == 'DES' - // } - // } - // - // void 'weak cipher vulnerability is present when calling key generator with provider'() { - // setup: - // String url = "http://localhost:${httpPort}/weak_key_generator_with_provider" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> - // vul.type == 'WEAK_CIPHER' && - // vul.evidence.value == 'DES' - // } - // } - // - // - // void 'insecure cookie vulnerability is present'() { - // setup: - // String url = "http://localhost:${httpPort}/insecure_cookie" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // def response = client.newCall(request).execute() - // - // then: - // response.isSuccessful() - // response.header('Set-Cookie').contains('user-id') - // hasVulnerability { vul -> - // vul.type == 'INSECURE_COOKIE' && - // vul.evidence.value == 'user-id' - // } - // } - // - // void 'hsts header missing vulnerability is present'() { - // setup: - // String url = "http://localhost:${httpPort}/hstsmissing" - // def request = new Request.Builder().url(url).header("X-Forwarded-Proto", "https").get().build() - // - // when: - // def response = client.newCall(request).execute() - // - // then: - // response.isSuccessful() - // hasVulnerability { vul -> - // vul.type == 'HSTS_HEADER_MISSING' - // } - // } - // - // void 'X content type options missing header vulnerability is present'() { - // setup: - // String url = "http://localhost:${httpPort}/xcontenttypeoptionsmissing" - // def request = new Request.Builder().url(url).get().build() - // when: - // def response = client.newCall(request).execute() - // then: - // response.isSuccessful() - // hasVulnerability { vul -> - // vul.type == 'XCONTENTTYPE_HEADER_MISSING' - // } - // } - // - // void 'X content type options missing header vulnerability is absent'() { - // setup: - // String url = "http://localhost:${httpPort}/xcontenttypeoptionsecure" - // def request = new Request.Builder().url(url).get().build() - // when: - // def response = client.newCall(request).execute() - // then: - // response.isSuccessful() - // noVulnerability { vul -> - // vul.type == 'XCONTENTTYPE_HEADER_MISSING' - // } - // } - // - // - // void 'no HttpOnly cookie vulnerability is present'() { - // setup: - // String url = "http://localhost:${httpPort}/insecure_cookie" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // def response = client.newCall(request).execute() - // - // then: - // response.isSuccessful() - // response.header('Set-Cookie').contains('user-id') - // hasVulnerability { vul -> - // vul.type == 'NO_HTTPONLY_COOKIE' && - // vul.evidence.value == 'user-id' - // } - // } - // - // void 'no SameSite cookie vulnerability is present'() { - // setup: - // String url = "http://localhost:${httpPort}/insecure_cookie" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // def response = client.newCall(request).execute() - // - // then: - // response.isSuccessful() - // response.header('Set-Cookie').contains('user-id') - // hasVulnerability { vul -> - // vul.type == 'NO_SAMESITE_COOKIE' && - // vul.evidence.value == 'user-id' - // } - // } - // - // void 'insecure cookie vulnerability from addheader is present'() { - // setup: - // String url = "http://localhost:${httpPort}/insecure_cookie_from_header" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // def response = client.newCall(request).execute() - // - // then: - // response.isSuccessful() - // response.header('Set-Cookie').contains('user-id') - // hasVulnerability { vul -> - // vul.type == 'INSECURE_COOKIE' && - // vul.evidence.value == 'user-id' - // } - // } - // - // - // void 'weak hash vulnerability is present on boot'() { - // setup: - // String url = "http://localhost:${httpPort}/greeting" - // def request = new Request.Builder().url(url).get().build() - // - // when: 'ensure the controller is loaded' - // client.newCall(request).execute() - // - // then: 'a vulnerability pops in the logs (startup traces might not always be available)' - // hasVulnerabilityInLogs { vul -> - // vul.type == 'WEAK_HASH' && - // vul.evidence.value == 'SHA1' && - // vul.location.spanId > 0 - // } - // } - // - // void 'weak hash vulnerability is present on thread'() { - // setup: - // String url = "http://localhost:${httpPort}/async_weakhash" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> - // vul.type == 'WEAK_HASH' && - // vul.evidence.value == 'MD4' && - // vul.location.spanId > 0 - // } - // } - // - // void 'getParameter taints string'() { - // setup: - // String url = "http://localhost:${httpPort}/getparameter?param=A" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasTainted { tainted -> - // tainted.value == 'A' && - // tainted.ranges[0].source.name == 'param' && - // tainted.ranges[0].source.origin == 'http.request.parameter' - // } - // } - // - // void 'command injection is present with runtime'() { - // setup: - // final url = "http://localhost:${httpPort}/cmdi/runtime?cmd=ls" - // final request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> vul.type == 'COMMAND_INJECTION' } - // } - // - // void 'command injection is present with process builder'() { - // setup: - // final url = "http://localhost:${httpPort}/cmdi/process_builder?cmd=ls" - // final request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> vul.type == 'COMMAND_INJECTION' } - // } - // - // void 'xpath injection is present when compile expression'() { - // setup: - // final url = "http://localhost:${httpPort}/xpathi/compile?expression=%2Fbookstore%2Fbook%2Ftitle" - // final request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> vul.type == 'XPATH_INJECTION' } - // } - // - // - // void 'xpath injection is present when evaluate expression'() { - // setup: - // final url = "http://localhost:${httpPort}/xpathi/evaluate?expression=%2Fbookstore%2Fbook%2Ftitle" - // final request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> vul.type == 'XPATH_INJECTION' } - // } - // - // void 'trust boundary violation is present'() { - // setup: - // final url = "http://localhost:${httpPort}/trust_boundary_violation?paramValue=test" - // final request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> vul.type == 'TRUST_BOUNDARY_VIOLATION' } - // } - // - // void 'xss is present'() { - // setup: - // final url = "http://localhost:${httpPort}/xss/${method}?string=${param}" - // final request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> vul.type == 'XSS' && vul.location.method == method } - // - // where: - // method | param - // 'write' | 'test' - // 'write2' | 'test' - // 'write3' | 'test' - // 'write4' | 'test' - // 'print' | 'test' - // 'print2' | 'test' - // 'println' | 'test' - // 'println2' | 'test' - // 'printf' | 'Formatted%20like%3A%20%251%24s%20and%20%252%24s.' - // 'printf2' | 'test' - // 'printf3' | 'Formatted%20like%3A%20%251%24s%20and%20%252%24s.' - // 'printf4' | 'test' - // 'format' | 'Formatted%20like%3A%20%251%24s%20and%20%252%24s.' - // 'format2' | 'test' - // 'format3' | 'Formatted%20like%3A%20%251%24s%20and%20%252%24s.' - // 'format4' | 'test' - // 'responseBody' | 'test' - // } - // - // void 'trust boundary violation with cookie propagation'() { - // setup: - // final url = "http://localhost:${httpPort}/trust_boundary_violation_for_cookie" - // final request = new Request.Builder().url(url).get().addHeader("Cookie", "https%3A%2F%2Fuser-id2=https%3A%2F%2Fkkk").build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> vul.type == 'TRUST_BOUNDARY_VIOLATION' } - // hasTainted { tainted -> - // tainted.value == 'https%3A%2F%2Fuser-id2' && - // tainted.ranges[0].source.origin == 'http.request.cookie.name' - // } - // hasTainted { tainted -> - // tainted.value == 'https://kkk' && - // tainted.ranges[0].source.origin == 'http.request.cookie.value' - // } - // } - // - // - // void 'path traversal is present with file'() { - // setup: - // final url = "http://localhost:${httpPort}/path_traversal/file?path=test" - // final request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> vul.type == 'PATH_TRAVERSAL' } - // } - // - // void 'path traversal is present with paths'() { - // setup: - // final url = "http://localhost:${httpPort}/path_traversal/paths?path=test" - // final request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> vul.type == 'PATH_TRAVERSAL' } - // } - // - // void 'path traversal is present with path'() { - // setup: - // final url = "http://localhost:${httpPort}/path_traversal/path?path=test" - // final request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> vul.type == 'PATH_TRAVERSAL' } - // } - // - // void 'parameter binding taints bean strings'() { - // setup: - // String url = "http://localhost:${httpPort}/param_binding/test?name=parameter&value=binding" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasTainted { tainted -> - // tainted.value == 'binding' && - // tainted.ranges[0].source.name == 'value' && - // tainted.ranges[0].source.origin == 'http.request.parameter' - // } - // } - // - // void 'getRequestURL taints its output'() { - // setup: - // String url = "http://localhost:${httpPort}/getrequesturl" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasTainted { tainted -> - // tainted.value == url && - // tainted.ranges[0].source.origin == 'http.request.uri' - // } - // } - // - // void 'request header taint string'() { - // setup: - // String url = "http://localhost:${httpPort}/request_header/test" - // def request = new Request.Builder().url(url).header("test-header", "test").get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasTainted { tainted -> - // tainted.value == 'test' && - // tainted.ranges[0].source.name == 'test-header' && - // tainted.ranges[0].source.origin == 'http.request.header' - // } - // } - // - // void 'path param taint string'() { - // setup: - // String url = "http://localhost:${httpPort}/path_param?param=test" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasTainted { tainted -> - // tainted.value == 'test' && - // tainted.ranges[0].source.name == 'param' && - // tainted.ranges[0].source.origin == 'http.request.parameter' - // } - // } - // - // void 'request body taint json'() { - // setup: - // String url = "http://localhost:${httpPort}/request_body/test" - // def request = new Request.Builder().url(url).post(RequestBody.create(JSON, '{"name": "nameTest", "value" : "valueTest"}')).build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasTainted { tainted -> - // tainted.value == 'nameTest' && - // tainted.ranges[0].source.origin == 'http.request.body' - // } - // } - // - // void 'request query string'() { - // given: - // final url = "http://localhost:${httpPort}/query_string?key=value" - // final request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasTainted { tainted -> - // tainted.value == 'key=value' && - // tainted.ranges[0].source.origin == 'http.request.query' - // } - // } - // - // void 'request cookie propagation'() { - // given: - // final url = "http://localhost:${httpPort}/cookie" - // final request = new Request.Builder().url(url).header('Cookie', 'name=value').get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasTainted { tainted -> - // tainted.value == 'name' && - // tainted.ranges[0].source.origin == 'http.request.cookie.name' - // } - // hasTainted { tainted -> - // tainted.value == 'value' && - // tainted.ranges[0].source.name == 'name' && - // tainted.ranges[0].source.origin == 'http.request.cookie.value' - // } - // } - // - // void 'tainting of path variables — simple variant'() { - // given: - // String url = "http://localhost:${httpPort}/simple/foobar" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasTainted { - // it.value == 'foobar' && - // it.ranges[0].source.origin == 'http.request.path.parameter' && - // it.ranges[0].source.name == 'var1' - // } - // } - // - // @SuppressWarnings('CyclomaticComplexity') - // void 'tainting of path variables — RequestMappingInfoHandlerMapping variant'() { - // given: - // String url = "http://localhost:${httpPort}/matrix/value1;xxx=aaa,bbb;yyy=ccc/value2;zzz=ddd" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasTainted { tainted -> - // final firstRange = tainted.ranges[0] - // tainted.value == 'value1' && - // firstRange?.source?.origin == 'http.request.path.parameter' && - // firstRange?.source?.name == 'var1' - // } - // ['xxx', 'aaa', 'bbb', 'yyy', 'ccc'].each { - // hasTainted { tainted -> - // final firstRange = tainted.ranges[0] - // tainted.value == it && - // firstRange?.source?.origin == 'http.request.matrix.parameter' && - // firstRange?.source?.name == 'var1' - // } - // } - // hasTainted { tainted -> - // final firstRange = tainted.ranges[0] - // tainted.value == 'value2' && - // firstRange?.source?.origin == 'http.request.path.parameter' && - // firstRange?.source?.name == 'var2' - // } - // ['zzz', 'ddd'].each { - // hasTainted { tainted -> - // final firstRange = tainted.ranges[0] - // tainted.value = it && - // firstRange?.source?.origin == 'http.request.matrix.parameter' && - // firstRange?.source?.name == 'var2' - // } - // } - // } + void 'IAST subsystem starts'() { + given: 'an initial request has succeeded' + String url = "http://localhost:${httpPort}/greeting" + def request = new Request.Builder().url(url).get().build() + client.newCall(request).execute() + + when: 'logs are read' + String startMsg = null + String errorMsg = null + checkLogPostExit { + if (it.contains('Not starting IAST subsystem')) { + errorMsg = it + } + if (it.contains('IAST is starting')) { + startMsg = it + } + // Check that there's no logged exception about missing classes from Datadog. + // We had this problem before with JDK9StackWalker. + if (it.contains('java.lang.ClassNotFoundException: datadog/')) { + errorMsg = it + } + } + + then: 'there are no errors in the log and IAST has started' + errorMsg == null + startMsg != null + !logHasErrors + } + + void 'default home page without errors'() { + setup: + String url = "http://localhost:${httpPort}/greeting" + def request = new Request.Builder().url(url).get().build() + + when: + def response = client.newCall(request).execute() + + then: + def responseBodyStr = response.body().string() + responseBodyStr != null + responseBodyStr.contains('Sup Dawg') + response.body().contentType().toString().contains('text/plain') + response.code() == 200 + + checkLogPostExit() + !logHasErrors + } + + void 'Multipart Request parameters'() { + given: + String url = "http://localhost:${httpPort}/multipart" + + RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) + .addFormDataPart("theFile", "theFileName", + RequestBody.create(MediaType.parse("text/plain"), "FILE_CONTENT")) + .addFormDataPart("param1", "param1Value") + .build() + + Request request = new Request.Builder() + .url(url) + .post(requestBody) + .build() + when: + final retValue = client.newCall(request).execute().body().string() + + then: + retValue == "fileName: theFile" + hasTainted { tainted -> + tainted.value == 'theFile' && + tainted.ranges[0].source.name == 'Content-Disposition' && + tainted.ranges[0].source.origin == 'http.request.multipart.parameter' + } + + } + + void 'Multipart Request original file name'() { + given: + String url = "http://localhost:${httpPort}/multipart" + + RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) + .addFormDataPart("theFile", "theFileName", + RequestBody.create(MediaType.parse("text/plain"), "FILE_CONTENT")) + .addFormDataPart("param1", "param1Value") + .build() + + Request request = new Request.Builder() + .url(url) + .post(requestBody) + .build() + when: + final retValue = client.newCall(request).execute().body().string() + + then: + retValue == "fileName: theFile" + hasTainted { tainted -> + tainted.value == 'theFileName' && + tainted.ranges[0].source.name == 'filename' && + tainted.ranges[0].source.origin == 'http.request.multipart.parameter' + } + + } + + + void 'iast.enabled tag is present'() { + setup: + String url = "http://localhost:${httpPort}/greeting" + def request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasMetric('_dd.iast.enabled', 1) + } + + void 'vulnerabilities have stacktrace'(){ + setup: + final url = "http://localhost:${httpPort}/cmdi/runtime?cmd=ls" + final request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerabilityStack() + } + + void 'weak hash vulnerability is present'() { + setup: + String url = "http://localhost:${httpPort}/weakhash" + def request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> + vul.type == 'WEAK_HASH' && + vul.evidence.value == 'MD5' + } + } + + void 'weak cipher vulnerability is present when calling key generator'() { + setup: + String url = "http://localhost:${httpPort}/weak_key_generator" + def request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> + vul.type == 'WEAK_CIPHER' && + vul.evidence.value == 'DES' + } + } + + void 'weak cipher vulnerability is present when calling key generator with provider'() { + setup: + String url = "http://localhost:${httpPort}/weak_key_generator_with_provider" + def request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> + vul.type == 'WEAK_CIPHER' && + vul.evidence.value == 'DES' + } + } + + + void 'insecure cookie vulnerability is present'() { + setup: + String url = "http://localhost:${httpPort}/insecure_cookie" + def request = new Request.Builder().url(url).get().build() + + when: + def response = client.newCall(request).execute() + + then: + response.isSuccessful() + response.header('Set-Cookie').contains('user-id') + hasVulnerability { vul -> + vul.type == 'INSECURE_COOKIE' && + vul.evidence.value == 'user-id' + } + } + + void 'hsts header missing vulnerability is present'() { + setup: + String url = "http://localhost:${httpPort}/hstsmissing" + def request = new Request.Builder().url(url).header("X-Forwarded-Proto", "https").get().build() + + when: + def response = client.newCall(request).execute() + + then: + response.isSuccessful() + hasVulnerability { vul -> + vul.type == 'HSTS_HEADER_MISSING' + } + } + + void 'X content type options missing header vulnerability is present'() { + setup: + String url = "http://localhost:${httpPort}/xcontenttypeoptionsmissing" + def request = new Request.Builder().url(url).get().build() + when: + def response = client.newCall(request).execute() + then: + response.isSuccessful() + hasVulnerability { vul -> + vul.type == 'XCONTENTTYPE_HEADER_MISSING' + } + } + + void 'X content type options missing header vulnerability is absent'() { + setup: + String url = "http://localhost:${httpPort}/xcontenttypeoptionsecure" + def request = new Request.Builder().url(url).get().build() + when: + def response = client.newCall(request).execute() + then: + response.isSuccessful() + noVulnerability { vul -> + vul.type == 'XCONTENTTYPE_HEADER_MISSING' + } + } + + + void 'no HttpOnly cookie vulnerability is present'() { + setup: + String url = "http://localhost:${httpPort}/insecure_cookie" + def request = new Request.Builder().url(url).get().build() + + when: + def response = client.newCall(request).execute() + + then: + response.isSuccessful() + response.header('Set-Cookie').contains('user-id') + hasVulnerability { vul -> + vul.type == 'NO_HTTPONLY_COOKIE' && + vul.evidence.value == 'user-id' + } + } + + void 'no SameSite cookie vulnerability is present'() { + setup: + String url = "http://localhost:${httpPort}/insecure_cookie" + def request = new Request.Builder().url(url).get().build() + + when: + def response = client.newCall(request).execute() + + then: + response.isSuccessful() + response.header('Set-Cookie').contains('user-id') + hasVulnerability { vul -> + vul.type == 'NO_SAMESITE_COOKIE' && + vul.evidence.value == 'user-id' + } + } + + void 'insecure cookie vulnerability from addheader is present'() { + setup: + String url = "http://localhost:${httpPort}/insecure_cookie_from_header" + def request = new Request.Builder().url(url).get().build() + + when: + def response = client.newCall(request).execute() + + then: + response.isSuccessful() + response.header('Set-Cookie').contains('user-id') + hasVulnerability { vul -> + vul.type == 'INSECURE_COOKIE' && + vul.evidence.value == 'user-id' + } + } + + + void 'weak hash vulnerability is present on boot'() { + setup: + String url = "http://localhost:${httpPort}/greeting" + def request = new Request.Builder().url(url).get().build() + + when: 'ensure the controller is loaded' + client.newCall(request).execute() + + then: 'a vulnerability pops in the logs (startup traces might not always be available)' + hasVulnerabilityInLogs { vul -> + vul.type == 'WEAK_HASH' && + vul.evidence.value == 'SHA1' && + vul.location.spanId > 0 + } + } + + void 'weak hash vulnerability is present on thread'() { + setup: + String url = "http://localhost:${httpPort}/async_weakhash" + def request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> + vul.type == 'WEAK_HASH' && + vul.evidence.value == 'MD4' && + vul.location.spanId > 0 + } + } + + void 'getParameter taints string'() { + setup: + String url = "http://localhost:${httpPort}/getparameter?param=A" + def request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasTainted { tainted -> + tainted.value == 'A' && + tainted.ranges[0].source.name == 'param' && + tainted.ranges[0].source.origin == 'http.request.parameter' + } + } + + void 'command injection is present with runtime'() { + setup: + final url = "http://localhost:${httpPort}/cmdi/runtime?cmd=ls" + final request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> vul.type == 'COMMAND_INJECTION' } + } + + void 'command injection is present with process builder'() { + setup: + final url = "http://localhost:${httpPort}/cmdi/process_builder?cmd=ls" + final request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> vul.type == 'COMMAND_INJECTION' } + } + + void 'xpath injection is present when compile expression'() { + setup: + final url = "http://localhost:${httpPort}/xpathi/compile?expression=%2Fbookstore%2Fbook%2Ftitle" + final request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> vul.type == 'XPATH_INJECTION' } + } + + + void 'xpath injection is present when evaluate expression'() { + setup: + final url = "http://localhost:${httpPort}/xpathi/evaluate?expression=%2Fbookstore%2Fbook%2Ftitle" + final request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> vul.type == 'XPATH_INJECTION' } + } + + void 'trust boundary violation is present'() { + setup: + final url = "http://localhost:${httpPort}/trust_boundary_violation?paramValue=test" + final request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> vul.type == 'TRUST_BOUNDARY_VIOLATION' } + } + + void 'xss is present'() { + setup: + final url = "http://localhost:${httpPort}/xss/${method}?string=${param}" + final request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> vul.type == 'XSS' && vul.location.method == method } + + where: + method | param + 'write' | 'test' + 'write2' | 'test' + 'write3' | 'test' + 'write4' | 'test' + 'print' | 'test' + 'print2' | 'test' + 'println' | 'test' + 'println2' | 'test' + 'printf' | 'Formatted%20like%3A%20%251%24s%20and%20%252%24s.' + 'printf2' | 'test' + 'printf3' | 'Formatted%20like%3A%20%251%24s%20and%20%252%24s.' + 'printf4' | 'test' + 'format' | 'Formatted%20like%3A%20%251%24s%20and%20%252%24s.' + 'format2' | 'test' + 'format3' | 'Formatted%20like%3A%20%251%24s%20and%20%252%24s.' + 'format4' | 'test' + 'responseBody' | 'test' + } + + void 'trust boundary violation with cookie propagation'() { + setup: + final url = "http://localhost:${httpPort}/trust_boundary_violation_for_cookie" + final request = new Request.Builder().url(url).get().addHeader("Cookie", "https%3A%2F%2Fuser-id2=https%3A%2F%2Fkkk").build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> vul.type == 'TRUST_BOUNDARY_VIOLATION' } + hasTainted { tainted -> + tainted.value == 'https%3A%2F%2Fuser-id2' && + tainted.ranges[0].source.origin == 'http.request.cookie.name' + } + hasTainted { tainted -> + tainted.value == 'https://kkk' && + tainted.ranges[0].source.origin == 'http.request.cookie.value' + } + } + + + void 'path traversal is present with file'() { + setup: + final url = "http://localhost:${httpPort}/path_traversal/file?path=test" + final request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> vul.type == 'PATH_TRAVERSAL' } + } + + void 'path traversal is present with paths'() { + setup: + final url = "http://localhost:${httpPort}/path_traversal/paths?path=test" + final request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> vul.type == 'PATH_TRAVERSAL' } + } + + void 'path traversal is present with path'() { + setup: + final url = "http://localhost:${httpPort}/path_traversal/path?path=test" + final request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> vul.type == 'PATH_TRAVERSAL' } + } + + void 'parameter binding taints bean strings'() { + setup: + String url = "http://localhost:${httpPort}/param_binding/test?name=parameter&value=binding" + def request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasTainted { tainted -> + tainted.value == 'binding' && + tainted.ranges[0].source.name == 'value' && + tainted.ranges[0].source.origin == 'http.request.parameter' + } + } + + void 'getRequestURL taints its output'() { + setup: + String url = "http://localhost:${httpPort}/getrequesturl" + def request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasTainted { tainted -> + tainted.value == url && + tainted.ranges[0].source.origin == 'http.request.uri' + } + } + + void 'request header taint string'() { + setup: + String url = "http://localhost:${httpPort}/request_header/test" + def request = new Request.Builder().url(url).header("test-header", "test").get().build() + + when: + client.newCall(request).execute() + + then: + hasTainted { tainted -> + tainted.value == 'test' && + tainted.ranges[0].source.name == 'test-header' && + tainted.ranges[0].source.origin == 'http.request.header' + } + } + + void 'path param taint string'() { + setup: + String url = "http://localhost:${httpPort}/path_param?param=test" + def request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasTainted { tainted -> + tainted.value == 'test' && + tainted.ranges[0].source.name == 'param' && + tainted.ranges[0].source.origin == 'http.request.parameter' + } + } + + void 'request body taint json'() { + setup: + String url = "http://localhost:${httpPort}/request_body/test" + def request = new Request.Builder().url(url).post(RequestBody.create(JSON, '{"name": "nameTest", "value" : "valueTest"}')).build() + + when: + client.newCall(request).execute() + + then: + hasTainted { tainted -> + tainted.value == 'nameTest' && + tainted.ranges[0].source.origin == 'http.request.body' + } + } + + void 'request query string'() { + given: + final url = "http://localhost:${httpPort}/query_string?key=value" + final request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasTainted { tainted -> + tainted.value == 'key=value' && + tainted.ranges[0].source.origin == 'http.request.query' + } + } + + void 'request cookie propagation'() { + given: + final url = "http://localhost:${httpPort}/cookie" + final request = new Request.Builder().url(url).header('Cookie', 'name=value').get().build() + + when: + client.newCall(request).execute() + + then: + hasTainted { tainted -> + tainted.value == 'name' && + tainted.ranges[0].source.origin == 'http.request.cookie.name' + } + hasTainted { tainted -> + tainted.value == 'value' && + tainted.ranges[0].source.name == 'name' && + tainted.ranges[0].source.origin == 'http.request.cookie.value' + } + } + + void 'tainting of path variables — simple variant'() { + given: + String url = "http://localhost:${httpPort}/simple/foobar" + def request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasTainted { + it.value == 'foobar' && + it.ranges[0].source.origin == 'http.request.path.parameter' && + it.ranges[0].source.name == 'var1' + } + } + + @SuppressWarnings('CyclomaticComplexity') + void 'tainting of path variables — RequestMappingInfoHandlerMapping variant'() { + given: + String url = "http://localhost:${httpPort}/matrix/value1;xxx=aaa,bbb;yyy=ccc/value2;zzz=ddd" + def request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasTainted { tainted -> + final firstRange = tainted.ranges[0] + tainted.value == 'value1' && + firstRange?.source?.origin == 'http.request.path.parameter' && + firstRange?.source?.name == 'var1' + } + ['xxx', 'aaa', 'bbb', 'yyy', 'ccc'].each { + hasTainted { tainted -> + final firstRange = tainted.ranges[0] + tainted.value == it && + firstRange?.source?.origin == 'http.request.matrix.parameter' && + firstRange?.source?.name == 'var1' + } + } + hasTainted { tainted -> + final firstRange = tainted.ranges[0] + tainted.value == 'value2' && + firstRange?.source?.origin == 'http.request.path.parameter' && + firstRange?.source?.name == 'var2' + } + ['zzz', 'ddd'].each { + hasTainted { tainted -> + final firstRange = tainted.ranges[0] + tainted.value = it && + firstRange?.source?.origin == 'http.request.matrix.parameter' && + firstRange?.source?.name == 'var2' + } + } + } void 'ssrf is present'() { setup: @@ -765,428 +765,428 @@ abstract class AbstractIastSpringBootTest extends AbstractIastServerSmokeTest { "okHttp3" | "url" | "https://dd.datad0g.com/" | "okHttp3" | false } - // void 'test iast metrics stored in spans'() { - // setup: - // final url = "http://localhost:${httpPort}/cmdi/runtime?cmd=ls" - // final request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasMetric('_dd.iast.telemetry.executed.sink.command_injection', 1) - // } - // - // void 'weak randomness is present in #evidence'() { - // setup: - // final url = "http://localhost:${httpPort}/weak_randomness?mode=${evidence}" - // final request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> vul.type == 'WEAK_RANDOMNESS' && vul.evidence.value == evidence } - // - // where: - // evidence | _ - // 'java.util.Random' | _ - // 'java.util.concurrent.ThreadLocalRandom' | _ - // 'java.lang.Math' | _ - // } - // - // def "unvalidated redirect from addheader is present"() { - // setup: - // String url = "http://localhost:${httpPort}/unvalidated_redirect_from_header?param=redirected" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // def response = client.newCall(request).execute() - // - // then: - // response.isRedirect() - // response.header("Location").contains("redirected") - // hasVulnerability { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromHeader' } - // } - // - // def "unvalidated redirect from sendRedirect is present"() { - // setup: - // String url = "http://localhost:${httpPort}/unvalidated_redirect_from_send_redirect?param=redirected" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // def response = client.newCall(request).execute() - // - // then: - // response.isRedirect() - // // TODO: span deserialization fails when checking the vulnerability - // // === Failure during message v0.4 decoding === - // //org.msgpack.core.MessageTypeException: Expected String, but got Nil (c0) - // hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromSendRedirect' } - // } - // - // def "unvalidated redirect from forward is present"() { - // setup: - // String url = "http://localhost:${httpPort}/unvalidated_redirect_from_forward?param=redirected" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromForward' } - // } - // - // def "unvalidated redirect from RedirectView is present"() { - // setup: - // String url = "http://localhost:${httpPort}/unvalidated_redirect_from_redirect_view?param=redirected" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // def response = client.newCall(request).execute() - // - // then: - // response.isRedirect() - // hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromRedirectView' } - // } - // - // def "unvalidated redirect from ModelAndView is present"() { - // setup: - // String url = "http://localhost:${httpPort}/unvalidated_redirect_from_model_and_view?param=redirected" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // def response = client.newCall(request).execute() - // - // then: - // response.isRedirect() - // hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromModelAndView' } - // } - // - // - // def "unvalidated redirect forward from ModelAndView is present"() { - // setup: - // String url = "http://localhost:${httpPort}/unvalidated_redirect_forward_from_model_and_view?param=redirected" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectForwardFromModelAndView' } - // } - // - // - // def "unvalidated redirect from string"() { - // setup: - // String url = "http://localhost:${httpPort}/unvalidated_redirect_from_string?param=redirected" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // def response = client.newCall(request).execute() - // - // then: - // response.isRedirect() - // hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromString' } - // } - // - // def "unvalidated redirect forward from string"() { - // setup: - // String url = "http://localhost:${httpPort}/unvalidated_redirect_forward_from_string?param=redirected" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectForwardFromString' } - // } - // - // def "get View from tainted string"() { - // setup: - // String url = "http://localhost:${httpPort}/get_view_from_tainted_string?param=redirected" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'getViewfromTaintedString' } - // } - // - // void 'getRequestURI taints its output'() { - // setup: - // final url = "http://localhost:${httpPort}/getrequesturi" - // final request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasTainted { tainted -> - // tainted.value == '/getrequesturi' && - // tainted.ranges[0].source.origin == 'http.request.path' - // } - // } - // - // void 'header injection'() { - // setup: - // final url = "http://localhost:${httpPort}/header_injection?param=test" - // final request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> vul.type == 'HEADER_INJECTION' } - // } - // - // void 'header injection exclusion'() { - // setup: - // final url = "http://localhost:${httpPort}/header_injection_exclusion?param=testExclusion" - // final request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // noVulnerability { vul -> vul.type == 'HEADER_INJECTION' } - // } - // - // void 'header injection redaction'() { - // setup: - // String bearer = URLEncoder.encode("Authorization: bearer 12345644", "UTF-8") - // final url = "http://localhost:${httpPort}/header_injection_redaction?param=" + bearer - // final request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> vul.type == 'HEADER_INJECTION' && vul.evidence.valueParts[1].redacted == true } - // } - // - // void 'Insecure Auth Protocol vulnerability is present'() { - // setup: - // String url = "http://localhost:${httpPort}/insecureAuthProtocol" - // def request = new Request.Builder().url(url).header("Authorization", "Basic YWxhZGRpbjpvcGVuc2VzYW1l").get().build() - // when: - // def response = client.newCall(request).execute() - // then: - // response.isSuccessful() - // hasVulnerability { vul -> - // vul.type == 'INSECURE_AUTH_PROTOCOL' - // } - // } - // - // void "Check reflection injection forName"() { - // setup: - // String url = "http://localhost:${httpPort}/reflection_injection/class?param=java.lang.String" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> - // vul.type == 'REFLECTION_INJECTION' - // && vul.location.method == 'reflectionInjectionClass' - // && vul.evidence.valueParts[0].value == "java.lang.String" - // } - // } - // - // void "Check reflection injection getMethod"() { - // setup: - // String url = "http://localhost:${httpPort}/reflection_injection/method?param=isEmpty" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> - // vul.type == 'REFLECTION_INJECTION' - // && vul.location.method == 'reflectionInjectionMethod' - // && vul.evidence.valueParts[0].value == "java.lang.String#" - // && vul.evidence.valueParts[1].value == "isEmpty" - // && vul.evidence.valueParts[2].value == "()" - // } - // } - // - // void "Check reflection injection getField"() { - // setup: - // String url = "http://localhost:${httpPort}/reflection_injection/field?param=hash" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> - // vul.type == 'REFLECTION_INJECTION' - // && vul.location.method == 'reflectionInjectionField' - // && vul.evidence.valueParts[0].value == "java.lang.String#" - // && vul.evidence.valueParts[1].value == "hash" - // } - // } - // - // void "Check reflection injection lookup"() { - // setup: - // String url = "http://localhost:${httpPort}/reflection_injection/lookup?param=hash" - // def request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> - // vul.type == 'REFLECTION_INJECTION' - // && vul.location.method == 'reflectionInjectionLookup' - // && vul.evidence.valueParts[0].value == "java.lang.String#" - // && vul.evidence.valueParts[1].value == "hash" - // } - // } - // - // void 'find session rewriting'() { - // given: - // String url = "http://localhost:${httpPort}/greeting" - // - // when: - // Response response = client.newCall(new Request.Builder().url(url).get().build()).execute() - // - // then: - // response.successful - // hasVulnerabilityInLogs { vul -> - // vul.type == 'SESSION_REWRITING' - // } - // } - // - // void 'untrusted deserialization for an input stream'() { - // setup: - // final url = "http://localhost:${httpPort}/untrusted_deserialization" - // ByteArrayOutputStream baos = new ByteArrayOutputStream() - // ObjectOutputStream oos = new ObjectOutputStream(baos) - // oos.writeObject("This is a test object.") - // RequestBody requestBody = RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray()) - // final request = new Request.Builder().url(url).post(requestBody).build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } - // } - // - // void 'untrusted deserialization for a servlet file upload which calls parseRequest'() { - // setup: - // final url = "http://localhost:${httpPort}/untrusted_deserialization/parse_request" - // ByteArrayOutputStream baos = new ByteArrayOutputStream() - // ObjectOutputStream oos = new ObjectOutputStream(baos) - // oos.writeObject("This is a test object.") - // RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) - // .addFormDataPart("file", "test.txt", - // RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray())) - // .build() - // final request = new Request.Builder().url(url).post(requestBody).build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } - // } - // - // void 'untrusted deserialization for a servlet file upload which calls parseParameterMap'() { - // setup: - // final url = "http://localhost:${httpPort}/untrusted_deserialization/parse_parameter_map" - // ByteArrayOutputStream baos = new ByteArrayOutputStream() - // ObjectOutputStream oos = new ObjectOutputStream(baos) - // oos.writeObject("This is a test object.") - // RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) - // .addFormDataPart("file", "test.txt", - // RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray())) - // .build() - // final request = new Request.Builder().url(url).post(requestBody).build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } - // } - // - // void 'untrusted deserialization for a servlet file upload which calls getItemIterator'() { - // setup: - // final url = "http://localhost:${httpPort}/untrusted_deserialization/get_item_iterator" - // ByteArrayOutputStream baos = new ByteArrayOutputStream() - // ObjectOutputStream oos = new ObjectOutputStream(baos) - // oos.writeObject("This is a test object.") - // RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) - // .addFormDataPart("file", "test.txt", - // RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray())) - // .build() - // final request = new Request.Builder().url(url) - // .post(requestBody).build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } - // } - // - // void 'untrusted deserialization for a multipart file'() { - // setup: - // final url = "http://localhost:${httpPort}/untrusted_deserialization/multipart" - // ByteArrayOutputStream baos = new ByteArrayOutputStream() - // ObjectOutputStream oos = new ObjectOutputStream(baos) - // oos.writeObject("This is a test object.") - // RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) - // .addFormDataPart("file", "test.txt", - // RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray())) - // .build() - // final request = new Request.Builder().url(url) - // .post(requestBody).build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } - // } - // - // void 'untrusted deserialization for a part'() { - // setup: - // final url = "http://localhost:${httpPort}/untrusted_deserialization/part" - // ByteArrayOutputStream baos = new ByteArrayOutputStream() - // ObjectOutputStream oos = new ObjectOutputStream(baos) - // oos.writeObject("This is a test object.") - // RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) - // .addFormDataPart("file", "test.txt", - // RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray())) - // .build() - // final request = new Request.Builder().url(url) - // .post(requestBody).build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } - // } - // - // void 'untrusted deserialization for snakeyaml with a string'() { - // setup: - // final String yaml = "test" - // final url = "http://localhost:${httpPort}/untrusted_deserialization/snakeyaml?yaml=${yaml}" - // final request = new Request.Builder().url(url).get().build() - // - // when: - // client.newCall(request).execute() - // - // then: - // hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } - // } + void 'test iast metrics stored in spans'() { + setup: + final url = "http://localhost:${httpPort}/cmdi/runtime?cmd=ls" + final request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasMetric('_dd.iast.telemetry.executed.sink.command_injection', 1) + } + + void 'weak randomness is present in #evidence'() { + setup: + final url = "http://localhost:${httpPort}/weak_randomness?mode=${evidence}" + final request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> vul.type == 'WEAK_RANDOMNESS' && vul.evidence.value == evidence } + + where: + evidence | _ + 'java.util.Random' | _ + 'java.util.concurrent.ThreadLocalRandom' | _ + 'java.lang.Math' | _ + } + + def "unvalidated redirect from addheader is present"() { + setup: + String url = "http://localhost:${httpPort}/unvalidated_redirect_from_header?param=redirected" + def request = new Request.Builder().url(url).get().build() + + when: + def response = client.newCall(request).execute() + + then: + response.isRedirect() + response.header("Location").contains("redirected") + hasVulnerability { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromHeader' } + } + + def "unvalidated redirect from sendRedirect is present"() { + setup: + String url = "http://localhost:${httpPort}/unvalidated_redirect_from_send_redirect?param=redirected" + def request = new Request.Builder().url(url).get().build() + + when: + def response = client.newCall(request).execute() + + then: + response.isRedirect() + // TODO: span deserialization fails when checking the vulnerability + // === Failure during message v0.4 decoding === + //org.msgpack.core.MessageTypeException: Expected String, but got Nil (c0) + hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromSendRedirect' } + } + + def "unvalidated redirect from forward is present"() { + setup: + String url = "http://localhost:${httpPort}/unvalidated_redirect_from_forward?param=redirected" + def request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromForward' } + } + + def "unvalidated redirect from RedirectView is present"() { + setup: + String url = "http://localhost:${httpPort}/unvalidated_redirect_from_redirect_view?param=redirected" + def request = new Request.Builder().url(url).get().build() + + when: + def response = client.newCall(request).execute() + + then: + response.isRedirect() + hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromRedirectView' } + } + + def "unvalidated redirect from ModelAndView is present"() { + setup: + String url = "http://localhost:${httpPort}/unvalidated_redirect_from_model_and_view?param=redirected" + def request = new Request.Builder().url(url).get().build() + + when: + def response = client.newCall(request).execute() + + then: + response.isRedirect() + hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromModelAndView' } + } + + + def "unvalidated redirect forward from ModelAndView is present"() { + setup: + String url = "http://localhost:${httpPort}/unvalidated_redirect_forward_from_model_and_view?param=redirected" + def request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectForwardFromModelAndView' } + } + + + def "unvalidated redirect from string"() { + setup: + String url = "http://localhost:${httpPort}/unvalidated_redirect_from_string?param=redirected" + def request = new Request.Builder().url(url).get().build() + + when: + def response = client.newCall(request).execute() + + then: + response.isRedirect() + hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectFromString' } + } + + def "unvalidated redirect forward from string"() { + setup: + String url = "http://localhost:${httpPort}/unvalidated_redirect_forward_from_string?param=redirected" + def request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'unvalidatedRedirectForwardFromString' } + } + + def "get View from tainted string"() { + setup: + String url = "http://localhost:${httpPort}/get_view_from_tainted_string?param=redirected" + def request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerabilityInLogs { vul -> vul.type == 'UNVALIDATED_REDIRECT' && vul.location.method == 'getViewfromTaintedString' } + } + + void 'getRequestURI taints its output'() { + setup: + final url = "http://localhost:${httpPort}/getrequesturi" + final request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasTainted { tainted -> + tainted.value == '/getrequesturi' && + tainted.ranges[0].source.origin == 'http.request.path' + } + } + + void 'header injection'() { + setup: + final url = "http://localhost:${httpPort}/header_injection?param=test" + final request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> vul.type == 'HEADER_INJECTION' } + } + + void 'header injection exclusion'() { + setup: + final url = "http://localhost:${httpPort}/header_injection_exclusion?param=testExclusion" + final request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + noVulnerability { vul -> vul.type == 'HEADER_INJECTION' } + } + + void 'header injection redaction'() { + setup: + String bearer = URLEncoder.encode("Authorization: bearer 12345644", "UTF-8") + final url = "http://localhost:${httpPort}/header_injection_redaction?param=" + bearer + final request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> vul.type == 'HEADER_INJECTION' && vul.evidence.valueParts[1].redacted == true } + } + + void 'Insecure Auth Protocol vulnerability is present'() { + setup: + String url = "http://localhost:${httpPort}/insecureAuthProtocol" + def request = new Request.Builder().url(url).header("Authorization", "Basic YWxhZGRpbjpvcGVuc2VzYW1l").get().build() + when: + def response = client.newCall(request).execute() + then: + response.isSuccessful() + hasVulnerability { vul -> + vul.type == 'INSECURE_AUTH_PROTOCOL' + } + } + + void "Check reflection injection forName"() { + setup: + String url = "http://localhost:${httpPort}/reflection_injection/class?param=java.lang.String" + def request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> + vul.type == 'REFLECTION_INJECTION' + && vul.location.method == 'reflectionInjectionClass' + && vul.evidence.valueParts[0].value == "java.lang.String" + } + } + + void "Check reflection injection getMethod"() { + setup: + String url = "http://localhost:${httpPort}/reflection_injection/method?param=isEmpty" + def request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> + vul.type == 'REFLECTION_INJECTION' + && vul.location.method == 'reflectionInjectionMethod' + && vul.evidence.valueParts[0].value == "java.lang.String#" + && vul.evidence.valueParts[1].value == "isEmpty" + && vul.evidence.valueParts[2].value == "()" + } + } + + void "Check reflection injection getField"() { + setup: + String url = "http://localhost:${httpPort}/reflection_injection/field?param=hash" + def request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> + vul.type == 'REFLECTION_INJECTION' + && vul.location.method == 'reflectionInjectionField' + && vul.evidence.valueParts[0].value == "java.lang.String#" + && vul.evidence.valueParts[1].value == "hash" + } + } + + void "Check reflection injection lookup"() { + setup: + String url = "http://localhost:${httpPort}/reflection_injection/lookup?param=hash" + def request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> + vul.type == 'REFLECTION_INJECTION' + && vul.location.method == 'reflectionInjectionLookup' + && vul.evidence.valueParts[0].value == "java.lang.String#" + && vul.evidence.valueParts[1].value == "hash" + } + } + + void 'find session rewriting'() { + given: + String url = "http://localhost:${httpPort}/greeting" + + when: + Response response = client.newCall(new Request.Builder().url(url).get().build()).execute() + + then: + response.successful + hasVulnerabilityInLogs { vul -> + vul.type == 'SESSION_REWRITING' + } + } + + void 'untrusted deserialization for an input stream'() { + setup: + final url = "http://localhost:${httpPort}/untrusted_deserialization" + ByteArrayOutputStream baos = new ByteArrayOutputStream() + ObjectOutputStream oos = new ObjectOutputStream(baos) + oos.writeObject("This is a test object.") + RequestBody requestBody = RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray()) + final request = new Request.Builder().url(url).post(requestBody).build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } + } + + void 'untrusted deserialization for a servlet file upload which calls parseRequest'() { + setup: + final url = "http://localhost:${httpPort}/untrusted_deserialization/parse_request" + ByteArrayOutputStream baos = new ByteArrayOutputStream() + ObjectOutputStream oos = new ObjectOutputStream(baos) + oos.writeObject("This is a test object.") + RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) + .addFormDataPart("file", "test.txt", + RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray())) + .build() + final request = new Request.Builder().url(url).post(requestBody).build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } + } + + void 'untrusted deserialization for a servlet file upload which calls parseParameterMap'() { + setup: + final url = "http://localhost:${httpPort}/untrusted_deserialization/parse_parameter_map" + ByteArrayOutputStream baos = new ByteArrayOutputStream() + ObjectOutputStream oos = new ObjectOutputStream(baos) + oos.writeObject("This is a test object.") + RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) + .addFormDataPart("file", "test.txt", + RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray())) + .build() + final request = new Request.Builder().url(url).post(requestBody).build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } + } + + void 'untrusted deserialization for a servlet file upload which calls getItemIterator'() { + setup: + final url = "http://localhost:${httpPort}/untrusted_deserialization/get_item_iterator" + ByteArrayOutputStream baos = new ByteArrayOutputStream() + ObjectOutputStream oos = new ObjectOutputStream(baos) + oos.writeObject("This is a test object.") + RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) + .addFormDataPart("file", "test.txt", + RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray())) + .build() + final request = new Request.Builder().url(url) + .post(requestBody).build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } + } + + void 'untrusted deserialization for a multipart file'() { + setup: + final url = "http://localhost:${httpPort}/untrusted_deserialization/multipart" + ByteArrayOutputStream baos = new ByteArrayOutputStream() + ObjectOutputStream oos = new ObjectOutputStream(baos) + oos.writeObject("This is a test object.") + RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) + .addFormDataPart("file", "test.txt", + RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray())) + .build() + final request = new Request.Builder().url(url) + .post(requestBody).build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } + } + + void 'untrusted deserialization for a part'() { + setup: + final url = "http://localhost:${httpPort}/untrusted_deserialization/part" + ByteArrayOutputStream baos = new ByteArrayOutputStream() + ObjectOutputStream oos = new ObjectOutputStream(baos) + oos.writeObject("This is a test object.") + RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) + .addFormDataPart("file", "test.txt", + RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray())) + .build() + final request = new Request.Builder().url(url) + .post(requestBody).build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } + } + + void 'untrusted deserialization for snakeyaml with a string'() { + setup: + final String yaml = "test" + final url = "http://localhost:${httpPort}/untrusted_deserialization/snakeyaml?yaml=${yaml}" + final request = new Request.Builder().url(url).get().build() + + when: + client.newCall(request).execute() + + then: + hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' } + } } From 453d57b54f31efa4a9568b25c746e274d608ba35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Vidal=20Dom=C3=ADnguez?= Date: Fri, 8 Nov 2024 15:30:28 +0100 Subject: [PATCH 03/14] Remove AgentInstaller code --- .../trace/agent/tooling/AgentInstaller.java | 53 ------------------- 1 file changed, 53 deletions(-) diff --git a/dd-java-agent/agent-builder/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java b/dd-java-agent/agent-builder/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java index 2afea75d8f5..6fef809fef7 100644 --- a/dd-java-agent/agent-builder/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java +++ b/dd-java-agent/agent-builder/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java @@ -20,7 +20,6 @@ import datadog.trace.bootstrap.instrumentation.java.concurrent.ExcludeFilter; import datadog.trace.util.AgentTaskScheduler; import de.thetaphi.forbiddenapis.SuppressForbidden; -import java.io.File; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.util.Collections; @@ -43,7 +42,6 @@ import net.bytebuddy.dynamic.scaffold.MethodGraph; import net.bytebuddy.matcher.LatentMatcher; import net.bytebuddy.utility.JavaModule; -import net.bytebuddy.utility.nullability.MaybeNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -155,57 +153,6 @@ public static ClassFileTransformer installBytebuddyAgent( .with(AgentStrategies.bufferStrategy()) .with(AgentStrategies.typeStrategy()) .with(new ClassLoadListener()) - .with( - new AgentBuilder.Listener.Adapter() { - @Override - public void onTransformation( - TypeDescription typeDescription, - ClassLoader classLoader, - JavaModule module, - boolean loaded, - DynamicType dynamicType) { - try { - if (typeDescription - .getName() - .equals("org.apache.hc.core5.http.message.LineFormatter")) { - // - // Utils.getInstrumentation().retransformClasses(Class.forName("org.apache.hc.core5.http.message.BasicHttpRequest", true, classLoader)); - } - dynamicType.saveIn(new File("/Users/mario.vidal/Documents/instrumentations")); - } catch (Throwable e) { - throw new RuntimeException(e); - } - } - - @Override - public void onDiscovery( - String typeName, - @MaybeNull ClassLoader classLoader, - @MaybeNull JavaModule module, - boolean loaded) { - super.onDiscovery(typeName, classLoader, module, loaded); - } - - @Override - public void onIgnored( - TypeDescription typeDescription, - @MaybeNull ClassLoader classLoader, - @MaybeNull JavaModule module, - boolean loaded) { - super.onIgnored(typeDescription, classLoader, module, loaded); - } - - @Override - public void onError( - String typeName, - ClassLoader classLoader, - JavaModule module, - boolean loaded, - Throwable throwable) { - super.onError(typeName, classLoader, module, loaded, throwable); - } - }) - // FIXME: we cannot enable it yet due to BB/JVM bug, see // https://github.com/raphw/byte-buddy/issues/558 // .with(AgentBuilder.LambdaInstrumentationStrategy.ENABLED) From e65bf5b954588f2988767bd0d221a32f0761e904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Vidal=20Dom=C3=ADnguez?= Date: Mon, 11 Nov 2024 19:40:36 +0100 Subject: [PATCH 04/14] Undo move of apache core module --- .../instrumentation/apache-httpclient-4/build.gradle | 2 +- .../{apache-httpcore => apache-httpcore-4}/build.gradle | 0 .../{apache-httpcore => apache-httpcore-4}/gradle.lockfile | 0 .../apachehttpcore/IastHttpHostInstrumentation.java | 0 .../apachehttpcore/IastHttpHostInstrumentationTest.groovy | 0 .../{apache-httpcore => }/apache-httpcore-5/build.gradle | 0 .../apachehttpcore5/BasicHttpRequestInstrumentation.java | 0 .../BasicHttpRequestInstrumentationTest.groovy | 0 .../smoketest/springboot/controller/SsrfController.java | 2 ++ .../datadog/smoketest/AbstractIastSpringBootTest.groovy | 2 +- settings.gradle | 4 ++-- 11 files changed, 6 insertions(+), 4 deletions(-) rename dd-java-agent/instrumentation/{apache-httpcore => apache-httpcore-4}/build.gradle (100%) rename dd-java-agent/instrumentation/{apache-httpcore => apache-httpcore-4}/gradle.lockfile (100%) rename dd-java-agent/instrumentation/{apache-httpcore => apache-httpcore-4}/src/main/java/datadog/trace/instrumentation/apachehttpcore/IastHttpHostInstrumentation.java (100%) rename dd-java-agent/instrumentation/{apache-httpcore => apache-httpcore-4}/src/test/groovy/datadog/trace/instrumentation/apachehttpcore/IastHttpHostInstrumentationTest.groovy (100%) rename dd-java-agent/instrumentation/{apache-httpcore => }/apache-httpcore-5/build.gradle (100%) rename dd-java-agent/instrumentation/{apache-httpcore => }/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentation.java (100%) rename dd-java-agent/instrumentation/{apache-httpcore => }/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentationTest.groovy (100%) diff --git a/dd-java-agent/instrumentation/apache-httpclient-4/build.gradle b/dd-java-agent/instrumentation/apache-httpclient-4/build.gradle index 87e28bb9a42..4c06d6f1bda 100644 --- a/dd-java-agent/instrumentation/apache-httpclient-4/build.gradle +++ b/dd-java-agent/instrumentation/apache-httpclient-4/build.gradle @@ -39,7 +39,7 @@ dependencies { iastIntegrationTestImplementation(testFixtures(project(':dd-java-agent:agent-iast'))) iastIntegrationTestImplementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.0' iastIntegrationTestRuntimeOnly(project(':dd-java-agent:instrumentation:jetty-9')) - iastIntegrationTestRuntimeOnly(project(':dd-java-agent:instrumentation:apache-httpcore')) + iastIntegrationTestRuntimeOnly(project(':dd-java-agent:instrumentation:apache-httpcore-4')) iastIntegrationTestRuntimeOnly(project(':dd-java-agent:instrumentation:servlet')) iastIntegrationTestRuntimeOnly(project(':dd-java-agent:instrumentation:java-lang')) iastIntegrationTestRuntimeOnly(project(':dd-java-agent:instrumentation:java-net')) diff --git a/dd-java-agent/instrumentation/apache-httpcore/build.gradle b/dd-java-agent/instrumentation/apache-httpcore-4/build.gradle similarity index 100% rename from dd-java-agent/instrumentation/apache-httpcore/build.gradle rename to dd-java-agent/instrumentation/apache-httpcore-4/build.gradle diff --git a/dd-java-agent/instrumentation/apache-httpcore/gradle.lockfile b/dd-java-agent/instrumentation/apache-httpcore-4/gradle.lockfile similarity index 100% rename from dd-java-agent/instrumentation/apache-httpcore/gradle.lockfile rename to dd-java-agent/instrumentation/apache-httpcore-4/gradle.lockfile diff --git a/dd-java-agent/instrumentation/apache-httpcore/src/main/java/datadog/trace/instrumentation/apachehttpcore/IastHttpHostInstrumentation.java b/dd-java-agent/instrumentation/apache-httpcore-4/src/main/java/datadog/trace/instrumentation/apachehttpcore/IastHttpHostInstrumentation.java similarity index 100% rename from dd-java-agent/instrumentation/apache-httpcore/src/main/java/datadog/trace/instrumentation/apachehttpcore/IastHttpHostInstrumentation.java rename to dd-java-agent/instrumentation/apache-httpcore-4/src/main/java/datadog/trace/instrumentation/apachehttpcore/IastHttpHostInstrumentation.java diff --git a/dd-java-agent/instrumentation/apache-httpcore/src/test/groovy/datadog/trace/instrumentation/apachehttpcore/IastHttpHostInstrumentationTest.groovy b/dd-java-agent/instrumentation/apache-httpcore-4/src/test/groovy/datadog/trace/instrumentation/apachehttpcore/IastHttpHostInstrumentationTest.groovy similarity index 100% rename from dd-java-agent/instrumentation/apache-httpcore/src/test/groovy/datadog/trace/instrumentation/apachehttpcore/IastHttpHostInstrumentationTest.groovy rename to dd-java-agent/instrumentation/apache-httpcore-4/src/test/groovy/datadog/trace/instrumentation/apachehttpcore/IastHttpHostInstrumentationTest.groovy diff --git a/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/build.gradle b/dd-java-agent/instrumentation/apache-httpcore-5/build.gradle similarity index 100% rename from dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/build.gradle rename to dd-java-agent/instrumentation/apache-httpcore-5/build.gradle diff --git a/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentation.java b/dd-java-agent/instrumentation/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentation.java similarity index 100% rename from dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentation.java rename to dd-java-agent/instrumentation/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentation.java diff --git a/dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentationTest.groovy b/dd-java-agent/instrumentation/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentationTest.groovy similarity index 100% rename from dd-java-agent/instrumentation/apache-httpcore/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentationTest.groovy rename to dd-java-agent/instrumentation/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentationTest.groovy diff --git a/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/SsrfController.java b/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/SsrfController.java index cad2bde3890..fb7ac1de245 100644 --- a/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/SsrfController.java +++ b/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/SsrfController.java @@ -97,6 +97,8 @@ public String apacheHttpClient5( @RequestParam(value = "url", required = false) final String url, @RequestParam(value = "host", required = false) final String host) { CloseableHttpClient client = HttpClients.createDefault(); + org.apache.hc.core5.http.message.BasicHttpRequest test = + new org.apache.hc.core5.http.message.BasicHttpRequest("GET", "/"); org.apache.hc.client5.http.classic.methods.HttpGet request = new org.apache.hc.client5.http.classic.methods.HttpGet(url); try { diff --git a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy index fad6741dce4..61deac69810 100644 --- a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy +++ b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy @@ -22,7 +22,7 @@ abstract class AbstractIastSpringBootTest extends AbstractIastServerSmokeTest { List command = [] command.add(javaPath()) // command.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005") - // command.add("-Ddd.trace.debug=true") + command.add("-Ddd.trace.trace=true") command.add("-verbose:class") command.addAll(defaultJavaProperties) command.addAll(iastJvmOpts()) diff --git a/settings.gradle b/settings.gradle index 2e0dbdb7b90..225eeecbcef 100644 --- a/settings.gradle +++ b/settings.gradle @@ -178,8 +178,8 @@ include ':dd-java-agent:instrumentation:akka-init' include ':dd-java-agent:instrumentation:apache-httpasyncclient-4' include ':dd-java-agent:instrumentation:apache-httpclient-4' include ':dd-java-agent:instrumentation:apache-httpclient-5' -include ':dd-java-agent:instrumentation:apache-httpcore' -include ':dd-java-agent:instrumentation:apache-httpcore:apache-httpcore-5' +include ':dd-java-agent:instrumentation:apache-httpcore-4' +include ':dd-java-agent:instrumentation:apache-httpcore-5' include ':dd-java-agent:instrumentation:armeria-grpc' include ':dd-java-agent:instrumentation:armeria-jetty' include ':dd-java-agent:instrumentation:avro' From c4bd2fb9b2727b6d887c7bcce06bffe3d75e849c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Vidal=20Dom=C3=ADnguez?= Date: Mon, 11 Nov 2024 19:44:32 +0100 Subject: [PATCH 05/14] Undo apache decorator --- .../apachehttpclient5/ApacheHttpClientDecorator.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/ApacheHttpClientDecorator.java b/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/ApacheHttpClientDecorator.java index 96517cf21d2..b1988a51de6 100644 --- a/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/ApacheHttpClientDecorator.java +++ b/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/ApacheHttpClientDecorator.java @@ -42,15 +42,6 @@ protected URI url(final HttpRequest request) throws URISyntaxException { return request.getUri(); } - @Override - protected Object sourceUrl(final HttpRequest request) { - try { - return request.getUri(); - } catch (URISyntaxException e) { - return null; - } - } - @Override protected int status(final HttpResponse httpResponse) { return httpResponse.getCode(); From b24df395f589194893310478c2502d9d89154721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Vidal=20Dom=C3=ADnguez?= Date: Mon, 11 Nov 2024 19:47:13 +0100 Subject: [PATCH 06/14] Remove unused variable in the ssrf controller --- .../smoketest/springboot/controller/SsrfController.java | 2 -- .../groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy | 3 --- 2 files changed, 5 deletions(-) diff --git a/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/SsrfController.java b/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/SsrfController.java index fb7ac1de245..cad2bde3890 100644 --- a/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/SsrfController.java +++ b/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/SsrfController.java @@ -97,8 +97,6 @@ public String apacheHttpClient5( @RequestParam(value = "url", required = false) final String url, @RequestParam(value = "host", required = false) final String host) { CloseableHttpClient client = HttpClients.createDefault(); - org.apache.hc.core5.http.message.BasicHttpRequest test = - new org.apache.hc.core5.http.message.BasicHttpRequest("GET", "/"); org.apache.hc.client5.http.classic.methods.HttpGet request = new org.apache.hc.client5.http.classic.methods.HttpGet(url); try { diff --git a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy index 61deac69810..8f9dae73aa5 100644 --- a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy +++ b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy @@ -21,9 +21,6 @@ abstract class AbstractIastSpringBootTest extends AbstractIastServerSmokeTest { List command = [] command.add(javaPath()) - // command.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005") - command.add("-Ddd.trace.trace=true") - command.add("-verbose:class") command.addAll(defaultJavaProperties) command.addAll(iastJvmOpts()) command.addAll((String[]) ['-jar', springBootShadowJar, "--server.port=${httpPort}"]) From c7ffba16628ae52c9a2c6860b55b73d9e7a7e1bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Vidal=20Dom=C3=ADnguez?= Date: Wed, 13 Nov 2024 10:11:22 +0100 Subject: [PATCH 07/14] Simplify instrumentation --- .../apachehttpcore5/BasicHttpRequestInstrumentation.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dd-java-agent/instrumentation/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentation.java b/dd-java-agent/instrumentation/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentation.java index ae6dcbd2d0c..735d55681ce 100644 --- a/dd-java-agent/instrumentation/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentation.java +++ b/dd-java-agent/instrumentation/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentation.java @@ -1,10 +1,12 @@ package datadog.trace.instrumentation.apachehttpcore5; -import static net.bytebuddy.matcher.ElementMatchers.any; +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.agent.tooling.InstrumenterModule; +import java.net.URI; import net.bytebuddy.asm.Advice; @AutoService(InstrumenterModule.class) @@ -22,7 +24,9 @@ public String instrumentedType() { @Override public void methodAdvice(MethodTransformer transformer) { - transformer.applyAdvice(any(), BasicHttpRequestInstrumentation.class.getName() + "$CtorAdvice"); + transformer.applyAdvice( + isConstructor().and(takesArguments(String.class, URI.class)), + BasicHttpRequestInstrumentation.class.getName() + "$CtorAdvice"); } public static class CtorAdvice { From 29ea5f291777a8e79ba35b42d2e52bdf1464e441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Vidal=20Dom=C3=ADnguez?= Date: Wed, 13 Nov 2024 14:36:38 +0100 Subject: [PATCH 08/14] First working solution --- .../ApacheHttpClientDecorator.java | 5 ++ ...IastHttpUriRequestBaseInstrumentation.java | 53 +++++++++++++++++++ ...tpUriRequestBaseInstrumentationTest.groovy | 29 ++++++++++ .../BasicHttpRequestInstrumentation.java | 38 ------------- ...BasicHttpRequestInstrumentationTest.groovy | 26 --------- .../iastinstrumenter/iast_exclusion.trie | 1 - 6 files changed, 87 insertions(+), 65 deletions(-) create mode 100644 dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/IastHttpUriRequestBaseInstrumentation.java create mode 100644 dd-java-agent/instrumentation/apache-httpclient-5/src/test/groovy/IastHttpUriRequestBaseInstrumentationTest.groovy delete mode 100644 dd-java-agent/instrumentation/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentation.java delete mode 100644 dd-java-agent/instrumentation/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentationTest.groovy diff --git a/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/ApacheHttpClientDecorator.java b/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/ApacheHttpClientDecorator.java index b1988a51de6..a52250cc5e6 100644 --- a/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/ApacheHttpClientDecorator.java +++ b/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/ApacheHttpClientDecorator.java @@ -42,6 +42,11 @@ protected URI url(final HttpRequest request) throws URISyntaxException { return request.getUri(); } + @Override + protected Object sourceUrl(final HttpRequest request) { + return request; + } + @Override protected int status(final HttpResponse httpResponse) { return httpResponse.getCode(); diff --git a/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/IastHttpUriRequestBaseInstrumentation.java b/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/IastHttpUriRequestBaseInstrumentation.java new file mode 100644 index 00000000000..830cd3c74ec --- /dev/null +++ b/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/IastHttpUriRequestBaseInstrumentation.java @@ -0,0 +1,53 @@ +package datadog.trace.instrumentation.apachehttpclient5; + +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.agent.tooling.bytebuddy.iast.TaintableVisitor; +import datadog.trace.api.iast.InstrumentationBridge; +import datadog.trace.api.iast.Propagation; +import datadog.trace.api.iast.propagation.PropagationModule; +import java.net.URI; +import net.bytebuddy.asm.Advice; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; + +@AutoService(InstrumenterModule.class) +public class IastHttpUriRequestBaseInstrumentation extends InstrumenterModule.Iast + implements Instrumenter.ForSingleType, Instrumenter.HasTypeAdvice { + + public IastHttpUriRequestBaseInstrumentation() { + super("testApache"); + } + + @Override + public String instrumentedType() { + return "org.apache.hc.client5.http.classic.methods.HttpUriRequestBase"; + } + + @Override + public void typeAdvice(TypeTransformer transformer) { + transformer.applyAdvice(new TaintableVisitor(instrumentedType())); + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isConstructor().and(takesArguments(String.class, URI.class)), + IastHttpUriRequestBaseInstrumentation.class.getName() + "$CtorAdvice"); + } + + public static class CtorAdvice { + @Advice.OnMethodExit() + @Propagation + public static void afterCtor( + @Advice.This final HttpUriRequestBase self, @Advice.Argument(1) final URI uri) { + final PropagationModule module = InstrumentationBridge.PROPAGATION; + if (module != null) { + module.taintObjectIfTainted(self, uri); + } + } + } +} diff --git a/dd-java-agent/instrumentation/apache-httpclient-5/src/test/groovy/IastHttpUriRequestBaseInstrumentationTest.groovy b/dd-java-agent/instrumentation/apache-httpclient-5/src/test/groovy/IastHttpUriRequestBaseInstrumentationTest.groovy new file mode 100644 index 00000000000..a3d88d683b3 --- /dev/null +++ b/dd-java-agent/instrumentation/apache-httpclient-5/src/test/groovy/IastHttpUriRequestBaseInstrumentationTest.groovy @@ -0,0 +1,29 @@ +import datadog.trace.agent.test.AgentTestRunner +import datadog.trace.api.iast.InstrumentationBridge +import datadog.trace.api.iast.propagation.PropagationModule +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase + +class IastHttpUriRequestBaseInstrumentationTest extends AgentTestRunner { + + @Override + protected void configurePreAgent() { + injectSysConfig('dd.iast.enabled', 'true') + } + + void 'test constructor'() { + given: + final module = Mock(PropagationModule) + InstrumentationBridge.registerIastModule(module) + + when: + HttpUriRequestBase.newInstance(method, new URI(uri)) + + then: + 1 * module.taintObjectIfTainted(_ as HttpUriRequestBase, _ as URI) + 0 * _ + + where: + method | uri + "GET" | 'http://localhost.com' + } +} diff --git a/dd-java-agent/instrumentation/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentation.java b/dd-java-agent/instrumentation/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentation.java deleted file mode 100644 index 735d55681ce..00000000000 --- a/dd-java-agent/instrumentation/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentation.java +++ /dev/null @@ -1,38 +0,0 @@ -package datadog.trace.instrumentation.apachehttpcore5; - -import static net.bytebuddy.matcher.ElementMatchers.isConstructor; -import static net.bytebuddy.matcher.ElementMatchers.takesArguments; - -import com.google.auto.service.AutoService; -import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.agent.tooling.InstrumenterModule; -import java.net.URI; -import net.bytebuddy.asm.Advice; - -@AutoService(InstrumenterModule.class) -public class BasicHttpRequestInstrumentation extends InstrumenterModule.Iast - implements Instrumenter.ForSingleType { - - public BasicHttpRequestInstrumentation() { - super("testApache"); - } - - @Override - public String instrumentedType() { - return "org.apache.hc.core5.http.message.BasicHttpRequest"; - } - - @Override - public void methodAdvice(MethodTransformer transformer) { - transformer.applyAdvice( - isConstructor().and(takesArguments(String.class, URI.class)), - BasicHttpRequestInstrumentation.class.getName() + "$CtorAdvice"); - } - - public static class CtorAdvice { - @Advice.OnMethodExit() - public static void afterCtor() { - System.out.println("CtorAdvice.afterCtor"); - } - } -} diff --git a/dd-java-agent/instrumentation/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentationTest.groovy b/dd-java-agent/instrumentation/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentationTest.groovy deleted file mode 100644 index 91a129ea13b..00000000000 --- a/dd-java-agent/instrumentation/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/BasicHttpRequestInstrumentationTest.groovy +++ /dev/null @@ -1,26 +0,0 @@ -package datadog.trace.instrumentation.apachehttpcore5 - -import datadog.trace.agent.test.AgentTestRunner -import org.apache.hc.core5.http.message.BasicHttpRequest - -class BasicHttpRequestInstrumentationTest extends AgentTestRunner { - - @Override - protected void configurePreAgent() { - injectSysConfig('dd.iast.enabled', 'true') - } - - void 'test constructor'(){ - given: - - when: - BasicHttpRequest.newInstance(*args) - - then: - 0 * _ - - where: - args | _ - ["GET", 'http://localhost.com'] | _ - } -} diff --git a/dd-java-agent/instrumentation/iast-instrumenter/src/main/resources/datadog/trace/instrumentation/iastinstrumenter/iast_exclusion.trie b/dd-java-agent/instrumentation/iast-instrumenter/src/main/resources/datadog/trace/instrumentation/iastinstrumenter/iast_exclusion.trie index 4fe880e6082..bf575257da8 100644 --- a/dd-java-agent/instrumentation/iast-instrumenter/src/main/resources/datadog/trace/instrumentation/iastinstrumenter/iast_exclusion.trie +++ b/dd-java-agent/instrumentation/iast-instrumenter/src/main/resources/datadog/trace/instrumentation/iastinstrumenter/iast_exclusion.trie @@ -196,7 +196,6 @@ #apache httpClient needs URI propagation 0 org.apache.http.client.methods.* 0 org.apache.hc.client5.http.classic.methods.* -0 org.apache.hc.core5.http.message.* # apache compiled jsps 0 org.apache.jsp.* 1 org.apiguardian.* From af7c2aa7eacc8840977d977eb6efe5310e4660a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Vidal=20Dom=C3=ADnguez?= Date: Wed, 13 Nov 2024 17:23:36 +0100 Subject: [PATCH 09/14] Add support for host methods --- .../HostAndRequestAsHttpUriRequest.java | 2 + ...IastHttpUriRequestBaseInstrumentation.java | 2 +- .../IastHttpHostInstrumentation.java | 47 +++++++++++++++++++ .../IastHttpHostInstrumentationTest.groovy | 31 ++++++++++++ .../springboot/controller/SsrfController.java | 17 +++++-- .../AbstractIastSpringBootTest.groovy | 25 +++++----- .../trace/api/iast/util/PropagationUtils.java | 12 +++++ 7 files changed, 118 insertions(+), 18 deletions(-) create mode 100644 dd-java-agent/instrumentation/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/IastHttpHostInstrumentation.java create mode 100644 dd-java-agent/instrumentation/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/IastHttpHostInstrumentationTest.groovy diff --git a/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/HostAndRequestAsHttpUriRequest.java b/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/HostAndRequestAsHttpUriRequest.java index 2cc2f41ba12..9240f7f79b7 100644 --- a/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/HostAndRequestAsHttpUriRequest.java +++ b/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/HostAndRequestAsHttpUriRequest.java @@ -1,5 +1,6 @@ package datadog.trace.instrumentation.apachehttpclient5; +import datadog.trace.api.iast.util.PropagationUtils; import java.net.URI; import java.net.URISyntaxException; import org.apache.hc.core5.http.Header; @@ -15,6 +16,7 @@ public class HostAndRequestAsHttpUriRequest extends BasicClassicHttpRequest { public HostAndRequestAsHttpUriRequest(final HttpHost httpHost, final HttpRequest httpRequest) { super(httpRequest.getMethod(), httpHost, httpRequest.getPath()); actualRequest = httpRequest; + PropagationUtils.taintObjectIfTainted(this, httpHost.toURI()); } @Override diff --git a/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/IastHttpUriRequestBaseInstrumentation.java b/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/IastHttpUriRequestBaseInstrumentation.java index 830cd3c74ec..d5e9c0b0f3c 100644 --- a/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/IastHttpUriRequestBaseInstrumentation.java +++ b/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/IastHttpUriRequestBaseInstrumentation.java @@ -19,7 +19,7 @@ public class IastHttpUriRequestBaseInstrumentation extends InstrumenterModule.Ia implements Instrumenter.ForSingleType, Instrumenter.HasTypeAdvice { public IastHttpUriRequestBaseInstrumentation() { - super("testApache"); + super("apache-httpclient", "httpclient5"); } @Override diff --git a/dd-java-agent/instrumentation/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/IastHttpHostInstrumentation.java b/dd-java-agent/instrumentation/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/IastHttpHostInstrumentation.java new file mode 100644 index 00000000000..3984a114c05 --- /dev/null +++ b/dd-java-agent/instrumentation/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/IastHttpHostInstrumentation.java @@ -0,0 +1,47 @@ +package datadog.trace.instrumentation.apachehttpcore5; + +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.api.iast.InstrumentationBridge; +import datadog.trace.api.iast.Propagation; +import datadog.trace.api.iast.propagation.PropagationModule; +import java.net.InetAddress; +import net.bytebuddy.asm.Advice; + +@AutoService(InstrumenterModule.class) +public class IastHttpHostInstrumentation extends InstrumenterModule.Iast + implements Instrumenter.ForSingleType { + + public IastHttpHostInstrumentation() { + super("httpcore", "apache-httpcore", "apache-http-core"); + } + + @Override + public String instrumentedType() { + return "org.apache.hc.core5.http.HttpHost"; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isConstructor() + .and(takesArguments(String.class, InetAddress.class, String.class, int.class)), + IastHttpHostInstrumentation.class.getName() + "$CtorAdvice"); + } + + public static class CtorAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + @Propagation + public static void afterCtor( + @Advice.This final Object self, @Advice.Argument(2) final Object host) { + final PropagationModule module = InstrumentationBridge.PROPAGATION; + if (module != null) { + module.taintObjectIfTainted(self, host); + } + } + } +} diff --git a/dd-java-agent/instrumentation/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/IastHttpHostInstrumentationTest.groovy b/dd-java-agent/instrumentation/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/IastHttpHostInstrumentationTest.groovy new file mode 100644 index 00000000000..d22fed18cd8 --- /dev/null +++ b/dd-java-agent/instrumentation/apache-httpcore-5/src/test/groovy/datadog/trace/instrumentation/apachehttpcore5/IastHttpHostInstrumentationTest.groovy @@ -0,0 +1,31 @@ +package datadog.trace.instrumentation.apachehttpcore5 + +import datadog.trace.agent.test.AgentTestRunner +import datadog.trace.api.iast.InstrumentationBridge +import datadog.trace.api.iast.propagation.PropagationModule +import org.apache.hc.core5.http.HttpHost + +class IastHttpHostInstrumentationTest extends AgentTestRunner { + + @Override + protected void configurePreAgent() { + injectSysConfig('dd.iast.enabled', 'true') + } + + void 'test constructor'(){ + given: + final module = Mock(PropagationModule) + InstrumentationBridge.registerIastModule(module) + + when: + HttpHost.newInstance(*args) + + then: + 1 * module.taintObjectIfTainted( _ as HttpHost, 'localhost') + + where: + args | _ + ['localhost'] | _ + ['localhost', 8080] | _ + } +} diff --git a/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/SsrfController.java b/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/SsrfController.java index cad2bde3890..27ab7a16511 100644 --- a/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/SsrfController.java +++ b/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/SsrfController.java @@ -95,17 +95,24 @@ public String okHttp3(@RequestParam(value = "url") final String url) { @PostMapping("/apache-httpclient5") public String apacheHttpClient5( @RequestParam(value = "url", required = false) final String url, + @RequestParam(value = "urlHandler", required = false) final String urlHandler, @RequestParam(value = "host", required = false) final String host) { CloseableHttpClient client = HttpClients.createDefault(); - org.apache.hc.client5.http.classic.methods.HttpGet request = - new org.apache.hc.client5.http.classic.methods.HttpGet(url); try { if (host != null) { - // final HttpHost httpHost = new HttpHost(host); - // final BasicHttpRequest request = new BasicHttpRequest("GET", "/"); - // client.execute(httpHost, request); + final org.apache.hc.core5.http.HttpHost httpHost = + new org.apache.hc.core5.http.HttpHost(host); + final org.apache.hc.client5.http.classic.methods.HttpGet request = + new org.apache.hc.client5.http.classic.methods.HttpGet("/"); + client.execute(httpHost, request); } else if (url != null) { + final org.apache.hc.client5.http.classic.methods.HttpGet request = + new org.apache.hc.client5.http.classic.methods.HttpGet(url); client.execute(request); + } else if (urlHandler != null) { + final org.apache.hc.client5.http.classic.methods.HttpGet request = + new org.apache.hc.client5.http.classic.methods.HttpGet(urlHandler); + client.execute(request, response -> null); } client.close(); } catch (Exception e) { diff --git a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy index 8f9dae73aa5..757fded30d3 100644 --- a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy +++ b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy @@ -724,7 +724,7 @@ abstract class AbstractIastSpringBootTest extends AbstractIastServerSmokeTest { 'host' | 'dd.datad0g.com' } - void 'ssrf is present (#path)'() { + void 'ssrf is present (#path) (#parameter)'() { setup: final url = "http://localhost:${httpPort}/ssrf/${path}" final body = new FormBody.Builder().add(parameter, value).build() @@ -739,27 +739,28 @@ abstract class AbstractIastSpringBootTest extends AbstractIastServerSmokeTest { return false } final parts = vul.evidence.valueParts - if (parameter == 'url') { + if (parameter == 'url' || parameter == 'urlHandler') { return parts.size() == 1 && parts[0].value == value && parts[0].source.origin == 'http.request.parameter' && parts[0].source.name == parameter } else if (parameter == 'host') { String protocol = protocolSecure ? 'https://' : 'http://' - return parts.size() == 2 - && parts[0].value == protocol + value && parts[0].source.origin == 'http.request.parameter' && parts[0].source.name == parameter - && parts[1].value == '/' && parts[1].source == null + String finalValue = protocol + value + (endSlash ? '/' : '') + return parts[0].value.endsWith(finalValue) && parts[0].source.origin == 'http.request.parameter' && parts[0].source.name == parameter } else { throw new IllegalArgumentException("Parameter $parameter not supported") } } where: - path | parameter | value | protocolSecure - "apache-httpclient4" | "url" | "https://dd.datad0g.com/" | true - "apache-httpclient4" | "host" | "dd.datad0g.com" | false - "apache-httpclient5" | "url" | "https://dd.datad0g.com/" | false - "commons-httpclient2" | "url" | "https://dd.datad0g.com/" | true - "okHttp2" | "url" | "https://dd.datad0g.com/" | true - "okHttp3" | "url" | "https://dd.datad0g.com/" | true + path | parameter | value | protocolSecure | endSlash + "apache-httpclient4" | "url" | "https://dd.datad0g.com/" | true | true + "apache-httpclient4" | "host" | "dd.datad0g.com" | false | false + "apache-httpclient5" | "url" | "https://dd.datad0g.com/" | true | true + "apache-httpclient5" | "urlHandler" | "https://dd.datad0g.com/" | true | true + "apache-httpclient5" | "host" | "dd.datad0g.com" | false | true + "commons-httpclient2" | "url" | "https://dd.datad0g.com/" | true | true + "okHttp2" | "url" | "https://dd.datad0g.com/" | true | true + "okHttp3" | "url" | "https://dd.datad0g.com/" | true | true } void 'test iast metrics stored in spans'() { diff --git a/internal-api/src/main/java/datadog/trace/api/iast/util/PropagationUtils.java b/internal-api/src/main/java/datadog/trace/api/iast/util/PropagationUtils.java index 6049a6b4339..12d8eb195df 100644 --- a/internal-api/src/main/java/datadog/trace/api/iast/util/PropagationUtils.java +++ b/internal-api/src/main/java/datadog/trace/api/iast/util/PropagationUtils.java @@ -2,6 +2,7 @@ import datadog.trace.api.iast.InstrumentationBridge; import datadog.trace.api.iast.propagation.CodecModule; +import datadog.trace.api.iast.propagation.PropagationModule; import datadog.trace.api.iast.propagation.StringModule; import java.net.URI; @@ -44,4 +45,15 @@ public static StringBuilder onStringBuilderAppend(final String path, final Strin } return sb; } + + public static void taintObjectIfTainted(final Object target, final Object input) { + final PropagationModule module = InstrumentationBridge.PROPAGATION; + if (module != null) { + try { + module.taintObjectIfTainted(target, input); + } catch (final Throwable e) { + module.onUnexpectedException("taintObjectIfTainted threw", e); + } + } + } } From 160c0ca395e31bc360bbfa75b82c488262e77721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Vidal=20Dom=C3=ADnguez?= Date: Wed, 13 Nov 2024 17:31:52 +0100 Subject: [PATCH 10/14] Added support for last methods --- .../apachehttpclient5/HostAndRequestAsHttpUriRequest.java | 4 +++- .../datadog/smoketest/AbstractIastSpringBootTest.groovy | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/HostAndRequestAsHttpUriRequest.java b/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/HostAndRequestAsHttpUriRequest.java index 9240f7f79b7..da3045f8b45 100644 --- a/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/HostAndRequestAsHttpUriRequest.java +++ b/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/HostAndRequestAsHttpUriRequest.java @@ -16,7 +16,9 @@ public class HostAndRequestAsHttpUriRequest extends BasicClassicHttpRequest { public HostAndRequestAsHttpUriRequest(final HttpHost httpHost, final HttpRequest httpRequest) { super(httpRequest.getMethod(), httpHost, httpRequest.getPath()); actualRequest = httpRequest; - PropagationUtils.taintObjectIfTainted(this, httpHost.toURI()); + // Propagate in case the host or request is tainted + PropagationUtils.taintObjectIfTainted(this, httpHost); + PropagationUtils.taintObjectIfTainted(this, httpRequest); } @Override diff --git a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy index 757fded30d3..d220214415d 100644 --- a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy +++ b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy @@ -741,7 +741,7 @@ abstract class AbstractIastSpringBootTest extends AbstractIastServerSmokeTest { final parts = vul.evidence.valueParts if (parameter == 'url' || parameter == 'urlHandler') { return parts.size() == 1 - && parts[0].value == value && parts[0].source.origin == 'http.request.parameter' && parts[0].source.name == parameter + && parts[0].value.endsWith(value) && parts[0].source.origin == 'http.request.parameter' && parts[0].source.name == parameter } else if (parameter == 'host') { String protocol = protocolSecure ? 'https://' : 'http://' String finalValue = protocol + value + (endSlash ? '/' : '') From 03a95d67116079d77febafd049227b6be05d63aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Vidal=20Dom=C3=ADnguez?= Date: Wed, 13 Nov 2024 17:39:58 +0100 Subject: [PATCH 11/14] Add test propagationUtils --- .../api/iast/util/PropagationUtilsTest.groovy | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/internal-api/src/test/groovy/datadog/trace/api/iast/util/PropagationUtilsTest.groovy b/internal-api/src/test/groovy/datadog/trace/api/iast/util/PropagationUtilsTest.groovy index d379283aa01..f180f946db8 100644 --- a/internal-api/src/test/groovy/datadog/trace/api/iast/util/PropagationUtilsTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/api/iast/util/PropagationUtilsTest.groovy @@ -2,6 +2,7 @@ package datadog.trace.api.iast.util import datadog.trace.api.iast.InstrumentationBridge import datadog.trace.api.iast.propagation.CodecModule +import datadog.trace.api.iast.propagation.PropagationModule import datadog.trace.api.iast.propagation.StringModule import datadog.trace.test.util.DDSpecification @@ -106,6 +107,23 @@ class PropagationUtilsTest extends DDSpecification { value << ['http://test.com'] } + void 'test taintObjectIfTainted'() { + setup: + final iastModule = Mock(PropagationModule) + InstrumentationBridge.registerIastModule(iastModule) + String inputTest = "test" + + when: + PropagationUtils.taintObjectIfTainted(value, inputTest) + + then: + 1 * iastModule.taintObjectIfTainted(value, inputTest) + 0 * _ + + where: + value << ['http://test.com'] + } + void 'test onUriCreate throw exception'() { setup: final iastModule = Mock(CodecModule) @@ -162,4 +180,22 @@ class PropagationUtilsTest extends DDSpecification { where: value << ['http://test.com'] } + + void 'test taintObjectIfTainted throw exception'() { + setup: + final iastModule = Mock(PropagationModule) + InstrumentationBridge.registerIastModule(iastModule) + String inputTest = "test" + + when: + PropagationUtils.taintObjectIfTainted(value, inputTest) + + then: + 1 * iastModule.taintObjectIfTainted(value, inputTest) >> { throw new Exception("test exception") } + 1 * iastModule.onUnexpectedException("taintObjectIfTainted threw", _ as Exception) + 0 * _ + + where: + value << ['http://test.com'] + } } From 7c085ba15b90abca0fd39c44cfb482edb120c2b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Vidal=20Dom=C3=ADnguez?= Date: Thu, 14 Nov 2024 11:35:32 +0100 Subject: [PATCH 12/14] Add support for apache-httpasynclient --- .../ApacheHttpAsyncClientDecorator.java | 5 +++ dd-smoke-tests/iast-util/build.gradle | 1 + .../springboot/controller/SsrfController.java | 32 +++++++++++++++++++ .../AbstractIastSpringBootTest.groovy | 31 +++++++++++------- .../spring-boot-2.6-webmvc/build.gradle | 1 + dd-smoke-tests/springboot/build.gradle | 2 +- 6 files changed, 60 insertions(+), 12 deletions(-) diff --git a/dd-java-agent/instrumentation/apache-httpasyncclient-4/src/main/java/datadog/trace/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientDecorator.java b/dd-java-agent/instrumentation/apache-httpasyncclient-4/src/main/java/datadog/trace/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientDecorator.java index 5f9e44fd6df..58cf3600286 100644 --- a/dd-java-agent/instrumentation/apache-httpasyncclient-4/src/main/java/datadog/trace/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientDecorator.java +++ b/dd-java-agent/instrumentation/apache-httpasyncclient-4/src/main/java/datadog/trace/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientDecorator.java @@ -41,6 +41,11 @@ protected URI url(final HttpUriRequest request) throws URISyntaxException { return request.getURI(); } + @Override + protected Object sourceUrl(final HttpUriRequest request) { + return request.getURI(); + } + @Override protected int status(final HttpContext context) { final Object responseObject = context.getAttribute(HttpCoreContext.HTTP_RESPONSE); diff --git a/dd-smoke-tests/iast-util/build.gradle b/dd-smoke-tests/iast-util/build.gradle index 2324b1fd420..a7d97b6c75f 100644 --- a/dd-smoke-tests/iast-util/build.gradle +++ b/dd-smoke-tests/iast-util/build.gradle @@ -18,4 +18,5 @@ dependencies { compileOnly group: 'com.squareup.okhttp', name: 'okhttp', version: '2.2.0' compileOnly group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.0.0' compileOnly group: 'org.apache.httpcomponents.client5', name: 'httpclient5', version: '5.0' + compileOnly group: 'org.apache.httpcomponents', name: 'httpasyncclient', version: '4.0' } diff --git a/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/SsrfController.java b/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/SsrfController.java index 27ab7a16511..411e857f9db 100644 --- a/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/SsrfController.java +++ b/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/SsrfController.java @@ -12,7 +12,11 @@ import org.apache.http.HttpHost; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; +import org.apache.http.impl.nio.client.HttpAsyncClients; import org.apache.http.message.BasicHttpRequest; +import org.apache.http.nio.client.methods.HttpAsyncMethods; +import org.apache.http.nio.protocol.HttpAsyncRequestProducer; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -119,4 +123,32 @@ public String apacheHttpClient5( } return "ok"; } + + @PostMapping("/apache-httpasyncclient") + public String apacheHttpAsyncClient( + @RequestParam(value = "url", required = false) final String url, + @RequestParam(value = "host", required = false) final String host, + @RequestParam(value = "urlProducer", required = false) final String urlProducer) { + final CloseableHttpAsyncClient client = HttpAsyncClients.createDefault(); + client.start(); + try { + if (host != null) { + final HttpHost httpHost = new HttpHost(host); + client.execute(httpHost, new HttpGet("/"), null); + } else if (url != null) { + final HttpGet request = new HttpGet(url); + client.execute(request, null); + } else if (urlProducer != null) { + final HttpAsyncRequestProducer producer = HttpAsyncMethods.create(new HttpGet(urlProducer)); + client.execute(producer, null, null); + } + } catch (Exception e) { + } finally { + try { + client.close(); + } catch (Exception e) { + } + } + return "ok"; + } } diff --git a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy index d220214415d..60afbb43256 100644 --- a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy +++ b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy @@ -21,6 +21,9 @@ abstract class AbstractIastSpringBootTest extends AbstractIastServerSmokeTest { List command = [] command.add(javaPath()) + // command.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005") + command.add("-Ddd.trace.trace=true") + command.add("-verbose:class") command.addAll(defaultJavaProperties) command.addAll(iastJvmOpts()) command.addAll((String[]) ['-jar', springBootShadowJar, "--server.port=${httpPort}"]) @@ -739,28 +742,34 @@ abstract class AbstractIastSpringBootTest extends AbstractIastServerSmokeTest { return false } final parts = vul.evidence.valueParts - if (parameter == 'url' || parameter == 'urlHandler') { + if (parameter == 'url') { return parts.size() == 1 - && parts[0].value.endsWith(value) && parts[0].source.origin == 'http.request.parameter' && parts[0].source.name == parameter + && parts[0].value == value && parts[0].source.origin == 'http.request.parameter' && parts[0].source.name == parameter } else if (parameter == 'host') { String protocol = protocolSecure ? 'https://' : 'http://' String finalValue = protocol + value + (endSlash ? '/' : '') return parts[0].value.endsWith(finalValue) && parts[0].source.origin == 'http.request.parameter' && parts[0].source.name == parameter + } else if (parameter == 'urlProducer' || parameter == 'urlHandler') { + return parts.size() == 1 + && parts[0].value.endsWith(value) && parts[0].source.origin == 'http.request.parameter' && parts[0].source.name == parameter } else { throw new IllegalArgumentException("Parameter $parameter not supported") } } where: - path | parameter | value | protocolSecure | endSlash - "apache-httpclient4" | "url" | "https://dd.datad0g.com/" | true | true - "apache-httpclient4" | "host" | "dd.datad0g.com" | false | false - "apache-httpclient5" | "url" | "https://dd.datad0g.com/" | true | true - "apache-httpclient5" | "urlHandler" | "https://dd.datad0g.com/" | true | true - "apache-httpclient5" | "host" | "dd.datad0g.com" | false | true - "commons-httpclient2" | "url" | "https://dd.datad0g.com/" | true | true - "okHttp2" | "url" | "https://dd.datad0g.com/" | true | true - "okHttp3" | "url" | "https://dd.datad0g.com/" | true | true + path | parameter | value | protocolSecure | endSlash + "apache-httpclient4" | "url" | "https://dd.datad0g.com/" | true | true + "apache-httpclient4" | "host" | "dd.datad0g.com" | false | false + "apache-httpasyncclient" | "url" | "https://dd.datad0g.com/" | true | true + "apache-httpasyncclient" | "urlProducer" | "https://dd.datad0g.com/" | true | true + "apache-httpasyncclient" | "host" | "dd.datad0g.com" | false | false + "apache-httpclient5" | "url" | "https://dd.datad0g.com/" | true | true + "apache-httpclient5" | "urlHandler" | "https://dd.datad0g.com/" | true | true + "apache-httpclient5" | "host" | "dd.datad0g.com" | false | true + "commons-httpclient2" | "url" | "https://dd.datad0g.com/" | true | true + "okHttp2" | "url" | "https://dd.datad0g.com/" | true | true + "okHttp3" | "url" | "https://dd.datad0g.com/" | true | true } void 'test iast metrics stored in spans'() { diff --git a/dd-smoke-tests/spring-boot-2.6-webmvc/build.gradle b/dd-smoke-tests/spring-boot-2.6-webmvc/build.gradle index d2d76846e80..e0003f7668b 100644 --- a/dd-smoke-tests/spring-boot-2.6-webmvc/build.gradle +++ b/dd-smoke-tests/spring-boot-2.6-webmvc/build.gradle @@ -43,6 +43,7 @@ dependencies { implementation group: 'com.squareup.okhttp', name: 'okhttp', version: '2.2.0' implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.0.0' implementation group: 'org.apache.httpcomponents.client5', name: 'httpclient5', version: '5.0' + implementation group: 'org.apache.httpcomponents', name: 'httpasyncclient', version: '4.0' testImplementation project(':dd-smoke-tests') implementation project(':dd-smoke-tests:iast-util') diff --git a/dd-smoke-tests/springboot/build.gradle b/dd-smoke-tests/springboot/build.gradle index 3f0f5d34d89..fc4edc573c5 100644 --- a/dd-smoke-tests/springboot/build.gradle +++ b/dd-smoke-tests/springboot/build.gradle @@ -34,7 +34,7 @@ dependencies { implementation group: 'com.squareup.okhttp', name: 'okhttp', version: '2.2.0' implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.0.0' implementation group: 'org.apache.httpcomponents.client5', name: 'httpclient5', version: '5.0' - + implementation group: 'org.apache.httpcomponents', name: 'httpasyncclient', version: '4.0' testImplementation project(':dd-smoke-tests') testImplementation(testFixtures(project(":dd-smoke-tests:iast-util"))) From 7b2ee35c932a464a4cc556cd8fb3df43d318aa1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Vidal=20Dom=C3=ADnguez?= Date: Thu, 14 Nov 2024 16:20:41 +0100 Subject: [PATCH 13/14] Minor changes --- .../apachehttpasyncclient/ApacheHttpAsyncClientDecorator.java | 2 +- .../apachehttpclient5/ApacheHttpClientDecorator.java | 2 +- .../apachehttpcore5/IastHttpHostInstrumentation.java | 2 +- .../groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy | 3 --- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/dd-java-agent/instrumentation/apache-httpasyncclient-4/src/main/java/datadog/trace/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientDecorator.java b/dd-java-agent/instrumentation/apache-httpasyncclient-4/src/main/java/datadog/trace/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientDecorator.java index 58cf3600286..e8faa71340d 100644 --- a/dd-java-agent/instrumentation/apache-httpasyncclient-4/src/main/java/datadog/trace/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientDecorator.java +++ b/dd-java-agent/instrumentation/apache-httpasyncclient-4/src/main/java/datadog/trace/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientDecorator.java @@ -42,7 +42,7 @@ protected URI url(final HttpUriRequest request) throws URISyntaxException { } @Override - protected Object sourceUrl(final HttpUriRequest request) { + protected URI sourceUrl(final HttpUriRequest request) { return request.getURI(); } diff --git a/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/ApacheHttpClientDecorator.java b/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/ApacheHttpClientDecorator.java index a52250cc5e6..78d7958d6f6 100644 --- a/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/ApacheHttpClientDecorator.java +++ b/dd-java-agent/instrumentation/apache-httpclient-5/src/main/java/datadog/trace/instrumentation/apachehttpclient5/ApacheHttpClientDecorator.java @@ -43,7 +43,7 @@ protected URI url(final HttpRequest request) throws URISyntaxException { } @Override - protected Object sourceUrl(final HttpRequest request) { + protected HttpRequest sourceUrl(final HttpRequest request) { return request; } diff --git a/dd-java-agent/instrumentation/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/IastHttpHostInstrumentation.java b/dd-java-agent/instrumentation/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/IastHttpHostInstrumentation.java index 3984a114c05..0a07b270640 100644 --- a/dd-java-agent/instrumentation/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/IastHttpHostInstrumentation.java +++ b/dd-java-agent/instrumentation/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/IastHttpHostInstrumentation.java @@ -17,7 +17,7 @@ public class IastHttpHostInstrumentation extends InstrumenterModule.Iast implements Instrumenter.ForSingleType { public IastHttpHostInstrumentation() { - super("httpcore", "apache-httpcore", "apache-http-core"); + super("httpcore-5", "apache-httpcore-5", "apache-http-core-5"); } @Override diff --git a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy index 60afbb43256..e7f7000f78b 100644 --- a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy +++ b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy @@ -21,9 +21,6 @@ abstract class AbstractIastSpringBootTest extends AbstractIastServerSmokeTest { List command = [] command.add(javaPath()) - // command.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005") - command.add("-Ddd.trace.trace=true") - command.add("-verbose:class") command.addAll(defaultJavaProperties) command.addAll(iastJvmOpts()) command.addAll((String[]) ['-jar', springBootShadowJar, "--server.port=${httpPort}"]) From 5347dd7d98b88538bb473f55a30a66a2b92e4b27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Vidal=20Dom=C3=ADnguez?= Date: Mon, 9 Dec 2024 10:14:38 +0100 Subject: [PATCH 14/14] Change argument to string --- .../apachehttpcore5/IastHttpHostInstrumentation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dd-java-agent/instrumentation/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/IastHttpHostInstrumentation.java b/dd-java-agent/instrumentation/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/IastHttpHostInstrumentation.java index 0a07b270640..67bb5f83a5a 100644 --- a/dd-java-agent/instrumentation/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/IastHttpHostInstrumentation.java +++ b/dd-java-agent/instrumentation/apache-httpcore-5/src/main/java/datadog/trace/instrumentation/apachehttpcore5/IastHttpHostInstrumentation.java @@ -37,7 +37,7 @@ public static class CtorAdvice { @Advice.OnMethodExit(suppress = Throwable.class) @Propagation public static void afterCtor( - @Advice.This final Object self, @Advice.Argument(2) final Object host) { + @Advice.This final Object self, @Advice.Argument(2) final String host) { final PropagationModule module = InstrumentationBridge.PROPAGATION; if (module != null) { module.taintObjectIfTainted(self, host);