From 9a94659d9717392522187e7e0c776bb76e5425d9 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Mon, 24 Nov 2025 09:15:22 +0100 Subject: [PATCH] feat: SSRF env var to allow all connections before it's configured In some projects, network requests can happen after the fuzz test has started, but before the user had a chance to configure allowed/denied connections. Now the user can start Jazzer with the environmental variable JAZZER_SSRF_PERMISSIVE_UNTIL_CONFIGURED set to a truthy value and all network requests will be allowed, until the user specifies otherwise in the fuzz test using BugDetectors.allowNetworkConnections(...) --- docs/arguments-and-configuration-options.md | 4 ++ .../sanitizers/ServerSideRequestForgery.java | 19 +++++- .../src/test/java/com/example/BUILD.bazel | 17 ++++++ .../com/example/SsrfAllowUntilConfigured.java | 58 +++++++++++++++++++ 4 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 sanitizers/src/test/java/com/example/SsrfAllowUntilConfigured.java diff --git a/docs/arguments-and-configuration-options.md b/docs/arguments-and-configuration-options.md index 4f18737cb..16e7c5183 100644 --- a/docs/arguments-and-configuration-options.md +++ b/docs/arguments-and-configuration-options.md @@ -126,6 +126,10 @@ Some parameters only have an effect when used with standalone Jazzer binary (mar - `false` (default): Use only thej crash file folder. - `true`: Use both the crash file folder and the corpus folder. +- **JAZZER_SSRF_PERMISSIVE_UNTIL_CONFIGURED** [bool, default="false"] (*environment variable only*) + - When set to `true`, the SSRF sanitizer will allow all outgoing requests until it is explicitly configured with BugDetectors.allowNetworkConnections(...). + This is useful to avoid false positives in multithreaded applications that make network requests after the fuzzing has started, but before the user had a chance to configure the sanitizer. + - **keep_going** [uint64, default="1"] - Number of distinct findings after which the fuzzer should stop. See [here](advanced.md#keep-going) for more details. diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ServerSideRequestForgery.java b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ServerSideRequestForgery.java index 5ac3f795d..2d52c7a27 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ServerSideRequestForgery.java +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ServerSideRequestForgery.java @@ -34,9 +34,19 @@ public class ServerSideRequestForgery { public static final AtomicReference> connectionPermitted = new AtomicReference<>((host, port) -> true); - // Disallow all connections right before the first fuzz target is executed. static { - Jazzer.onFuzzTargetReady(() -> connectionPermitted.set((host, port) -> false)); + // Disallow all connections right before the first fuzz target is executed, if the user has not + // specifically opted into permissive behavior. + // In multithreaded fuzzing scenarios, a network request can be made after the fuzzing has + // started but before a user had a chance to call BugDetectors.allowNetworkConnections(). + if (!permissivelyParseBoolean(System.getenv("JAZZER_SSRF_PERMISSIVE_UNTIL_CONFIGURED"))) { + Jazzer.onFuzzTargetReady(() -> connectionPermitted.set((host, port) -> false)); + } + } + + static boolean permissivelyParseBoolean(String value) { + return value != null + && (value.equalsIgnoreCase("true") || value.equals("1") || value.equalsIgnoreCase("yes")); } /** @@ -132,7 +142,10 @@ private static void checkSsrf(Object[] arguments) { + "If the fuzz test is expected to perform network connections, call" + " com.code_intelligence.jazzer.api.BugDetectors#allowNetworkConnections at" + " the beginning of your fuzz test and optionally provide a predicate" - + " matching the expected hosts.", + + " matching the expected hosts.\n\n" + + "In multithreaded fuzzing scenarios consider using the environment variable" + + " JAZZER_SSRF_PERMISSIVE_UNTIL_CONFIGURED to allow all connections until" + + " the fuzz test has been configured to allow specific connections.", host, port))); } } diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel index 4d64b0fca..76a1d32eb 100644 --- a/sanitizers/src/test/java/com/example/BUILD.bazel +++ b/sanitizers/src/test/java/com/example/BUILD.bazel @@ -594,6 +594,23 @@ java_fuzz_target_test( verify_crash_reproducer = False, ) +java_fuzz_target_test( + name = "SsrfAllowUntilConfigured", + srcs = [ + "SsrfAllowUntilConfigured.java", + ], + env = { + "JAZZER_SSRF_PERMISSIVE_UNTIL_CONFIGURED": "1", + }, + fuzzer_args = [ + "-runs=1", + "-print_final_stats=1", + ], + # tags = ["dangerous"], + target_class = "com.example.SsrfAllowUntilConfigured", + verify_crash_reproducer = False, +) + java_fuzz_target_test( name = "ScriptEngineInjection", srcs = [ diff --git a/sanitizers/src/test/java/com/example/SsrfAllowUntilConfigured.java b/sanitizers/src/test/java/com/example/SsrfAllowUntilConfigured.java new file mode 100644 index 000000000..7b44c9172 --- /dev/null +++ b/sanitizers/src/test/java/com/example/SsrfAllowUntilConfigured.java @@ -0,0 +1,58 @@ +/* + * Copyright 2024 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example; + +import com.code_intelligence.jazzer.api.BugDetectors; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.concurrent.CountDownLatch; + +public class SsrfAllowUntilConfigured { + + private static final CountDownLatch fuzzTestStarted = new CountDownLatch(1); + + static { + // Simulate a background thread that starts before fuzz test configuration + Thread backgroundThread = + new Thread( + () -> { + try { + // Wait for fuzz test to start but before it configures SSRF + fuzzTestStarted.await(); + System.out.println("Background thread making early request..."); + + URL url = new URL("https://localhost:8080"); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(1000); + conn.setReadTimeout(1000); + conn.getResponseCode(); + conn.disconnect(); + } catch (Exception ignored) { + } + }); + backgroundThread.setDaemon(true); + backgroundThread.start(); + } + + public static void fuzzerTestOneInput(boolean ignored) throws Exception { + fuzzTestStarted.countDown(); + Thread.sleep(500); // Ensure background thread has time to run + + BugDetectors.allowNetworkConnections((host, port) -> host.equals("localhost")); + } +}