Skip to main content

Find Your First Bug in Java

This quick start guide teaches you the basics of using CI Fuzz with a Java project. CI Fuzz supports Maven and Gradle as build systems.

This guide was created using macOS. If you are on Linux or Windows, you should be able to follow along without any issues.

If you didn't already install cifuzz, head over to the setup section.


Maven

Set up the repository

Download or clone the following repository: https://github.com/CodeIntelligenceTesting/ci-fuzz-cli-tutorials. This repository contains several tutorials and example projects. This guide focuses on the project in the tutorials/java/maven directory.

note

Run all cifuzz commands listed in this section from the tutorials/java/maven directory.

Initialize the project

To use CI Fuzz in the project, you first need to initialize it.

Run the following command to create the CI Fuzz configuration file cifuzz.yaml:

cifuzz init

CI Fuzz automatically recognizes Maven as the build system of the project and provides instructions on how to configure your project to enable fuzz testing.

For an easy setup you can use the CI Fuzz Maven extension by adding it to the projects pom.xml additionally with the access to the CI repository:

pom.xml
<build>
<extensions>
<extension>
<groupId>com.code-intelligence</groupId>
<artifactId>cifuzz-maven-extension</artifactId>
<version>1.7.0</version>
</extension>
</extensions>
</build>

<repositories>
<repository>
<id>code-intelligence</id>
<url>https://gitlab.code-intelligence.com/api/v4/projects/89/packages/maven</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>code-intelligence</id>
<url>https://gitlab.code-intelligence.com/api/v4/projects/89/packages/maven</url>
</pluginRepository>
</pluginRepositories>

As stated in the instructions, don't forget to add your access information to the CI Repository in your global settings file ~/.m2/settings.xml:

settings.xml
<?xml version="1.0" encoding="UTF-8"?>
<settings xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 http://maven.apache.org/xsd/settings-1.2.0.xsd" xmlns="http://maven.apache.org/SETTINGS/1.2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<servers>
<server>
<id>code-intelligence</id>
<username><!--YOUR_USERNAME--></username>
<password><!--YOUR_TOKEN--></password>
</server>
</servers>
</settings>

Following the setup instructions, your pom.xml should look like this:

pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.github.CodeIntelligenceTesting.cifuzz</groupId>
<artifactId>maven-example</artifactId>
<version>1.0-SNAPSHOT</version>

<name>maven-example</name>
<description>A simple maven-example for cifuzz</description>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>

<dependencies>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
</plugins>
<extensions>
<extension>
<groupId>com.code-intelligence</groupId>
<artifactId>cifuzz-maven-extension</artifactId>
<version>1.7.0</version>
</extension>
</extensions>
</build>

<repositories>
<repository>
<id>code-intelligence</id>
<url>https://gitlab.code-intelligence.com/api/v4/projects/89/packages/maven</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>code-intelligence</id>
<url>https://gitlab.code-intelligence.com/api/v4/projects/89/packages/maven</url>
</pluginRepository>
</pluginRepositories>
</project>

Create a fuzz test

The next step is to create a Java fuzz test template. To make fuzz testing as easy as unit testing, you should place the fuzz test in a test directory, exactly as you would a standard unit test.

Run the following command to create a file MyFuzzTest.java with a fuzz test stub:

cifuzz create java -o src/test/java/com/example/MyFuzzTest.java

Before you write the fuzz test, take a look at the target method that you want to fuzz in src/main/java/com/example/ExploreMe.java:

ExploreMe.java
public static void exploreMe(int a, int b, String c) {
if (a >= 20000) {
if (b >= 2000000) {
if (b - a < 100000) {
if (c.startsWith("@")) {
String className = c.substring(1);
try {
Class.forName(className).newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException ignored) {
}
}
}
}
}
}

The main parts to focus on here is the parameters that the exploreMe function requires: int a, int b and string c. As long as you pass the correct data types to the function, the fuzzer takes care of the rest.

Now you can fill in the fuzz test template with a real fuzz test:

MyFuzzTest.java
package com.example;

