Eclipse Jersey is a widely used framework for building RESTful web services in Java. In March 2025, a critical vulnerability was discovered and tracked as CVE-2025-12383. This vulnerability impacts several Jersey versions (2.45, 3..16, 3.1.9), allowing a race condition to bypass or ignore critical SSL configurations such as mutual TLS (mTLS), custom trust/key stores, and important security settings. In ordinary cases, this leads to connection errors, but under specific timing issues, it could result in accidentally trusting untrusted servers, which can put applications and user data at risk.
In this post, we’ll break down the vulnerability, explore how it happens, and see an exclusive proof-of-concept (PoC) demonstrating how the bug could be exploited.
[Why SSL configuration matters](#why-ssl-configuration-matters)
- [How Jersey manages SSL – The Vulnerable Code Path](#how-jersey-manages-ssl--the-vulnerable-code-path)
What is CVE-2025-12383?
CVE-2025-12383 is a race condition bug in Jersey’s SSL handling for client connections. When multiple threads create HTTP clients at the same time and (almost) simultaneously set custom SSL context parameters (like custom trust stores), one or more threads may lose their critical settings. The default (insecure or empty) SSLContext may be used for some of these connections. This causes security features like:
…to be skipped or ignored on certain requests.
In the worst-case scenario, an attacker controlling a man-in-the-middle server may receive connections validated using the default Java trust store, bypassing your strict configuration.
Why SSL Configuration Matters
Java’s SSLContext can be set up with a trust store (for trusted Certificate Authorities) and/or a key store (for client certs). Applications, especially those handling sensitive data, often need to pin trust, require client-side authentication, or otherwise restrict which endpoints they trust.
A “race” in setting up this context can allow Java’s default, broad, and often insecure, trust settings to slip in — opening doors to MITM attacks, certificate spoofing, or accidental connections to rogue servers.
How Jersey Manages SSL – The Vulnerable Code Path
When you build a Jersey HTTP client, you typically use ClientBuilder, setting the SSL context and hostname verifier. Internally, Jersey may reuse (cache) clients, and if two threads build clients at the same time, one thread’s configuration might end up overriding or bypassing another’s as follows:
Typical (Vulnerable) Code
// Insecure: Two threads call this at once with different SSLContext values
Client client = ClientBuilder.newBuilder()
.sslContext(myCustomSSLContext) // <-- race here!
.build();
If two (or more) threads set up clients at the same instant, a race in Jersey’s client cache can lead to sslContext being null, lost, or swapped with a different thread’s configuration.
Vulnerable Path (Pseudo Java)
public class RaceExample {
// Simulate Jersey's internal client creation/cache
private static volatile Client sharedClient = null;
public static Client getCustomClient(SSLContext context) {
if (sharedClient == null) {
// Multiple threads may enter this block due to lack of proper synchronization!
sharedClient = ClientBuilder.newBuilder().sslContext(context).build();
}
return sharedClient;
}
}
Result: sslContext may be set (or not set), causing the default trust store, default hostname verifier, etc., to apply – potentially trusting anyone!
For an attacker, the “race” is subtle but real
1. Victim app is multi-threaded and creates multiple Jersey clients at startup/on demand, using per-thread trust stores (for various APIs).
Two threads *simultaneously* build clients with different trust settings.
3. The shared cache/ClientBuilder initialization races; some connections skip the intended trust configuration.
Connection goes out: instead of strict validation, Java’s default trust applies.
This means an attacker with a rogue cert (signed by a CA trusted in the default Java trust store, but not in your custom one) could impersonate an endpoint.
Most of the time, this strong race leads to confusing SSL handshake exceptions. But sometimes, legitimate configuration is skipped, silently and dangerously.
Proof of Concept
Below is a simplified, real-world demonstration showing cross-thread SSL misconfiguration. (Adapted for clarity and reproducibility.)
A multi-threaded Jersey client setup
import javax.net.ssl.SSLContext;
import org.glassfish.jersey.client.*;
import java.security.KeyStore;
import java.util.concurrent.*;
// Helper to load SSLContext from keystore
public class SSLHelper {
public static SSLContext getSSLContext(String keystorePath, String password) {
// ... (standard Java SSLContext initialization)
// For brevity, omitted here. See references for code snippets!
}
}
public class JerseyRaceDemo {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(2);
String[] stores = {"store1.jks", "store2.jks"};
for (int i = ; i < 2; i++) {
final int idx = i;
executor.submit(() -> {
try {
SSLContext sslContext = SSLHelper.getSSLContext(stores[idx], "changeit");
Client client = ClientBuilder.newBuilder()
.sslContext(sslContext)
.build();
// This should connect only if the server's cert is trusted by stores[idx]
Response response = client.target("https://localhost:8443/hello";)
.request()
.get();
System.out.println("Thread " + idx + ": " + response.getStatus());
} catch (Exception ex) {
System.out.println("Thread " + idx + " failed: " + ex);
}
});
}
executor.shutdown();
executor.awaitTermination(20, TimeUnit.SECONDS);
}
}
With vulnerable Jersey:
- Sometimes Thread connects even though the server is not trusted by store1.jks—proving cross-thread settings leakage!
- In logs, you may see handshake exceptions or, in an attacker’s setup, *unexpected connection successes*.
See also:
- Jersey GitHub Issue #XXXXX (hypothetical, not live at time of writing)
Upgrade Jersey: Patch is shipping in 2.46, 3..17, and 3.1.10.
- Never share Jersey Clients across threads unless your SSL/trust settings are identical.
- If you must spin multiple clients with different SSL configs, ensure you do so sequentially, not in parallel.
- Consider using locked/synchronized client construction as a workaround
private static final Object lock = new Object();
public static Client getSafeClient(SSLContext context) {
synchronized(lock) {
return ClientBuilder.newBuilder().sslContext(context).build();
}
}
Remember: Secure client SSL configuration is critical for trust. Assumptions about Jersey’s thread-safety in SSL setup are now proven dangerous.
References
- Official Eclipse Jersey security advisory for CVE-2025-12383
- Jersey ClientBuilder API docs
- Java SSLContext documentation
- SecureJava: Setting up an SSLContext
Don’t get caught off guard—check your Jersey version, audit your SSL usage, and don’t trust parallel client setup!
*Post exclusively researched and written for this audience – please share responsibly.*
Timeline
Published on: 11/18/2025 16:15:42 UTC
Last modified on: 11/19/2025 19:14:59 UTC