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

Reading time: 6 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

Ця сторінка документує поширений «guarded» патерн Java deserialization, побудований навколо java.security.SignedObject, і те, як на перший погляд недосяжні sinks можуть стати pre-auth досяжними через потоки обробки помилок. Техніка була помічена у Fortra GoAnywhere MFT (CVE-2025-10035), але застосовна до подібних реалізацій.

Модель загрози

  • Зловмисник може дістатися HTTP endpoint, який врешті обробляє переданий зловмисником byte[], призначений для serialized SignedObject.
  • Код використовує validating wrapper (наприклад, Apache Commons IO ValidatingObjectInputStream або кастомний adapter), щоб обмежити зовнішній тип до SignedObject (або byte[]).
  • Внутрішній об'єкт, який повертається SignedObject.getObject(), — це місце, де можуть спрацьовувати gadget chains (наприклад, CommonsBeanutils1), але тільки після проходження перевірки підпису.

Типовий вразливий патерн

Спрощений приклад на основі 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();
}
}

Ключові спостереження:

  • Валідувальний десеріалізатор у (1) блокує довільні top-level gadget classes; приймається лише SignedObject (або raw byte[]).
  • Примітив RCE буде знаходитися в внутрішньому об'єкті, матеріалізованому SignedObject.getObject() у (3).
  • Підписовий шлюз у (2) вимагає, щоб SignedObject пройшов verify() проти вбудованого у продукті публічного ключа. Якщо зловмисник не може створити дійсний підпис, внутрішній gadget ніколи не десеріалізується.

Міркування щодо експлуатації

Щоб досягти виконання коду, зловмисник має доставити правильно підписаний SignedObject, який обгортає ланцюжок malicious gadget як внутрішній об'єкт. Це зазвичай вимагає одного з наступних:

  • Компрометація приватного ключа: отримати відповідний приватний ключ, який продукт використовує для підпису/перевірки об'єктів ліцензії.
  • Signing oracle: змусити вендора або довірений signing service підписати серіалізований вміст, контрольований зловмисником (наприклад, якщо license server підписує вбудований довільний об'єкт з клієнтського вводу).
  • Alternate reachable path: знайти серверний шлях, який десеріалізує внутрішній об'єкт без виклику verify(), або який пропускає перевірки підпису в певному режимі.

За відсутності одного з перелічених, перевірка підпису перешкоджатиме експлуатації незважаючи на наявність десеріалізаційного sink.

Доступ до десеріалізації до аутентифікації через обробку помилок

Навіть якщо здається, що deserialization endpoint вимагає автентифікації або сесійно-прив'язаний токен, код обробки помилок може ненавмисно згенерувати та приєднати цей токен до неаутентифікованої сесії.

Приклад ланцюжка досяжності (GoAnywhere MFT):

  • Цільовий servlet: /goanywhere/lic/accept/ вимагає сесійно-прив'язаний token запиту ліцензії.
  • Шлях помилки: звернення до /goanywhere/license/Unlicensed.xhtml з додатковими даними та некоректним станом JSF запускає AdminErrorHandlerServlet, який робить:
  • SessionUtilities.generateLicenseRequestToken(session)
  • Перенаправляє на vendor license server із підписаним запитом ліцензії в bundle=<...>
  • Bundle можна розшифрувати офлайн (жорстко вбудовані ключі) щоб відновити GUID. Зберігши той самий session cookie і виконавши POST на /goanywhere/lic/accept/ з байтами bundle, контрольованими зловмисником, досягається 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=...
  • Виправлено: редирект без bundle (без генерації токена).

Виявлення для 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() на підсилену обгортку, яка повторно застосовує фільтрацію до внутрішнього потоку (наприклад, deserializeUntrustedSignedObject із ValidatingObjectInputStream/ObjectInputFilter і списками дозволених).
  • Видаліть потоки обробників помилок, які видають токени, прив'язані до сесії, для неавторизованих користувачів. Розглядайте шляхи обробки помилок як площу атаки.
  • Віддавайте перевагу Java serialization filters (JEP 290) із суворими списками дозволених як для зовнішньої, так і для внутрішньої десеріалізації. Приклад:
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

Приклад attack chain — підсумок (CVE-2025-10035)

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

Отримайте 302 з bundle=... та ASESSIONID=...; decrypt bundle offline щоб відновити 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. Дослідники не змогли обійти перевірку підпису; експлуатація залежить від доступу до відповідного приватного ключа або signing oracle.

Фіксовані версії та зміни поведінки

  • GoAnywhere MFT 7.8.4 and Sustain Release 7.6.3:
  • Посилено внутрішню десеріалізацію, замінивши SignedObject.getObject() обгорткою (deserializeUntrustedSignedObject).
  • Видалено генерацію токена обробника помилок, що закриває pre-auth досяжність.

Примітки щодо JSF/ViewState

Трюк з досяжністю використовує JSF-сторінку (.xhtml) та некоректний javax.faces.ViewState, щоб спрямувати виконання в привілейований обробник помилок. Хоча це не є проблемою десеріалізації JSF, це повторюваний pre-auth шаблон: проникнення в обробники помилок, які виконують привілейовані дії та встановлюють сесійні атрибути, що впливають на безпеку.

References

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