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

Reading time: 7 minutes

tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks

Αυτή η σελίδα τεκμηριώνει ένα κοινό «προφυλαγμένο» πρότυπο Java deserialization γύρω από το java.security.SignedObject και το πώς φαινομενικά απρόσιτα sinks μπορούν να γίνουν προσβάσιμα πριν από την αυθεντικοποίηση μέσω ροών χειρισμού σφαλμάτων. Η τεχνική παρατηρήθηκε στο Fortra GoAnywhere MFT (CVE-2025-10035) αλλά είναι εφαρμόσιμη σε παρόμοιες σχεδιάσεις.

Υπόδειγμα απειλής

  • Ο attacker μπορεί να φτάσει σε ένα HTTP endpoint που τελικά επεξεργάζεται ένα attacker-supplied byte[] προοριζόμενο να είναι ένα σειριοποιημένο SignedObject.
  • Ο κώδικας χρησιμοποιεί ένα validating wrapper (π.χ. Apache Commons IO ValidatingObjectInputStream ή έναν custom adapter) για να περιορίσει τον εξωτερικό τύπο σε SignedObject (ή byte[]).
  • Το εσωτερικό αντικείμενο που επιστρέφεται από το SignedObject.getObject() είναι όπου οι gadget chains μπορούν να ενεργοποιηθούν (π.χ., CommonsBeanutils1), αλλά μόνο μετά από πύλη επαλήθευσης υπογραφής.

Τυπικό ευάλωτο μοτίβο

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

Κύριες παρατηρήσεις:

  • Ο validating deserializer στο (1) μπλοκάρει αυθαίρετες top-level gadget classes· γίνεται αποδεκτό μόνο το SignedObject (ή raw byte[]).
  • Το RCE primitive θα βρίσκεται στο εσωτερικό αντικείμενο που υλοποιείται από SignedObject.getObject() στο (3).
  • Ένας signature gate στο (2) επιβάλλει ότι το SignedObject πρέπει να επαληθεύεται έναντι ενός public key ενσωματωμένου στο προϊόν. Εκτός κι αν ο επιτιθέμενος μπορεί να παραγάγει έγκυρη υπογραφή, το inner gadget δεν απο-σειριοποιείται ποτέ.

Παρατηρήσεις εκμετάλλευσης

Για να επιτευχθεί εκτέλεση κώδικα, ο επιτιθέμενος πρέπει να παραδώσει ένα σωστά υπογεγραμμένο SignedObject που τυλίγει μια κακόβουλη gadget chain ως το εσωτερικό αντικείμενο. Αυτό γενικά απαιτεί ένα από τα ακόλουθα:

  • Private key compromise: απόκτηση του αντίστοιχου private key που χρησιμοποιεί το προϊόν για να υπογράφει/επαληθεύει license objects.
  • Signing oracle: εξαναγκασμός του vendor ή μιας αξιόπιστης signing υπηρεσίας να υπογράψει serialized περιεχόμενο ελεγχόμενο από τον επιτιθέμενο (π.χ. αν ένας license server υπογράφει ένα ενσωματωμένο arbitrary object από input του client).
  • Alternate reachable path: εύρεση server-side διαδρομής που απο-σειριοποιεί το inner object χωρίς να επιβάλει verify(), ή που παρακάμπτει τους ελέγχους υπογραφής υπό μια συγκεκριμένη λειτουργία.

Αν δεν ισχύει κάποια από αυτές, η επαλήθευση της υπογραφής θα αποτρέψει την εκμετάλλευση παρά την ύπαρξη ενός deserialization sink.

Πρόσβαση προ-αυθεντικοποίησης μέσω ροών χειρισμού σφαλμάτων

Ακόμα κι όταν ένα deserialization endpoint φαίνεται να απαιτεί authentication ή ένα session-bound token, ο error-handling κώδικας μπορεί κατά λάθος να δημιουργήσει και να επισυνάψει το token σε μια μη αυθεντικοποιημένη συνεδρία.

