Java SignedObject-gated Deserialization and Pre-auth Reachability via Error Paths

Reading time: 6 minutes

tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks

Ta strona dokumentuje powszechny wzorzec "guarded" Java deserialization zbudowany wokół java.security.SignedObject oraz jak pozornie nieosiągalne sinks mogą stać się dostępne pre-auth przez ścieżki obsługi błędów. Technika została zaobserwowana w Fortra GoAnywhere MFT (CVE-2025-10035), ale ma zastosowanie do podobnych projektów.

Threat model

  • Atakujący może dotrzeć do endpointu HTTP, który ostatecznie przetwarza dostarczone przez atakującego byte[] przeznaczone do bycia serializowanym SignedObject.
  • Kod używa wrappera walidującego (np. Apache Commons IO ValidatingObjectInputStream lub custom adapter) aby ograniczyć zewnętrzny typ do SignedObject (lub byte[]).
  • Obiekt wewnętrzny zwracany przez SignedObject.getObject() to miejsce, gdzie gadget chains mogą zostać uruchomione (np. CommonsBeanutils1), ale tylko po bramce weryfikacji podpisu.

Typical vulnerable pattern

A simplified example based on com.linoma.license.gen2.BundleWorker.verify:

java
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();
}
}

Key observations:

  • Walidujący deserializer w (1) blokuje dowolne top-level gadget classes; akceptowane jest tylko SignedObject (lub raw byte[]).
  • Primitiv RCE znajdowałby się w obiekcie wewnętrznym zmaterializowanym przez SignedObject.getObject() w (3).
  • Bramka podpisu w (2) wymusza, że SignedObject musi zostać zweryfikowany przy użyciu klucza publicznego dostarczonego przez producenta. Jeśli atakujący nie potrafi wygenerować prawidłowego podpisu, wewnętrzny gadget nigdy się nie deserializuje.

Rozważania dotyczące eksploatacji

Aby osiągnąć wykonanie kodu, atakujący musi dostarczyć prawidłowo podpisany SignedObject, który zawiera złośliwy gadget chain jako obiekt wewnętrzny. Zazwyczaj wymaga to jednego z poniższych:

  • Private key compromise: uzyskanie odpowiadającego klucza prywatnego używanego przez produkt do sign/verify obiektów licencji.
  • Signing oracle: zmuszenie dostawcy lub zaufanej usługi podpisującej do podpisania kontrolowanej przez atakującego serializowanej treści (np. jeśli license server podpisuje osadzony dowolny obiekt z wejścia klienta).
  • Alternate reachable path: znalezienie ścieżki po stronie serwera, która deserializuje obiekt wewnętrzny bez egzekwowania verify(), albo która pomija sprawdzenia podpisu w określonym trybie.

W braku jednego z powyższych, weryfikacja podpisu uniemożliwi eksploatację pomimo istnienia deserialization sink.

Pre-auth reachability via error-handling flows

Nawet gdy deserialization endpoint wydaje się wymagać uwierzytelnienia lub tokenu związanego z sesją, kod obsługi błędów może przypadkowo wygenerować i dołączyć token do nieuwierzytelnionej sesji.

Example reachability chain (GoAnywhere MFT):

  • Target servlet: /goanywhere/lic/accept/ requires a session-bound license request token.
  • Error path: hitting /goanywhere/license/Unlicensed.xhtml with trailing junk and invalid JSF state triggers AdminErrorHandlerServlet, which does:
  • SessionUtilities.generateLicenseRequestToken(session)
  • Redirects to vendor license server with a signed license request in bundle=<...>
  • The bundle can be decrypted offline (hard-coded keys) to recover the GUID. Keep the same session cookie and POST to /goanywhere/lic/accept/ with attacker-controlled bundle bytes, reaching the SignedObject sink pre-auth.

Proof-of-reachability (impact-less) probe:

http
GET /goanywhere/license/Unlicensed.xhtml/x?javax.faces.ViewState=x&GARequestAction=activate HTTP/1.1
Host: <target>
  • Niezałatany: 302 Location header to https://my.goanywhere.com/lic/request?bundle=... and Set-Cookie: ASESSIONID=...
  • Załatany: przekierowanie bez bundle (brak generowania tokena).

Wykrywanie (Blue-team)

Wskaźniki w stack traces/logs wyraźnie sugerują próby trafienia w 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

Wskazówki dotyczące utwardzania

  • Utrzymuj weryfikację podpisu przed każdym wywołaniem getObject() i upewnij się, że weryfikacja używa zamierzonego klucza publicznego/algorytmu.
  • Zastąp bezpośrednie wywołania SignedObject.getObject() bezpieczną nakładką, która ponownie stosuje filtrowanie do wewnętrznego strumienia (np. deserializeUntrustedSignedObject używając ValidatingObjectInputStream/ObjectInputFilter allow-lists).
  • Usuń ścieżki obsługi błędów, które wydają tokeny powiązane z sesją dla użytkowników nieuwierzytelnionych. Traktuj ścieżki błędów jako powierzchnię ataku.
  • Preferuj Java serialization filters (JEP 290) ze ścisłymi allow-lists zarówno dla zewnętrznej, jak i wewnętrznej deserializacji. Przykład:
java
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

Przykładowe podsumowanie łańcucha ataku (CVE-2025-10035)

  1. Pre-auth token minting via error handler:
http
GET /goanywhere/license/Unlicensed.xhtml/watchTowr?javax.faces.ViewState=watchTowr&GARequestAction=activate

Otrzymaj 302 z bundle=... i ASESSIONID=...; odszyfruj bundle offline, aby odzyskać GUID.

  1. Dotrzyj do sink pre-auth z tym samym cookie:
http
POST /goanywhere/lic/accept/<GUID> HTTP/1.1
Cookie: ASESSIONID=<value>
Content-Type: application/x-www-form-urlencoded

bundle=<attacker-controlled-bytes>
  1. RCE wymaga poprawnie podpisanego SignedObject zawierającego gadget chain. Badacze nie byli w stanie obejść weryfikacji podpisu; eksploatacja zależy od dostępu do pasującego klucza prywatnego lub signing oracle.

Naprawione wersje i zmiany w zachowaniu

  • GoAnywhere MFT 7.8.4 and Sustain Release 7.6.3:
  • Wzmocniono wewnętrzną deserializację przez zastąpienie SignedObject.getObject() wrapperem (deserializeUntrustedSignedObject).
  • Usunięto generowanie tokenów obsługi błędów, zamykając pre-auth reachability.

Uwagi dotyczące JSF/ViewState

Sztuczka z reachability wykorzystuje stronę JSF (.xhtml) i nieprawidłowy javax.faces.ViewState, aby przekierować do uprzywilejowanego error handlera. Chociaż nie jest to problem deserializacji JSF, to powtarzający się wzorzec pre-auth: wejść do error handlerów, które wykonują uprzywilejowane akcje i ustawiają atrybuty sesji istotne dla bezpieczeństwa.

References

tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks