# Soap Threadlocal Auth Bypass

> 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.

- Skill: `abelrguezr/soap-threadlocal-auth-bypass` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add abelrguezr/soap-threadlocal-auth-bypass`
- Raw SKILL.md: https://api.skillmd.com/api/skills/abelrguezr/soap-threadlocal-auth-bypass/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: abelrguezr (https://skillmd.com/u/abelrguezr)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/abelrguezr/soap-threadlocal-auth-bypass

---


# 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:**

```java
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));
        }
        // BUG: Subject is never cleared when header is missing!
    }
    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

1. **Trigger authenticated context** - Send requests with valid authentication headers to populate the `ThreadLocal` cache on worker threads
2. **Flood with header-less requests** - Send well-formed SOAP bodies without the authentication header
3. **Catch the reuse** - When a header-less request lands on a thread that previously executed a privileged action, it inherits that `Subject`
4. **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:

```bash
# Unpack and inspect
unzip *.ear
# Look for:
# - application.xml (service definitions)
# - web.xml (handler chain configurations)
# - @WebService annotations in Java sources
# - LoginHandlerChain.xml or similar handler configs
```

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:**

```http
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

1. **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

2. **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`

3. **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:

1. First `getUsers` to leak information
2. 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:

```bash
# Start with JDWP
-agentlib:jdwp=transport=dt_socket,server=y,address=5005,suspend=n

# Monitor ThreadLocal before and after each call
# Confirm that unauthenticated requests inherit prior administrator Subject
```

### 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:

1. **Always clear ThreadLocal on request completion** - Use `finally` blocks or interceptor cleanup
2. **Validate authentication on every request** - Don't rely on cached state
3. **Use request-scoped authentication** - Bind authentication to the request lifecycle
4. **Clear ThreadLocal in handler chains** - Explicitly reset state when headers are missing
5. **Implement proper thread-local cleanup** - Use `ThreadLocal.remove()` in finally blocks

## References

- [Synacktiv – ActivID authentication bypass (HID-PSA-2025-002)](https://www.synacktiv.com/en/advisories/activid-authentication-bypass.html)
- [HID Global – Product Security Advisory HID-PSA-2025-002 SOAP-API Authentication Bypass](https://www.hidglobal.com/sites/default/files/documentlibrary/HID-PSA-2025-02%20SOAP_API_a.pdf)
- [Synacktiv – ActivID administrator account takeover: the story behind HID-PSA-2025-002](https://www.synacktiv.com/publications/activid-administrator-account-takeover-the-story-behind-hid-psa-2025-002.html)
- [PortSwigger – Wsdler (WSDL parser) extension](https://portswigger.net/bappstore/594a49bb233748f2bc80a9eb18a2e08f)

