# Java Signedobject Deserialization

> Identify and analyze Java SignedObject-gated deserialization vulnerabilities, including pre-auth reachability via error handlers. Use this skill whenever investigating Java deserialization issues, analyzing stack traces with SignedObject.getObject() calls, reviewing license/authentication endpoints, or assessing applications that use java.security.SignedObject for serialization. Trigger on mentions of SignedObject, Java deserialization, license validation, signature verification, or CVE-2025-10035 patterns.

- Skill: `abelrguezr/java-signedobject-deserialization` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add abelrguezr/java-signedobject-deserialization`
- Raw SKILL.md: https://api.skillmd.com/api/skills/abelrguezr/java-signedobject-deserialization/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/java-signedobject-deserialization

---


# Java SignedObject-Gated Deserialization Analysis

This skill helps identify and analyze "guarded" Java deserialization patterns built around `java.security.SignedObject` where seemingly unreachable sinks become exploitable via error-handling flows or signature verification bypasses.

## When to Use This Skill

Use this skill when:
- You encounter Java deserialization vulnerabilities with `SignedObject` wrappers
- Stack traces show `SignedObject.getObject()` in the call chain
- Investigating license/authentication endpoints that deserialize user input
- Analyzing applications using Apache Commons IO `ValidatingObjectInputStream` or similar wrappers
- Pre-auth access appears possible through error handlers or exception paths
- You need to assess signature verification bypass possibilities

## Vulnerability Pattern Overview

### The Guarded Deserialization Pattern

```java
// Outer deserialization restricted to SignedObject only
SignedObject so = (SignedObject) JavaSerializationUtilities.deserialize(
    payload, SignedObject.class, new Class[]{ byte[].class });

// Signature verification gate
if (!so.verify(pub, sig)) {
    throw new IOException("Unable to verify signature!");
}

