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

install
source · Clone the upstream repo
git clone https://github.com/abelrguezr/hacktricks-skills
manifest: skills/pentesting-web/deserialization/java-signedobject-gated-deserialization/SKILL.MD
source content

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

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

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

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

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

3. Implement Strict Serialization Filters

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

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

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

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

Risk: Downgrade attacks or algorithm confusion

Pattern 2: Conditional Verification

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

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

Related Skills

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