import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import com.code_intelligence.jazzer.junit.FuzzTest;

public class MyFuzzTest {
@FuzzTest
void myFuzzTest(FuzzedDataProvider data) {
int a = data.consumeInt();
int b = data.consumeInt();
String c = data.consumeRemainingAsString();

ExploreMe.exploreMe(a, b, c);
}
}

A few notes about this fuzz test:

  • The fuzz test is part of the same package as the target class/method.
  • The @FuzzTest annotation is what enables you to write fuzz tests similar to how you'd write JUnit tests.
  • The fuzz test must import com.code_intelligence.jazzer.junit.FuzzTest
  • This fuzz test uses the FuzzedDataProvider class. This isn't required, but it's a convenient way to split the fuzzing input in the data variable into different data types. Here is a link to the FuzzedDataProvider class documentation if you want to view its other methods.
  • Once you have created the appropriate variables (a, b, and c) using data from the fuzzer, the fuzz test has to call the target method (ExploreMe.exploreMe) with the fuzz data.

Run the fuzz test

You now have successfully set up and initialized your project, added all necessary configuration and created a fuzz test.

Now you can run the fuzz test:

cifuzz run com.example.MyFuzzTest::myFuzzTest 

cifuzz should discover a Remote Code Execution quickly:

5 Unit Test Equivalents and 1 new Finding in 6s.
1 Finding in total.

💥 [nimble_peccary] Security Issue: Remote Code Execution in com.example.ExploreMe.exploreMe (src/main/java/com/example/ExploreMe.java:11)

The command also asks if you want to run the cifuzz coverage command, in case you want coverage information about the fuzz test.

Examine the Finding

If cifuzz discovers a Finding, it stores the output from the Finding and the input that caused it. You can list all Findings discovered so far by running cifuzz findings.

Origin | Severity | Name           | Description                           | Fuzz Test              | Location
Local | 7.0 | nimble_peccary | Security Issue: Remote Code Execution | com.example.MyFuzzTest | src/main/java/com/example/ExploreMe.java:11

If you want to see the stack trace and details of a specific Finding you can provide its name, for example cifuzz finding nimble_peccary. This information can help you debug and fix the issue.

Examining the output from cifuzz finding nimble_peccary shows that there was a Remote Code Execution and this triggers at line 11 in the exploreMe function. If you examine line 11 in src/main/java/com/example/ExploreMe.java, you can see this is where it attempts to load a class based off of input data.

 == Java Exception: com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh: Remote Code Execution
Unrestricted class/object creation based on externally controlled data may allow
remote code execution depending on available classes on the classpath.
at jaz.Zer.reportFinding(Zer.java:108)
at jaz.Zer.reportFindingIfEnabled(Zer.java:103)
at jaz.Zer.<init>(Zer.java:76)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
at java.base/java.lang.reflect.ReflectAccess.newInstance(ReflectAccess.java:128)
at java.base/jdk.internal.reflect.ReflectionFactory.newInstance(ReflectionFactory.java:347)
at java.base/java.lang.Class.newInstance(Class.java:645)
at com.example.ExploreMe.exploreMe(ExploreMe.java:11)
at com.example.MyFuzzTest.myFuzzTest(MyFuzzTest.java:13)

If possible, CI Fuzz also provides more information about the kind of Finding:

┌────────────────────────────────────────────────────────────────────────────────────────────────────┐
| Name | Remote Code Execution |
| Severity Level | Critical |
| Severity Score | 7.0 |
| CWE Name | Improper Control of Generation of Code ('Code Injection') |
| CWE Description | The product constructs all or part of a code segment using |
| | externally-influenced input from an upstream component, but it does not |
| | neutralize or incorrectly neutralizes special elements that could modify the |
| | syntax or behavior of the intended code segment when it is sent to a downstream |
| | component. |
└────────────────────────────────────────────────────────────────────────────────────────────────────┘
Description:
Remote code execution attacks allow an attacker to execute arbitrary code on a vulnerable system.
This can be used to steal data, bypass authentication, and gain access to more systems.
RCE is often combined with other application vulnerability, such as SQL injection, to gain initial access to a system.