Παράδειγμα αλυσίδας προσβασιμότητας (GoAnywhere MFT):

  • Target servlet: /goanywhere/lic/accept/ απαιτεί ένα session-bound license request token.
  • Error path: πρόσβαση στο /goanywhere/license/Unlicensed.xhtml με trailing junk και invalid JSF state προκαλεί το AdminErrorHandlerServlet, το οποίο κάνει:
  • SessionUtilities.generateLicenseRequestToken(session)
  • Redirects to vendor license server with a signed license request in bundle=<...>
  • Το bundle μπορεί να αποκρυπτογραφηθεί offline (hard-coded keys) για να ανακτηθεί το GUID. Κρατήστε το ίδιο session cookie και κάντε POST στο /goanywhere/lic/accept/ με attacker-controlled bundle bytes, φτάνοντας στο SignedObject sink προ-αυθεντικοποίησης.

Δοκιμή απόδειξης προσβασιμότητας (χωρίς επιπτώσεις):

http
GET /goanywhere/license/Unlicensed.xhtml/x?javax.faces.ViewState=x&GARequestAction=activate HTTP/1.1
Host: <target>
  • Μη επιδιορθωμένο: 302 Location header to https://my.goanywhere.com/lic/request?bundle=... and Set-Cookie: ASESSIONID=...
  • Επιδιορθωμένο: redirect without bundle (no token generation).

Ανίχνευση Blue-team

Δείκτες σε stack traces/logs υποδηλώνουν έντονα προσπάθειες να στοχεύσουν ένα 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

Οδηγίες ενίσχυσης

  • Διατηρείτε την επαλήθευση υπογραφής πριν από οποιαδήποτε κλήση getObject() και βεβαιωθείτε ότι η επαλήθευση χρησιμοποιεί το προβλεπόμενο δημόσιο κλειδί/αλγόριθμο.
  • Αντικαταστήστε άμεσες κλήσεις SignedObject.getObject() με ένα σκληρυμένο wrapper που εφαρμόζει ξανά φιλτράρισμα στο εσωτερικό stream (π.χ., deserializeUntrustedSignedObject χρησιμοποιώντας allow-lists του ValidatingObjectInputStream/ObjectInputFilter).
  • Αφαιρέστε ροές χειρισμού σφαλμάτων που εκδίδουν session-bound tokens για μη πιστοποιημένους χρήστες. Αντιμετωπίστε τα μονοπάτια σφάλματος ως επιφάνεια επίθεσης.
  • Προτιμήστε Java serialization filters (JEP 290) με αυστηρές allow-lists τόσο για την εξωτερική όσο και για την εσωτερική αποσειριοποίηση. Παράδειγμα:
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

Παράδειγμα ανακεφαλαίωσης αλυσίδας επίθεσης (CVE-2025-10035)

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

Λάβετε 302 με bundle=... και ASESSIONID=...; αποκρυπτογραφήστε το bundle εκτός σύνδεσης για να ανακτήσετε το GUID.

  1. Φτάστε στο sink pre-auth με το ίδιο 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 απαιτεί ένα σωστά υπογεγραμμένο SignedObject που περιβάλλει ένα gadget chain. Οι ερευνητές δεν μπόρεσαν να παρακάμψουν την signature verification· η εκμετάλλευση εξαρτάται από την πρόσβαση σε ένα matching private key ή σε ένα signing oracle.

Διορθωμένες εκδόσεις και αλλαγές συμπεριφοράς

  • GoAnywhere MFT 7.8.4 and Sustain Release 7.6.3:
  • Ενισχύουν την inner deserialization αντικαθιστώντας το SignedObject.getObject() με έναν wrapper (deserializeUntrustedSignedObject).
  • Αφαιρούν το error-handler token generation, κλείνοντας την pre-auth reachability.

Σημειώσεις για JSF/ViewState

Το reachability trick αξιοποιεί μια JSF σελίδα (.xhtml) και ένα invalid javax.faces.ViewState για να δρομολογήσει σε έναν privileged error handler. Παρότι δεν είναι ζήτημα JSF deserialization, αποτελεί ένα επαναλαμβανόμενο pre-auth μοτίβο: παραβιάστε error handlers που εκτελούν privileged actions και ορίζουν security-relevant session attributes.

Αναφορές

tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks