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

Reading time: 6 minutes

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

이 페이지는 java.security.SignedObject를 중심으로 구성된 일반적인 "guarded" Java deserialization 패턴과, 겉보기에는 도달 불가능한 sinks가 오류 처리 흐름을 통해 pre-auth로 도달 가능해질 수 있는 방법을 문서화합니다. 이 기법은 Fortra GoAnywhere MFT (CVE-2025-10035)에서 관찰되었지만 유사한 설계에도 적용됩니다.

위협 모델

  • 공격자는 결국 직렬화된 SignedObject로 의도된 공격자가 제공한 byte[]를 처리하는 HTTP 엔드포인트에 접근할 수 있다.
  • 해당 코드는 validating wrapper(예: Apache Commons IO ValidatingObjectInputStream 또는 커스텀 어댑터)를 사용하여 최외곽 타입을 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)에 있는 validating deserializer는 임의의 최상위 gadget classes를 차단하며, 오직 SignedObject (또는 raw byte[])만 허용한다.
  • RCE primitive는 (3)에서 SignedObject.getObject()로 실체화되는 inner object에 존재한다.
  • (2)의 signature gate는 SignedObject가 제품에 하드코딩된 공개 키로 verify()되어야 함을 강제한다. 공격자가 유효한 서명을 생성할 수 없으면 inner gadget은 결코 deserializes되지 않는다.

악용 고려사항

코드 실행을 달성하려면 공격자는 악성 gadget chain을 inner object로 감싼 올바르게 서명된 SignedObject를 전달해야 한다. 이는 일반적으로 다음 중 하나를 필요로 한다:

  • Private key compromise: 제품이 license 객체를 서명/검증하는 데 사용하는 대응하는 개인 키를 획득.
  • Signing oracle: 공급업체나 신뢰된 서명 서비스에 공격자가 제어하는 serialized 콘텐츠에 서명하도록 강제(예: license server가 클라이언트 입력에서 임베디드된 임의 객체에 서명하는 경우).
  • Alternate reachable path: 서버 측 경로를 찾아 inner object를 verify()를 강제하지 않고 deserializes 하거나 특정 모드에서 서명 검사를 건너뛰게 함.

이 중 어느 것도 없으면, signature verification이 deserialization sink가 존재하더라도 악용을 방지한다.

Pre-auth reachability via error-handling flows

deserialization endpoint가 인증이나 세션 바인딩 토큰을 요구하는 것처럼 보일지라도, 오류 처리 코드가 의도치 않게 토큰을 생성하여 인증되지 않은 세션에 첨부할 수 있다.

예제 도달성 체인 (GoAnywhere MFT):

  • Target servlet: /goanywhere/lic/accept/ 는 세션 바인딩된 license request token을 요구한다.
  • Error path: /goanywhere/license/Unlicensed.xhtml에 trailing junk와 잘못된 JSF 상태로 접근하면 AdminErrorHandlerServlet이 트리거되고, 다음을 수행한다:
  • SessionUtilities.generateLicenseRequestToken(session)
  • bundle=<...>에 서명된 license request와 함께 vendor license server로 리다이렉트
  • 해당 bundle은 오프라인에서 (hard-coded keys) 복호화하여 GUID를 복구할 수 있다. 동일한 세션 쿠키를 유지하고 attacker-controlled bundle bytes로 /goanywhere/lic/accept/에 POST하면 SignedObject sink에 pre-auth로 도달한다.

도달성 증명(영향 없음) probe:

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=... 및 Set-Cookie: ASESSIONID=...
  • 패치됨: bundle 없이 리디렉션(토큰 생성 없음).

블루팀 탐지

스택 트레이스/로그의 지표는 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() 호출을 내부 스트림에 대해 필터링을 재적용하는 하드닝된 래퍼(예: ValidatingObjectInputStream/ObjectInputFilter allow-lists를 사용하는 deserializeUntrustedSignedObject)로 교체하세요.
  • 인증되지 않은 사용자에게 session-bound 토큰을 발급하는 오류 처리 흐름을 제거하세요. 오류 경로를 공격 표면으로 간주하세요.
  • 외부 및 내부 역직렬화 모두에 대해 엄격한 allow-lists를 적용한 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

예시 공격 체인 요약 (CVE-2025-10035)

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

bundle=... 및 ASESSIONID=...가 포함된 302 응답을 수신; 오프라인에서 bundle을 decrypt하여 GUID를 복구.

  1. 동일한 cookie로 pre-auth 상태에서 sink에 도달:
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 requires a correctly signed SignedObject wrapping a gadget chain. 연구자들은 서명 검증(signature verification)을 우회할 수 없었으며; 악용은 일치하는 private key 또는 signing oracle에 대한 접근에 달려 있습니다.

수정된 버전 및 동작 변경사항

  • GoAnywhere MFT 7.8.4 및 Sustain Release 7.6.3:
  • 내부 역직렬화(inner deserialization)를 강화하기 위해 SignedObject.getObject()를 래퍼(deserializeUntrustedSignedObject)로 교체.
  • error-handler 토큰 생성 제거로 pre-auth 접근 가능성 차단.

JSF/ViewState 관련 메모

해당 도달성(reachability) 트릭은 JSF 페이지(.xhtml)와 잘못된 javax.faces.ViewState를 이용해 권한 있는 error handler로 경로를 유도합니다. 이는 JSF 역직렬화 문제는 아니지만, 반복되는 pre-auth 패턴으로—권한 있는 동작을 수행하고 보안 관련 세션 속성을 설정하는 error handler로 침투하는 방식입니다.

References

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기