cifuzz stores the crashing input in the resources directory. In this case: src/test/resources/com/example/MyFuzzTestInputs/myFuzzTest. The crashing input has the same name as the Finding and Findings in this directory now serve as inputs for future runs to help identify regressions.

Gradle

Set up the repository

Download or clone the following repository: https://github.com/CodeIntelligenceTesting/ci-fuzz-cli-tutorials. This repository contains several tutorials and example projects. This guide focuses on the project in the tutorials/java/gradle directory.

note

Run all cifuzz commands listed in this section from the tutorials/java/gradle directory.

Initialize the project

To use CI Fuzz in the project, you first need to initialize it.

Run the following command to create the CI Fuzz configuration file cifuzz.yaml:

cifuzz init

CI Fuzz automatically recognizes Gradle as the build system of the project and provides instructions how to configure your project to enable fuzz testing.

For an easy setup you can use the CI Fuzz Gradle plugin by adding it to the projects build.gradle additionally with the access to the CI repository:

plugins {
id "com.code-intelligence.cifuzz" version "1.17.0"
}

repositories {
maven {
name "CodeIntelligenceRepository"
url "https://gitlab.code-intelligence.com/api/v4/projects/89/packages/maven"
credentials {
username CodeIntelligenceRepositoryUsername
password CodeIntelligenceRepositoryPassword
}
content {
includeGroupByRegex("com\\.code-intelligence.*")
}
}
mavenCentral()
}

As stated in the instructions, don't forget to add your access information to the CI Repository in your global settings file ~/.gradle/gradle.properties and the projects settings.gradle:

CodeIntelligenceRepositoryUsername=<YOUR_USERNAME>
CodeIntelligenceRepositoryPassword=<YOUR_TOKEN>
pluginManagement {
repositories {
maven {
name "CodeIntelligenceRepository"
url "https://gitlab.code-intelligence.com/api/v4/projects/89/packages/maven"
credentials {
username CodeIntelligenceRepositoryUsername
password CodeIntelligenceRepositoryPassword
}
content {
includeGroupByRegex("com\\.code-intelligence.*")
}
}
gradlePluginPortal()
}
}

Following the setup instructions, your build.gradle should look like this:

plugins {
id 'application'
id "com.code-intelligence.cifuzz" version "1.17.0"
}

repositories {
maven {
name "CodeIntelligenceRepository"
url "https://gitlab.code-intelligence.com/api/v4/projects/89/packages/maven"
credentials {
username CodeIntelligenceRepositoryUsername
password CodeIntelligenceRepositoryPassword
}
content {
includeGroupByRegex("com\\.code-intelligence.*")
}
}
mavenCentral()
}

application {
mainClass = 'com.example.App'
}

Create a fuzz test

The next step is to create a Java fuzz test template. To make fuzz testing as easy as unit testing, you should place the fuzz test in a test directory, exactly as you would a standard unit test.

Run the following command to create a file MyFuzzTest.java with a fuzz test stub:

cifuzz create java -o src/test/java/com/example/MyFuzzTest.java

Before you write the fuzz test, take a look at the target method that you want to fuzz in src/main/java/com/example/ExploreMe.java:

ExploreMe.java
public static void exploreMe(int a, int b, String c) {
if (a >= 20000) {
if (b >= 2000000) {
if (b - a < 100000) {
if (c.startsWith("@")) {
String className = c.substring(1);
try {
Class.forName(className).newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException ignored) {
}
}
}
}
}
}

The main parts to focus on here are the parameters that the exploreMe function requires: int a, int b and string c. As long as you pass the correct data types to the function, the fuzzer takes care of the rest.

Now you can fill in the fuzz test template with a real fuzz test:

MyFuzzTest.java
package com.example;

import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import com.code_intelligence.jazzer.junit.FuzzTest;

public class MyFuzzTest {
@FuzzTest
void myFuzzTest(FuzzedDataProvider data) {
int a = data.consumeInt();
int b = data.consumeInt();
String c = data.consumeRemainingAsString();

ExploreMe.exploreMe(a, b, c);
}
}