// Inner object deserialization (potential gadget execution)
SignedContainer inner = (SignedObject) so.getObject();
return inner.getData();
```

**Key characteristics:**
- Outer deserializer blocks arbitrary top-level gadget classes
- Only `SignedObject` or `byte[]` accepted at the outer layer
- RCE primitive exists in the inner object from `getObject()`
- Signature verification gate must be bypassed for exploitation

## Exploitation Requirements

To achieve code execution, one of these conditions must be met:

1. **Private key compromise** - Obtain the matching private key used for signing
2. **Signing oracle** - Coerce a trusted service to sign attacker-controlled content
3. **Alternate reachable path** - Find a path that skips `verify()` or uses different validation
4. **Pre-auth token minting** - Error handlers that generate session tokens for unauthenticated users

## Pre-Auth Reachability Detection

### Error Handler Token Minting Pattern

Some applications inadvertently mint session-bound tokens in error handlers:

```
1. Unauthenticated request to error-prone endpoint
2. Error handler generates session token
3. Token attached to unauthenticated session
4. Token enables access to protected deserialization sink
```

### Proof-of-Reachability Probe

```http
GET /<app>/license/Unlicensed.xhtml/<junk>?javax.faces.ViewState=<junk>&GARequestAction=activate HTTP/1.1
Host: <target>
```

**Expected responses:**
- **Vulnerable:** 302 redirect with `bundle=<signed-data>` and `Set-Cookie: ASESSIONID=<token>`
- **Patched:** Redirect without bundle parameter or token generation

### Stack Trace Indicators

Look for these patterns in logs/stack traces:

```
java.io.ObjectInputStream.readObject
java.security.SignedObject.getObject
<app>.license.*.BundleWorker.verify
<app>.license.*.BundleWorker.unbundle
<app>.license.*.LicenseController.getResponse
<app>.license.*.LicenseAPI.getResponse
<app>.ui.admin.servlet.LicenseResponseServlet.doPost
```

## Analysis Workflow

### Step 1: Identify the Deserialization Sink

1. Search codebase for `SignedObject` usage
2. Locate `getObject()` calls
3. Check if signature verification precedes the call
4. Identify what classes are allowed through the outer filter

### Step 2: Assess Signature Verification

1. Determine the public key source (hardcoded, configuration, certificate)
2. Check for key version/algorithm selection logic
3. Look for conditional verification paths (e.g., `isServer()` checks)
4. Identify if verification can be bypassed or skipped

### Step 3: Map Pre-Auth Reachability

1. Find all endpoints that process the serialized payload
2. Check authentication requirements for each endpoint
3. Trace error handlers and exception paths
4. Look for token/session generation in error flows
5. Test if tokens can be obtained without authentication

### Step 4: Evaluate Exploitation Feasibility

1. Can the private key be obtained?
2. Is there a signing oracle available?
3. Can signature verification be bypassed?
4. Are there alternate deserialization paths?
5. What gadget chains are available in the classpath?

## Hardening Recommendations

### 1. Maintain Signature Verification

```java
// ALWAYS verify before getObject()
if (!so.verify(pub, sig)) {
    throw new IOException("Unable to verify signature!");
}
SignedContainer inner = (SignedContainer) so.getObject();
```

### 2. Apply Inner Stream Filtering

Replace direct `getObject()` calls with hardened wrappers:

```java
// Use ValidatingObjectInputStream or ObjectInputFilter for inner stream
SignedContainer inner = (SignedContainer) JavaSerializationUtilities.deserializeUntrustedSignedObject(
    so, SignedContainer.class, new Class[]{ byte[].class }
);
```

### 3. Implement Strict Serialization Filters

```java
ObjectInputFilter filter = info -> {
    Class<?> c = info.serialClass();
    if (c == null) return ObjectInputFilter.Status.UNDECIDED;
    
    // Outer layer: only SignedObject and byte[]
    if (c == java.security.SignedObject.class || c == byte[].class) {
        return ObjectInputFilter.Status.ALLOWED;
    }
    
    // Inner layer: strict DTO allow-list
    if (c == SignedContainer.class || c == byte[].class) {
        return ObjectInputFilter.Status.ALLOWED;
    }
    
    return ObjectInputFilter.Status.REJECTED;
};
ObjectInputFilter.Config.setSerialFilter(filter);
```

### 4. Remove Error Handler Token Generation

```java
// DON'T generate tokens in error handlers for unauthenticated users
public void handleError(HttpServletRequest req, HttpServletResponse resp) {
    // Bad: generates token for unauthenticated session
    // String token = SessionUtilities.generateLicenseRequestToken(session);
    
    // Good: log error and redirect without token generation
    log.error("License error", exception);
    resp.sendRedirect("/error/license");
}
```

### 5. Treat Error Paths as Attack Surface

- Audit all exception handlers
- Remove privileged operations from error flows
- Don't generate security tokens in error handlers
- Log and monitor error handler invocations

## Detection Signatures

### Log/Stack Trace Patterns

```regex
java\.security\.SignedObject\.getObject
java\.io\.ObjectInputStream\.readObject
.*\.BundleWorker\.verify
.*\.LicenseController\.getResponse
```

### Network Indicators

- POST requests to license/authentication endpoints with `bundle` parameters
- 302 redirects containing `bundle=` with base64-encoded data
- Session cookies set on error page responses

## Common Vulnerable Patterns

### Pattern 1: Key Version Controls Algorithm

```java
if ("2".equals(keyCfg.getVersion())) {
    sigAlg = "SHA512withRSA";
} else {
    sigAlg = "SHA1withDSA";  // Weaker algorithm
}
```

**Risk:** Downgrade attacks or algorithm confusion

### Pattern 2: Conditional Verification

```java
if (keyCfg.isServer()) {
    // Hardened path with additional validation
    return deserializeUntrustedSignedObject(...);
} else {
    // Client path with basic verification only
    if (!so.verify(pub, sig)) throw exception;
    return so.getObject();  // No inner filtering
}
```

**Risk:** Client path lacks inner stream protection

### Pattern 3: Error Handler Token Generation

```java
public void service(HttpServletRequest req, HttpServletResponse resp) {
    try {
        // Normal flow
    } catch (Exception e) {
        // Bad: generates token in error handler
        String token = generateLicenseRequestToken(session);
        resp.sendRedirect("/vendor?bundle=" + token);
    }
}
```

**Risk:** Pre-auth access to protected endpoints

## Testing Checklist

- [ ] Identify all `SignedObject` deserialization points
- [ ] Verify signature checks precede `getObject()` calls
- [ ] Check for inner stream filtering on deserialized objects
- [ ] Map authentication requirements for all related endpoints
- [ ] Test error handlers for token/session generation
- [ ] Attempt to obtain tokens without authentication
- [ ] Verify private key cannot be extracted or obtained
- [ ] Check for signing oracle possibilities
- [ ] Review serialization filter configurations
- [ ] Test with malformed/invalid inputs to trigger error paths

## References

- [watchTowr Labs – CVE-2025-10035 Analysis](https://labs.watchtowr.com/is-this-bad-this-feels-bad-goanywhere-cve-2025-10035/)
- [Fortra Advisory FI-2025-012](https://www.fortra.com/security/advisories/product-security/fi-2025-012)
- [JEP 290 – Serialization Filtering](https://openjdk.org/jeps/290)
- [Apache Commons IO – ValidatingObjectInputStream](https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/input/ValidatingObjectInputStream.html)

## Related Skills

- Java deserialization gadget chain analysis
- Session token security assessment
- Error handler security review
- Signature verification bypass techniques

