|
| 1 | +# Fuzz Testing in Moza Profiel Service |
| 2 | + |
| 3 | +To boost the security of our application, we have implemented fuzz testing for our REST endpoints and core components. Fuzzing is a testing technique that provides semi-random data as input to the application to find bugs, crashes, or security vulnerabilities. |
| 4 | + |
| 5 | +## What has been implemented? |
| 6 | + |
| 7 | +We have implemented **two types** of fuzz tests: |
| 8 | + |
| 9 | +### 1. JUnit-based Fuzz Tests (for local development) |
| 10 | + |
| 11 | +- **EndpointFuzzTest.java**: A JUnit test class using `@FuzzTest` annotations and `RestAssured` to fuzz REST endpoints in a running Quarkus test instance. |
| 12 | +- **Purpose**: Quick feedback during development and as part of the regular test suite. |
| 13 | +- **Runs**: As part of `mvn test`, with configurable duration using `-Djazzer.duration=<time>`. |
| 14 | + |
| 15 | +### 2. Standalone Fuzz Targets (for ClusterFuzzLite) |
| 16 | + |
| 17 | +These are lightweight, standalone classes that implement the `fuzzerTestOneInput` method expected by Jazzer's native driver. They run in ClusterFuzzLite's continuous fuzzing pipeline: |
| 18 | + |
| 19 | +- **EndpointFuzzer.java**: Coverage-guided fuzzing of all REST endpoints by starting Quarkus as a subprocess and sending HTTP requests. |
| 20 | +- **HashHelperFuzzer.java**: Tests the SHA-256 hashing functionality with arbitrary string inputs to verify determinism and detect edge cases. |
| 21 | +- **JsonDeserializationFuzzer.java**: Fuzzes JSON deserialization of all request DTOs (ContactgegevenRequest, VoorkeurRequest, DienstverlenerRequest, etc.) to find parsing vulnerabilities. |
| 22 | + |
| 23 | +**Key Difference**: The JUnit tests run Quarkus in-process with `@QuarkusTest`, while the ClusterFuzzLite targets are optimized for long-running, coverage-guided fuzzing in CI/CD pipelines. |
| 24 | + |
| 25 | +## How to run Fuzz Tests |
| 26 | + |
| 27 | +By default, fuzz tests run as part of the normal test suite but with only a few iterations. To run them for a longer period (which is recommended for finding real issues), you can use the following Maven command: |
| 28 | + |
| 29 | +```bash |
| 30 | +mvn test -Dtest=EndpointFuzzTest -Djazzer.duration=1m -Djacoco.skip=true |
| 31 | +``` |
| 32 | + |
| 33 | +The `-Djazzer.duration=1m` flag tells Jazzer to run for 1 minute. You can increase this (e.g., `10m`, `1h`) for more thorough testing. |
| 34 | + |
| 35 | +> **Note**: We use `-Djacoco.skip=true` because running only a subset of tests (like just the fuzz tests) will likely fail the JaCoCo coverage check, as the overall project coverage threshold won't be met. |
| 36 | +
|
| 37 | +### Running a specific Fuzz Test |
| 38 | + |
| 39 | +To run only one specific fuzz test method: |
| 40 | + |
| 41 | +```bash |
| 42 | +mvn test -Dtest=EndpointFuzzTest#fuzzGetPartij -Djazzer.duration=1m -Djacoco.skip=true |
| 43 | +``` |
| 44 | + |
| 45 | +## Adding more Fuzz Tests |
| 46 | + |
| 47 | +### Adding a JUnit-based Fuzz Test |
| 48 | + |
| 49 | +To add a new fuzz test to `EndpointFuzzTest`: |
| 50 | + |
| 51 | +1. Add a method to `EndpointFuzzTest` (or create a new test class). |
| 52 | +2. Annotate it with `@FuzzTest`. |
| 53 | +3. Use `FuzzedDataProvider` to generate semi-random input data. |
| 54 | +4. Use `RestAssured` to send this data to your endpoint. |
| 55 | + |
| 56 | +Example: |
| 57 | + |
| 58 | +```java |
| 59 | +@FuzzTest |
| 60 | +public void fuzzMyNewEndpoint(FuzzedDataProvider data) { |
| 61 | + String input = data.consumeRemainingAsString(); |
| 62 | + |
| 63 | + RestAssured.given() |
| 64 | + .body(input) |
| 65 | + .when() |
| 66 | + .post("/api/my-endpoint") |
| 67 | + .then() |
| 68 | + .extract().response(); |
| 69 | +} |
| 70 | +``` |
| 71 | + |
| 72 | +### Adding a Standalone Fuzzer for ClusterFuzzLite |
| 73 | + |
| 74 | +To add a new standalone fuzzer that runs in the CI/CD pipeline: |
| 75 | + |
| 76 | +1. Create a new class in `src/test/java/nl/rijksoverheid/moz/fuzzing/`. |
| 77 | +2. Implement a `public static void fuzzerTestOneInput(FuzzedDataProvider data)` method. |
| 78 | +3. Use `data` to consume input and test your functionality. |
| 79 | +4. The fuzzer will automatically be detected by `.clusterfuzzlite/build.sh` and included in continuous fuzzing. |
| 80 | + |
| 81 | +Example: |
| 82 | + |
| 83 | +```java |
| 84 | +package nl.rijksoverheid.moz.fuzzing; |
| 85 | + |
| 86 | +import com.code_intelligence.jazzer.api.FuzzedDataProvider; |
| 87 | + |
| 88 | +public class MyComponentFuzzer { |
| 89 | + |
| 90 | + public static void fuzzerTestOneInput(FuzzedDataProvider data) { |
| 91 | + String input = data.consumeRemainingAsString(); |
| 92 | + |
| 93 | + // Test your component with the fuzzed input |
| 94 | + MyComponent.process(input); |
| 95 | + } |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +## What to look for? |
| 100 | + |
| 101 | +Jazzer will automatically stop and report if it finds: |
| 102 | +- An unhandled exception that causes the JVM to crash (e.g., `OutOfMemoryError`, `StackOverflowError`). |
| 103 | +- Any security-relevant exceptions if configured. |
| 104 | +- Assertions that fail. |
| 105 | + |
| 106 | +If Jazzer finds a "finding", it will create a `fuzz-test-*.repro` file. You can use this file to reproduce the exact input that caused the failure. |
| 107 | + |
| 108 | +## GitHub Scorecard & Continuous Fuzzing |
| 109 | + |
| 110 | +To satisfy the [GitHub Scorecard](https://github.com/ossf/scorecard) "Fuzzing" requirement, this project is configured to use **ClusterFuzzLite (CFL)**. |
| 111 | + |
| 112 | +### How it works: |
| 113 | + |
| 114 | +1. **ClusterFuzzLite**: We have integrated ClusterFuzzLite via GitHub Actions (see `.clusterfuzzlite/` and `.github/workflows/cflite_*.yml`). |
| 115 | +2. **Continuous Testing**: |
| 116 | + - **PR Mode**: Every pull request triggers a short fuzzing session (5 minutes) to catch regressions before they are merged. This is configured to run on all pull requests with the target branch being `main`. |
| 117 | + - **Batch Mode**: A longer daily fuzzing session (1 hour) runs on the `main` branch to discover deeper issues. |
| 118 | + - **Manual Trigger**: Fuzzing workflows can also be triggered manually via the GitHub Actions "Run workflow" button. |
| 119 | +3. **Scorecard Recognition**: By having these workflows in place and running them, the OpenSSF Scorecard will recognize that the project is being actively fuzzed, which improves our security score. |
| 120 | + |
| 121 | +### Configuration |
| 122 | + |
| 123 | +- `.clusterfuzzlite/Dockerfile`: Defines the build environment (based on `oss-fuzz-base`). |
| 124 | +- `.clusterfuzzlite/build.sh`: Script that compiles the application and prepares the fuzzing targets for Jazzer. |
| 125 | +- `.github/workflows/cflite_pr.yml`: GitHub Action for PR fuzzing. |
| 126 | +- `.github/workflows/cflite_batch.yml`: GitHub Action for scheduled batch fuzzing. |
0 commit comments