A few notes about this fuzz test:

  • The fuzz test is part of the same package as the target class/method.
  • The @FuzzTest annotation is what enables you to write Fuzz Tests similar to how you'd write JUnit tests.
  • The fuzz test must import com.code_intelligence.jazzer.junit.FuzzTest
  • This fuzz test uses the FuzzedDataProvider class. This isn't required, but it's a convenient way to split the fuzzing input in the data variable into different data types. Here is a link to the FuzzedDataProvider class documentation if you want to view its other methods.
  • Once you have created the appropriate variables (a, b, and c) using data from the fuzzer, the fuzz test has to call the target method (ExploreMe.exploreMe) with the fuzz data.

Run the fuzz test

You now have successfully set up and initialized your project, added all necessary configuration and created a fuzz test.

Now you can run the fuzz test:

cifuzz run com.example.MyFuzzTest::myFuzzTest 

cifuzz should discover a Remote Code Execution quickly:

5 Unit Test Equivalents and 1 new Finding in 4s.
1 Finding in total.

💥 [clever_wasp] Security Issue: Remote Code Execution in com.example.ExploreMe.exploreMe (src/main/java/com/example/ExploreMe.java:11)

The command also asks if you want to run the cifuzz coverage command, in case you want coverage information about the fuzz test.

Examine the Findings

If cifuzz discovers a Finding, it stores the output from the Finding and the input that caused it. You can list all Findings discovered so far by running cifuzz findings.

Origin | Severity | Name        | Description                           | Fuzz Test              | Location
Local | 7.0 | clever_wasp | Security Issue: Remote Code Execution | com.example.MyFuzzTest | src/main/java/com/example/ExploreMe.java:11

If you want to see the stack trace and details of a specific Finding you can provide its name, for example cifuzz finding clever_wasp. This information can help you debug and fix the issue.

Examining the output from cifuzz finding nimble_peccary shows that there was a Remote Code Execution and this triggers at line 11 in the exploreMe function. If you examine line 11 in src/main/java/com/example/ExploreMe.java, you can see this is where it attempts to load a class based off of input data.

 == Java Exception: com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh: Remote Code Execution
Unrestricted class/object creation based on externally controlled data may allow
remote code execution depending on available classes on the classpath.
at jaz.Zer.reportFinding(Zer.java:108)
at jaz.Zer.reportFindingIfEnabled(Zer.java:103)
at jaz.Zer.<init>(Zer.java:76)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
at java.base/java.lang.reflect.ReflectAccess.newInstance(ReflectAccess.java:128)
at java.base/jdk.internal.reflect.ReflectionFactory.newInstance(ReflectionFactory.java:347)
at java.base/java.lang.Class.newInstance(Class.java:645)
at com.example.ExploreMe.exploreMe(ExploreMe.java:11)
at com.example.MyFuzzTest.myFuzzTest(MyFuzzTest.java:13)

If possible, CI Fuzz also provides more information about the kind of Finding:

┌────────────────────────────────────────────────────────────────────────────────────────────────────┐
| Name | Remote Code Execution |
| Severity Level | Critical |
| Severity Score | 7.0 |
| CWE Name | Improper Control of Generation of Code ('Code Injection') |
| CWE Description | The product constructs all or part of a code segment using |
| | externally-influenced input from an upstream component, but it does not |
| | neutralize or incorrectly neutralizes special elements that could modify the |
| | syntax or behavior of the intended code segment when it is sent to a downstream |
| | component. |
└────────────────────────────────────────────────────────────────────────────────────────────────────┘
Description:
Remote code execution attacks allow an attacker to execute arbitrary code on a vulnerable system.
This can be used to steal data, bypass authentication, and gain access to more systems.
RCE is often combined with other application vulnerability, such as SQL injection, to gain initial access to a system.

cifuzz stores the crashing input in the resources directory. In this case: src/test/resources/com/example/MyFuzzTestInputs/myFuzzTest. The crashing input has the same name as the Finding and Findings in this directory now serve as inputs for future runs to help identify regressions.