| name | soap-threadlocal-auth-bypass |
| description | How to identify and exploit SOAP/JAX-WS ThreadLocal authentication bypass vulnerabilities in Java web services. Use this skill whenever the user mentions SOAP endpoints, JAX-WS handlers, authentication bypass, ThreadLocal, WebLogic, JBoss, GlassFish, or any SOAP-based Java web service security testing. Also trigger when users are investigating SOAP header-based authentication, middleware authentication caching, or Java EE security handler chains. This skill covers reconnaissance, exploitation, and validation of ThreadLocal-based authentication bypass attacks. |
SOAP/JAX-WS ThreadLocal Authentication Bypass
This skill helps you identify and exploit authentication bypass vulnerabilities in SOAP-based Java web services where handlers cache authenticated subjects in ThreadLocal variables without clearing them when authentication headers are missing.
Understanding the Vulnerability
The Root Cause
Some Java middleware handlers store the authenticated Subject/Principal in a static ThreadLocal and only refresh it when a proprietary SOAP header arrives. Because application servers like WebLogic, JBoss, and GlassFish recycle worker threads, dropping that header causes the last privileged Subject processed by the thread to be silently reused.
Why this happens:
public boolean handleMessage(SOAPMessageContext ctx) {
if (!outbound) {
SOAPHeader hdr = ctx.getMessage().getSOAPPart().getEnvelope().getHeader();
SOAPHeaderElement e = findHeader(hdr, subjectName);
if (e != null) {
SubjectHolder.setSubject(unmarshal(e));
}
}
return true;
}
The handler only sets the Subject when the custom header is present, but never clears it when absent. This means the previous request's context survives on reused threads.
The Attack Pattern
- Trigger authenticated context - Send requests with valid authentication headers to populate the
ThreadLocal cache on worker threads
- Flood with header-less requests - Send well-formed SOAP bodies without the authentication header
- Catch the reuse - When a header-less request lands on a thread that previously executed a privileged action, it inherits that
Subject
- Execute privileged operations - Access protected endpoints like user managers, credential managers, or admin functions
Reconnaissance
Step 1: Map SOAP Endpoints
Start by enumerating the target to locate SOAP endpoints:
- Check reverse proxy and routing rules for hidden SOAP trees that may block
?wsdl but accept POSTs
- Look for common SOAP paths:
/services/*, /jaxws/*, /soap/*, /webservice/*
- Map these alongside your overall web methodology
Step 2: Analyze Application Artifacts
If you have access to EAR/WAR/EJB artifacts:
unzip *.ear
Key things to find:
- Handler class names (e.g.,
LoginHandler, AuthHandler)
- SOAP header QNames (e.g.,
mySubjectHeader, authToken)
- Backing EJB names (e.g.,
UserManager, CredentialManager)
Step 3: Recover WSDL
If metadata is missing:
- Brute-force likely
ServiceName?wsdl paths
- Temporarily relax lab proxies to capture WSDL
- Import recovered WSDL into tools like Burp Suite Wsdler to generate baseline envelopes
Step 4: Review Handler Sources
Look for ThreadLocal patterns in handler code:
- Search for
SubjectHolder.setSubject() or similar patterns
- Check if the
ThreadLocal is ever cleared when authentication headers are missing or malformed
- Look for static
ThreadLocal declarations that persist across requests
Exploitation
Phase 1: Establish Baseline
Send a valid request with the proprietary authentication header to learn:
- Normal response codes
- Error messages for invalid tokens
- Expected SOAP envelope structure
- Required namespaces and formatting
Phase 2: Craft Header-Less Requests
Resend the same SOAP body while omitting the authentication header:
- Keep the XML well-formed
- Respect all required namespaces
- Ensure the handler exits cleanly (don't trigger parsing errors)
Example request:
POST /ac-iasp-backend-jaxws/UserManager HTTP/1.1
Host: target
Content-Type: text/xml;charset=UTF-8
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:jax="http://jaxws.user.frontend.iasp.service.actividentity.com">
<soapenv:Header/>
<soapenv:Body>
<jax:findUserIds>
<arg0></arg0>
<arg1>spl*</arg1>
</jax:findUserIds>
</soapenv:Body>
</soapenv:Envelope>
Phase 3: Flood for Thread Reuse
Loop the header-less request with high parallelism:
- Use multiple concurrent connections
- Reuse Keep-Alive connections to maximize thread reuse probability
- Target large worker pools
- Monitor responses for privileged data or success indicators
Why parallelism matters: The more concurrent requests you send, the higher the probability that some will land on threads that previously executed privileged actions.
Phase 4: Execute Privileged Operations
Once you observe privileged responses, escalate:
- Access user management endpoints
- Create administrator accounts
- Import credentials
- Access credential managers
- Perform any operation that requires elevated privileges
Real-World Case Study: HID ActivID/IASP (HID-PSA-2025-002)
Vulnerability Details
Synacktiv discovered that JAX-WS LoginHandler in ActivID 8.6–8.7 sets SubjectHolder.subject when a mySubjectHeader SOAP header is present or when console/SSP traffic authenticates, but never clears it when the header is absent.
Exploitation Pattern
-
Trigger authenticated context on many threads:
- Spam
/ssp endpoint
- Log into
/aiconsole as admin in another browser tab
- Generate authenticated traffic to populate the thread pool
-
Flood header-less SOAP bodies:
- Target
/ac-iasp-backend-jaxws/UserManager or other EJB-backed JAX-WS endpoints
- Use high parallelism
- Each hit that reuses an "infected" thread executes with elevated
Subject
-
Repeat until privileged responses:
- Reuse Keep-Alive connections
- Maximize thread reuse probability
- Monitor for admin-level responses
Handler Flow
LoginHandlerChain.xml → LoginHandler.handleMessage() unmarshals mySubjectHeader and stores the Subject in SubjectHolder (a static ThreadLocal)
ProcessManager.triggerProcess() later injects SubjectHolder.getSubject() into business processes
- Missing headers leave stale identities intact
In-Field PoC
The advisory demonstrates a two-step SOAP abuse:
- First
getUsers to leak information
- Then
createUser + importCredential to plant a rogue admin when the privileged thread hits
Validating the Vulnerability
Development/Debug Environment
Attach JDWP to watch the ThreadLocal contents:
-agentlib:jdwp=transport=dt_socket,server=y,address=5005,suspend=n
Production Environment
Use JFR (Java Flight Recorder) or BTrace to dump SubjectHolder.getSubject() per request:
- Verify header-less reuse is occurring
- Confirm the
Subject persists across requests
- Document the thread reuse pattern
Common Indicators
Look for these signs that a SOAP endpoint may be vulnerable:
- SOAP handlers that only set authentication context when headers are present
- Static
ThreadLocal variables in handler chains
- No explicit clearing of authentication state
- Application servers with thread pooling (WebLogic, JBoss, GlassFish)
- Custom authentication headers that are optional or can be omitted
- SOAP endpoints that accept requests without authentication headers
Mitigation Recommendations
If you're securing a vulnerable system:
- Always clear ThreadLocal on request completion - Use
finally blocks or interceptor cleanup
- Validate authentication on every request - Don't rely on cached state
- Use request-scoped authentication - Bind authentication to the request lifecycle
- Clear ThreadLocal in handler chains - Explicitly reset state when headers are missing
- Implement proper thread-local cleanup - Use
ThreadLocal.remove() in finally blocks
References