Java SignedObject-gated Deserialization y Pre-auth Reachability a través de rutas de error
Reading time: 7 minutes
tip
Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
Esta página documenta un patrón común de deserialización Java "protegido" construido alrededor de java.security.SignedObject y cómo sinks aparentemente inaccesibles pueden volverse alcanzables pre-auth mediante flujos de manejo de errores. La técnica se observó en Fortra GoAnywhere MFT (CVE-2025-10035) pero es aplicable a diseños similares.
Modelo de amenaza
- El atacante puede alcanzar un endpoint HTTP que eventualmente procesa un byte[] suministrado por el atacante destinado a ser un SignedObject serializado.
- El código usa un wrapper de validación (p. ej., Apache Commons IO ValidatingObjectInputStream o un adaptador personalizado) para restringir el tipo más externo a SignedObject (o byte[]).
- El objeto interno devuelto por SignedObject.getObject() es donde las gadget chains pueden activarse (p. ej., CommonsBeanutils1), pero solo después de una puerta de verificación de firma.
Patrón vulnerable típico
Un ejemplo simplificado basado en com.linoma.license.gen2.BundleWorker.verify:
private static byte[] verify(byte[] payload, KeyConfig keyCfg) throws Exception {
String sigAlg = "SHA1withDSA";
if ("2".equals(keyCfg.getVersion())) {
sigAlg = "SHA512withRSA"; // key version controls algorithm
}
PublicKey pub = getPublicKey(keyCfg);
Signature sig = Signature.getInstance(sigAlg);
// 1) Outer, "guarded" deserialization restricted to SignedObject
SignedObject so = (SignedObject) JavaSerializationUtilities.deserialize(
payload, SignedObject.class, new Class[]{ byte[].class });
if (keyCfg.isServer()) {
// Hardened server path
return ((SignedContainer) JavaSerializationUtilities.deserializeUntrustedSignedObject(
so, SignedContainer.class, new Class[]{ byte[].class }
)).getData();
} else {
// 2) Signature check using a baked-in public key
if (!so.verify(pub, sig)) {
throw new IOException("Unable to verify signature!");
}
// 3) Inner object deserialization (potential gadget execution)
SignedContainer inner = (SignedContainer) so.getObject();
return inner.getData();
}
}
Observaciones clave:
- El deserializador validador en (1) bloquea clases de gadget arbitrarias a nivel superior; solo se acepta SignedObject (o raw byte[]).
- El primitivo de RCE estaría en el objeto interno materializado por SignedObject.getObject() en (3).
- Una comprobación de firma en (2) exige que el SignedObject se verifique usando verify() contra una clave pública incluida en el producto. A menos que el atacante pueda producir una firma válida, el gadget interno nunca se deserializa.
Consideraciones de explotación
Para lograr ejecución de código, un atacante debe entregar un SignedObject correctamente firmado que envuelva una cadena de gadgets maliciosa como su objeto interno. Esto generalmente requiere una de las siguientes opciones:
- Compromiso de la clave privada: obtener la clave privada correspondiente usada por el producto para firmar/verificar objetos de licencia.
- Signing oracle: coaccionar al vendor o a un servicio de firma de confianza para que firme contenido serializado controlado por el atacante (p. ej., si un license server firma un objeto arbitrario incrustado a partir de la entrada del cliente).
- Ruta alternativa accesible: encontrar una ruta del lado servidor que deserialice el objeto interno sin aplicar verify(), o que omita las comprobaciones de firma bajo un modo específico.
Si no se cumple alguna de estas, la verificación de la firma impedirá la explotación a pesar de la presencia de un deserialization sink.
Alcance pre-auth vía flujos de manejo de errores
Incluso cuando un endpoint de deserialización parece requerir autenticación o un token ligado a sesión, el código de manejo de errores puede crear inadvertidamente y adjuntar el token a una sesión no autenticada.
Cadena de alcance de ejemplo (GoAnywhere MFT):
- Servlet objetivo: /goanywhere/lic/accept/
requiere un token de solicitud de licencia ligado a la sesión. - Ruta de error: acceder a /goanywhere/license/Unlicensed.xhtml con datos basura al final y estado JSF inválido dispara AdminErrorHandlerServlet, que hace:
- SessionUtilities.generateLicenseRequestToken(session)
- Redirige al license server del proveedor con una signed license request en bundle=<...>
- El bundle puede ser descifrado offline (claves hard-coded) para recuperar el GUID. Mantén la misma cookie de sesión y haz POST a /goanywhere/lic/accept/
con bytes del bundle controlados por el atacante, alcanzando el SignedObject sink pre-auth.
Prueba de alcanzabilidad (sin impacto) de sondeo:
GET /goanywhere/license/Unlicensed.xhtml/x?javax.faces.ViewState=x&GARequestAction=activate HTTP/1.1
Host: <target>
- Sin parchear: cabecera 302 Location a https://my.goanywhere.com/lic/request?bundle=... y Set-Cookie: ASESSIONID=...
- Parcheado: redirección sin bundle (sin generación de token).
Blue-team detection
Indicadores en stack traces/logs sugieren fuertemente intentos de alcanzar un SignedObject-gated sink:
java.io.ObjectInputStream.readObject
java.security.SignedObject.getObject
com.linoma.license.gen2.BundleWorker.verify
com.linoma.license.gen2.BundleWorker.unbundle
com.linoma.license.gen2.LicenseController.getResponse
com.linoma.license.gen2.LicenseAPI.getResponse
com.linoma.ga.ui.admin.servlet.LicenseResponseServlet.doPost
Guía de endurecimiento
- Mantener la verificación de la firma antes de cualquier llamada a getObject() y asegurarse de que la verificación use la clave pública/algoritmo previsto.
- Reemplazar las llamadas directas a SignedObject.getObject() con un wrapper endurecido que vuelva a aplicar filtrado al stream interno (p. ej., deserializeUntrustedSignedObject usando ValidatingObjectInputStream/ObjectInputFilter con listas de permitidos (allow-lists)).
- Eliminar flujos de manejo de errores que emitan tokens ligados a sesión para usuarios no autenticados. Tratar las rutas de error como superficie de ataque.
- Preferir filtros de serialización de Java (JEP 290) con listas de permitidos estrictas para la deserialización tanto externa como interna. Ejemplo:
ObjectInputFilter filter = info -> {
Class<?> c = info.serialClass();
if (c == null) return ObjectInputFilter.Status.UNDECIDED;
if (c == java.security.SignedObject.class || c == byte[].class) return ObjectInputFilter.Status.ALLOWED;
return ObjectInputFilter.Status.REJECTED; // outer layer
};
ObjectInputFilter.Config.setSerialFilter(filter);
// For the inner object, apply a separate strict DTO allow-list
Resumen de la cadena de ataque de ejemplo (CVE-2025-10035)
- Pre-auth token minting via error handler:
GET /goanywhere/license/Unlicensed.xhtml/watchTowr?javax.faces.ViewState=watchTowr&GARequestAction=activate
Recibir 302 con bundle=... y ASESSIONID=...; descifrar bundle offline para recuperar el GUID.
- Alcanzar el sink pre-auth con la misma cookie:
POST /goanywhere/lic/accept/<GUID> HTTP/1.1
Cookie: ASESSIONID=<value>
Content-Type: application/x-www-form-urlencoded
bundle=<attacker-controlled-bytes>
- RCE requiere un SignedObject correctamente firmado que envuelva una gadget chain. Los investigadores no pudieron eludir la verificación de firmas; la explotación depende del acceso a una clave privada coincidente o a un signing oracle.
Versiones corregidas y cambios de comportamiento
- GoAnywhere MFT 7.8.4 y Sustain Release 7.6.3:
- Endurecer la deserialización interna reemplazando SignedObject.getObject() con un wrapper (deserializeUntrustedSignedObject).
- Eliminar la generación de tokens del error-handler, cerrando la pre-auth reachability.
Notas sobre JSF/ViewState
El truco de reachability aprovecha una página JSF (.xhtml) y un javax.faces.ViewState inválido para redirigir hacia un error handler privilegiado. Aunque no es un problema de deserialización de JSF, es un patrón recurrente pre-auth: entrar en error handlers que realizan acciones privilegiadas y establecen atributos de sesión relevantes para la seguridad.
References
- watchTowr Labs – Is This Bad? This Feels Bad — GoAnywhere CVE-2025-10035
- Fortra advisory FI-2025-012 – Deserialization Vulnerability in GoAnywhere MFT's License Servlet
tip
Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.