diff --git a/.java-version b/.java-version new file mode 100644 index 000000000..98d9bcb75 --- /dev/null +++ b/.java-version @@ -0,0 +1 @@ +17 diff --git a/pom.xml b/pom.xml index 8a3d6f151..1ab4d96ac 100644 --- a/pom.xml +++ b/pom.xml @@ -4,17 +4,22 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.springframework.samples - spring-petclinic + spring-petclinic-runtime-agent 3.2.0-SNAPSHOT org.springframework.boot spring-boot-starter-parent - 3.2.1 + 3.2.0 - petclinic + petclinic-runtime-agent + + 2.45.0-01 + http://ec2-107-23-150-171.compute-1.amazonaws.com:8070/ + ec2-107-23-150-171.compute-1.amazonaws.com + stage-release 17 @@ -143,6 +148,65 @@ + + + com.sonatype.clm + clm-maven-plugin + ${clm.maven.plugin.version} + + ${artifactId} + stage-release + + + + sonatype-prepare + test-compile + + evaluate + + + + sonatype-reevaluate-after-run + prepare-package + + evaluate + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + test + + + + + + -javaagent:../runtime-agent-1.0.7.jar + -Dsonatype.runtime.agent.enabled=true + -Dsonatype.runtime.agent.debugMode=false + -Dsonatype.runtime.agent.iq.protocol=http + -Dsonatype.runtime.agent.iq.host=ec2-107-23-150-171.compute-1.amazonaws.com + -Dsonatype.runtime.agent.iq.port=8070 + -Dsonatype.runtime.agent.iq.user=${env.IQ_USER} + -Dsonatype.runtime.agent.iq.password=${env.IQ_PASSWORD} + -Dsonatype.runtime.agent.iq.applicationId=spring-petclinic-runtime-agent + -Dsonatype.runtime.agent.isIqApplicationIdPublic=true + -Dsonatype.runtime.agent.blockedRunOnStartup=true + -Dsonatype.runtime.agent.scanClasspath=false + -Dsonatype.runtime.agent.fetchVulnerableClassesFromIQ=true + -Dsonatype.runtime.agent.vulnerableMethodDetectionEnabled=true + + + + + org.apache.maven.plugins maven-enforcer-plugin @@ -201,7 +265,8 @@ src/checkstyle/nohttp-checkstyle-suppressions.xml ${basedir} **/* - **/.git/**/*,**/.idea/**/*,**/target/**/,**/.flattened-pom.xml,**/*.class + + pom.xml,**/.git/**/*,**/.idea/**/*,**/target/**/,**/.flattened-pom.xml,**/*.class check @@ -218,7 +283,7 @@ spring-boot-maven-plugin - build-info @@ -376,7 +441,7 @@ - org.eclipse.m2e @@ -434,4 +499,4 @@ - \ No newline at end of file + diff --git a/readme.md b/readme.md index 79b0fb692..bfd6ff3ee 100644 --- a/readme.md +++ b/readme.md @@ -6,15 +6,41 @@ [See the presentation here](https://speakerdeck.com/michaelisvy/spring-petclinic-sample-application) -## Run Petclinic locally +## Run Petclinic with the runtime agent locally Spring Petclinic is a [Spring Boot](https://spring.io/guides/gs/spring-boot) application built using [Maven](https://spring.io/guides/gs/maven/) or [Gradle](https://spring.io/guides/gs/gradle/). You can build a jar file and run it from the command line (it should work just as well with Java 17 or newer): ```bash git clone https://github.com/spring-projects/spring-petclinic.git cd spring-petclinic + +# This runs the integration tests which will invoke some vulnerable methods as part of tests coverage and the +# runtime labels set in IQ on: +# - ch.qos.logback : logback-core : 1.4.11 +# - ch.qos.logback : logback-classic : 1.4.11 +# - org.springframework : spring-core : 6.1.1 ./mvnw package -java -jar target/*.jar + +# Note: Change the path the runtime-agent-1.0.7.jar as necessary +# After starting the app, by navigating to http://localhost:8080/vets.html invokes vulnerable method call. +# This will log additional vulnerable method calls that were not covered by unit tests and sets the runtime labels in IQ. +# If the label is already set due to integration tests, it will log as such. +# If needed, the labels can be deleted manually in IQ before starting the app. +java -javaagent:../runtime-agent-1.0.7.jar \ + -Dsonatype.runtime.agent.enabled=true \ + -Dsonatype.runtime.agent.debugMode=false \ + -Dsonatype.runtime.agent.iq.protocol=http \ + -Dsonatype.runtime.agent.iq.host=ec2-107-23-150-171.compute-1.amazonaws.com \ + -Dsonatype.runtime.agent.iq.port=8070 \ + -Dsonatype.runtime.agent.iq.user=${IQ_USER} \ + -Dsonatype.runtime.agent.iq.password=${IQ_PASSWORD} \ + -Dsonatype.runtime.agent.iq.applicationId=spring-petclinic-runtime-agent \ + -Dsonatype.runtime.agent.isIqApplicationIdPublic=true \ + -Dsonatype.runtime.agent.blockedRunOnStartup=true \ + -Dsonatype.runtime.agent.scanClasspath=false \ + -Dsonatype.runtime.agent.fetchVulnerableClassesFromIQ=true \ + -Dsonatype.runtime.agent.vulnerableMethodDetectionEnabled=true \ + -jar target/*.jar ``` You can then access the Petclinic at . diff --git a/src/main/java/org/springframework/samples/petclinic/vet/VetController.java b/src/main/java/org/springframework/samples/petclinic/vet/VetController.java index 3240814a6..c096321fb 100644 --- a/src/main/java/org/springframework/samples/petclinic/vet/VetController.java +++ b/src/main/java/org/springframework/samples/petclinic/vet/VetController.java @@ -17,6 +17,8 @@ package org.springframework.samples.petclinic.vet; import java.util.List; +import org.springframework.core.io.buffer.DefaultDataBuffer; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -43,6 +45,9 @@ class VetController { @GetMapping("/vets.html") public String showVetList(@RequestParam(defaultValue = "1") int page, Model model) { + // Demo: Inject vulnerable method manually. + simulateVulnerableMethodCall(); + // Here we are returning an object of type 'Vets' rather than a collection of Vet // objects so it is simpler for Object-Xml mapping Vets vets = new Vets(); @@ -51,6 +56,57 @@ class VetController { return addPaginationModel(page, paginated, model); } + /* + * There are 4 vulnerable method signatures in this application: + * 'ch/qos/logback/classic/spi/LoggingEventVO#readObject(Ljava/io/ObjectInputStream;)V + * ', + * 'org/springframework/core/io/buffer/DefaultDataBuffer#split(I)Lorg/springframework/ + * core/io/buffer/DataBuffer; + * 'org/h2/tools/Backup#process(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String; + * Z)V + * 'ch/qos/logback/core/net/HardenedObjectInputStream#(Ljava/io/InputStream;[ + * Ljava/lang/String;)V + * + * This method simulates a vulnerable method call for demo purposes. It seems like no + * other code path call any of the vulnerable method, so manually invoking it. + * + * The following logs will be printed while navigating to + * http://localhost:8080/vets.html: Sonatype Runtime Agent - [TIME]: *** Vulnerable + * CLASS LOADED [className=org/springframework/core/io/buffer/DefaultDataBuffer] by + * the JVM Sonatype Runtime Agent - [TIME]: Assigning label 'Runtime-Class-Loaded' to + * component 22d73bef97aff8a74a99 in application: a50576c3cd894d20b24dc0d98eea084b + * Sonatype Runtime Agent - [TIME]: Component evaluation for + * [ComponentEvaluation{hash='22d73bef97aff8a74a99'}] in application + * a50576c3cd894d20b24dc0d98eea084b successful. Result + * URL=api/v2/evaluation/applications/a50576c3cd894d20b24dc0d98eea084b/results/ + * 3d62858ec88e49e0afd552066cb160ad Sonatype Runtime Agent - [TIME]: *** Class with + * vulnerable METHOD LOADED + * [className=org/springframework/core/io/buffer/DefaultDataBuffer, methodName=split, + * methodDescriptor=(I)Lorg/springframework/core/io/buffer/DataBuffer;] by the JVM + * Sonatype Runtime Agent - [TIME]: Assigning label 'Runtime-Method-Loaded' to + * component 22d73bef97aff8a74a99 in application: a50576c3cd894d20b24dc0d98eea084b + * Sonatype Runtime Agent - [TIME]: Component evaluation for + * [ComponentEvaluation{hash='22d73bef97aff8a74a99'}] in application + * a50576c3cd894d20b24dc0d98eea084b successful. Result + * URL=api/v2/evaluation/applications/a50576c3cd894d20b24dc0d98eea084b/results/ + * 47fa37da85d8447f8c101d4db35ec797 Sonatype Runtime Agent - [TIME]: *** Vulnerable + * METHOD CALLED [className=org/springframework/core/io/buffer/DefaultDataBuffer, + * methodName=split, + * methodDescriptor=(I)Lorg/springframework/core/io/buffer/DataBuffer;] Sonatype + * Runtime Agent - [TIME]: Assigning label 'Runtime-Method-Called' to component + * 22d73bef97aff8a74a99 in application: a50576c3cd894d20b24dc0d98eea084b Sonatype + * Runtime Agent - [TIME]: Component evaluation for + * [ComponentEvaluation{hash='22d73bef97aff8a74a99'}] in application + * a50576c3cd894d20b24dc0d98eea084b successful. Result + * URL=api/v2/evaluation/applications/a50576c3cd894d20b24dc0d98eea084b/results/ + * 6a969f11748f45abba95870fcd7747bb + */ + private void simulateVulnerableMethodCall() { + DefaultDataBufferFactory defaultDataBufferFactory = new DefaultDataBufferFactory(); + DefaultDataBuffer defaultDataBuffer = defaultDataBufferFactory.allocateBuffer(1024); + defaultDataBuffer.split(0); + } + private String addPaginationModel(int page, Page paginated, Model model) { List listVets = paginated.getContent(); model.addAttribute("currentPage", page); diff --git a/src/test/java/org/springframework/samples/petclinic/MySqlIntegrationTests.java b/src/test/java/org/springframework/samples/petclinic/MySqlIntegrationTests.java index 867ef905b..4046589f0 100644 --- a/src/test/java/org/springframework/samples/petclinic/MySqlIntegrationTests.java +++ b/src/test/java/org/springframework/samples/petclinic/MySqlIntegrationTests.java @@ -16,8 +16,6 @@ package org.springframework.samples.petclinic; -import static org.assertj.core.api.Assertions.assertThat; - import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledInNativeImage; import org.springframework.beans.factory.annotation.Autowired; @@ -26,6 +24,8 @@ import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.core.io.buffer.DefaultDataBuffer; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.http.HttpStatus; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; @@ -37,6 +37,8 @@ import org.testcontainers.containers.MySQLContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; +import static org.assertj.core.api.Assertions.assertThat; + @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @ActiveProfiles("mysql") @Testcontainers(disabledWithoutDocker = true) @@ -57,8 +59,50 @@ class MySqlIntegrationTests { @Autowired private RestTemplateBuilder builder; + void simulateVulnerableMethodCall() { + /* + * Sonatype Runtime Agent - [TIME]: *** Vulnerable CLASS LOADED + * [className=org/springframework/core/io/buffer/DefaultDataBuffer] by the JVM + * Sonatype Runtime Agent - [TIME]: Assigning label 'Runtime-Class-Loaded' to + * component 22d73bef97aff8a74a99 in application: a50576c3cd894d20b24dc0d98eea084b + * Sonatype Runtime Agent - [TIME]: Component evaluation for + * [ComponentEvaluation{hash='22d73bef97aff8a74a99'}] in application + * a50576c3cd894d20b24dc0d98eea084b successful. Result + * URL=api/v2/evaluation/applications/a50576c3cd894d20b24dc0d98eea084b/results/ + * 3d62858ec88e49e0afd552066cb160ad Sonatype Runtime Agent - [TIME]: *** Class + * with vulnerable METHOD LOADED + * [className=org/springframework/core/io/buffer/DefaultDataBuffer, + * methodName=split, + * methodDescriptor=(I)Lorg/springframework/core/io/buffer/DataBuffer;] by the JVM + * Sonatype Runtime Agent - [TIME]: Assigning label 'Runtime-Method-Loaded' to + * component 22d73bef97aff8a74a99 in application: a50576c3cd894d20b24dc0d98eea084b + * Sonatype Runtime Agent - [TIME]: Component evaluation for + * [ComponentEvaluation{hash='22d73bef97aff8a74a99'}] in application + * a50576c3cd894d20b24dc0d98eea084b successful. Result + * URL=api/v2/evaluation/applications/a50576c3cd894d20b24dc0d98eea084b/results/ + * 47fa37da85d8447f8c101d4db35ec797 Sonatype Runtime Agent - [TIME]: *** + * Vulnerable METHOD CALLED + * [className=org/springframework/core/io/buffer/DefaultDataBuffer, + * methodName=split, + * methodDescriptor=(I)Lorg/springframework/core/io/buffer/DataBuffer;] Sonatype + * Runtime Agent - [TIME]: Assigning label 'Runtime-Method-Called' to component + * 22d73bef97aff8a74a99 in application: a50576c3cd894d20b24dc0d98eea084b Sonatype + * Runtime Agent - [TIME]: Component evaluation for + * [ComponentEvaluation{hash='22d73bef97aff8a74a99'}] in application + * a50576c3cd894d20b24dc0d98eea084b successful. Result + * URL=api/v2/evaluation/applications/a50576c3cd894d20b24dc0d98eea084b/results/ + * 6a969f11748f45abba95870fcd7747bb + */ + DefaultDataBufferFactory defaultDataBufferFactory = new DefaultDataBufferFactory(); + DefaultDataBuffer defaultDataBuffer = defaultDataBufferFactory.allocateBuffer(1024); + defaultDataBuffer.split(0); + } + @Test void testFindAll() throws Exception { + // Demo: Simulate Runtime-Method-Called + simulateVulnerableMethodCall(); + vets.findAll(); vets.findAll(); // served from cache } diff --git a/src/test/java/org/springframework/samples/petclinic/PostgresIntegrationTests.java b/src/test/java/org/springframework/samples/petclinic/PostgresIntegrationTests.java index 18945a570..4cdad9a2c 100644 --- a/src/test/java/org/springframework/samples/petclinic/PostgresIntegrationTests.java +++ b/src/test/java/org/springframework/samples/petclinic/PostgresIntegrationTests.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; +import ch.qos.logback.core.net.HardenedObjectInputStream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.jupiter.api.BeforeAll; @@ -77,8 +78,39 @@ public class PostgresIntegrationTests { .run(args); } + void simulateVulnerableClassWithMethodLoaded() { + /* + * Sonatype Runtime Agent - [TIME]: *** Vulnerable CLASS LOADED + * [className=ch/qos/logback/core/net/HardenedObjectInputStream] by the JVM + * Sonatype Runtime Agent - [TIME]: Assigning label 'Runtime-Class-Loaded' to + * component 2f9f280219a9922a7420 in application: a50576c3cd894d20b24dc0d98eea084b + * Sonatype Runtime Agent - [TIME]: Component evaluation for + * [ComponentEvaluation{hash='2f9f280219a9922a7420'}] in application + * a50576c3cd894d20b24dc0d98eea084b successful. Result + * URL=api/v2/evaluation/applications/a50576c3cd894d20b24dc0d98eea084b/results/ + * 74387681c75446a5924812d032c77cad Sonatype Runtime Agent - [TIME]: *** Class + * with vulnerable METHOD LOADED + * [className=ch/qos/logback/core/net/HardenedObjectInputStream, + * methodName=, + * methodDescriptor=(Ljava/io/InputStream;[Ljava/lang/String;)V] by the JVM + * Sonatype Runtime Agent - [TIME]: Assigning label 'Runtime-Method-Loaded' to + * component 2f9f280219a9922a7420 in application: a50576c3cd894d20b24dc0d98eea084b + * Sonatype Runtime Agent - [TIME]: Component evaluation for + * [ComponentEvaluation{hash='2f9f280219a9922a7420'}] in application + * a50576c3cd894d20b24dc0d98eea084b successful. Result + * URL=api/v2/evaluation/applications/a50576c3cd894d20b24dc0d98eea084b/results/ + * 488f999c6730499a8cd454b37d3201b2 >> + * org.springframework.samples.petclinic.PostgresIntegrationTests loaded + * ch.qos.logback.core.net.HardenedObjectInputStream + */ + System.out.println(">> " + getClass().getName() + " loaded " + HardenedObjectInputStream.class.getName()); + } + @Test void testFindAll() throws Exception { + // Demo: Simulate Runtime-Class-Loaded and Runtime-Method-Loaded + simulateVulnerableClassWithMethodLoaded(); + vets.findAll(); vets.findAll(); // served from cache }