环境搭建
组件下载:
https://mvnrepository.com/artifact/commons-collections/commons-collections/3.1
Idea 新建项目->选择jdk1.7—>选择File > project structure > Modules > dependencies > + JARS or directories ->加载下载的组件
漏洞复现
poc:
1 | import org.apache.commons.collections.Transformer; |
漏洞分析
step1
漏洞点在/commons-collections-3.1-sources.jar!/org/apache/commons/collections/functors/InvokerTransformer.java
1 | public class InvokerTransformer implements Transformer, Serializable { |
通过实现/commons-collections-3.1-sources.jar!/org/apache/commons/collections/Transformer.java
Transformer
接口,InvokerTransformer
构造方法在实例化的时候传入参数函数方法名以及参数名,transform
方法使用java反射机制得以调用任意方法。Transformer
接口:
1 | public interface Transformer { |
Java反射机制即参数传入一个对象,然后通过
getClass
、getMethod
等方法去获取其所属的类、所拥有的对象。
在Java中一切皆对象,调用系统命令的代码通常为:
1 | Runtime.getRuntime().exec("open -a Calculator"); |
可以通过构造这段代码实现命令执行:
1 | public class EvalObject { |
但是我们知道反序列化后一般只需要执行readObject
函数即可,如果直接序列化invokerTransformer
对象,那么readObject
之后的对象还需要主动调用transform(Runtime.getRuntime())
函数才能得以命令执行,显然是不太现实的。
demo:
1 | import java.io.*; |
step2: 反射链构造
这意味着Runtime.getRuntime()
的调用也需要我们通过反射来进行调用,而InvokerTransformer
的tansform
函数一次只能进行一次反射,这就需要我们构造一个反射链,最终调用exec
函数进行命令执行。
在 /commons-collections-3.1-sources.jar!/org/apache/commons/collections/functors/ChainedTransformer.java
中提供了我们构造一个函数对象调用链的一个方法:
1 | public class ChainedTransformer implements Transformer, Serializable { |
给ChainedTransformer
方法传递一个数组,在transform
方法里遍历调用其transform
方法,并将返回的结果作为下一次transform
函数的参数。
此时可以构造出这样一个poc:
1 | package test2; |
在Transformer
数组的第一个元素中用到了ConstantTransformer
类:
1 | public class ConstantTransformer implements Transformer, Serializable { |
通过初始化对象传入Runtime.class
类作为参数,然后在ChainedTransformer
类遍历数组调用其 ConstantTransformer
的transform
方法返回Runtime
类。
从transformer.transform("");
下断点跟进:
遍历数组,第一次进入ConstantTransformer
的transform
函数:
ConstantTransformer
的transform
返回在实例化时传入的Runtime
类:
第二次循环,将第一次返回的Runtime
类作为参数,带入第二次InvokerTransformer
类的transform
函数参数中:
这里通过java反射机制,从Runtime
类找到其getRuntime
方法,返回Runtime.getRuntime()
方法,作为下次循环的参数。
第三次循环再次通过InvokerTransformer
类的transform
方法,通过反射调用invoke
方法,真正的执行getRuntime
函数并返回Runtime
实例
在第四轮中我们可以看到object
参数变成了Runtime
对象,并且通过反射调用exec
函数来进行命令执行:
最后执行命令:
step3:寻找自动触发transform
在step2的poc中我们可以看到,反序列化之后其实还有一个对对象进行transform
函数的调用,虽然此时已经通过反射链解决了Runtime.getRuntime()
的参数传入问题,但是仍然需要我们调用transform
函数。
1 | Transformer transformer = (Transformer) unserialize(); |
这样的条件在实际环境中是难以利用的,我们希望的是仅调用readObject
函数就能够触发漏洞,即需要寻找一个有被重写的readObject
函数,而其中的流程能够触发transform
函数(可以直接搜索这两个关键字寻找)。
在/org/apache/commons/collections/map/TransformedMap.java
中:
1 | //65行 |
通过TransformedMap
函数设置成员变量,通过调用put
函数,触发transformValue
函数的valueTransformer.transform(object)
调用。
poc:
1 | public static void main(String[] args) throws Exception { |
虽然找到了一个能自动调用transform
的过程,但是要实现反序列化命令执行,还需要有对map的操作。这里还有另外一处也有调用transform
方法的功能:
1 | // 168行 |
在其父类/org/apache/commons/collections/map/AbstractInputCheckedMapDecorator.java
中实现了一个静态类的定义:
1 | // 180行 |
这里用了java类的嵌套,和php语言特性有点区别:https://blog.csdn.net/hguisu/article/details/7270086
step3: 寻找重写readObject
在jdk小于等于1.7的时,/sun/reflect/annotation/AnnotationInvocationHandler.class
中的readObject
中有对map的修改功能。
这里便于分析,用jd-gui
将其jar包逆向:/Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home/jre/lib/rt.jar
这里readObject
方法,使用了entry.setValue
方法。
在构造方法中,我们可以看到其将实例化传入的参数设为其成员变量this.memberValues
,接着在反序列化的时候,通过对readObject
的调用,触发MapEntry
的setValue
方法。
最后poc用了java反射去实例化创建对象,构造出一个完整的攻击链:
1 | package test; |
流程:参考seebug的一张图
远程利用实现
先学习几个概念:
- RMI(Remote Method Invocation)是一种基于序列化Java远程方法调用机制,作为一个常见的反序列化入口,和反序列化漏洞有密切联系。利用这种机制可以让某台服务器上的对象在调用另外一台服务器上的方法时,和在本地机上对象间的方法调用的语法规则一样。
- JNDI(Java Naming and Directory Interface),Java 命名与目录接口,JNDI支持的服务主要有以下几种:DNS、LDAP、 CORBA对象服务、RMI等。
(还有很多概念,先挖坑)
RMI服务端实现
构造一个User接口:User.java
1 | package RMI; |
实现User接口:UserImpl.java
1 | package RMI; |
实现Server端:
1 | package RMI; |
运行监听:
客户端UserClient:
1 | package RMI; |
最后
最后的transform
函数调用使用了jdk1.7
底层jar包,所以在不同的jdk版本利用链有所差异(挖坑),同时这个漏洞的关键在于/org/apache/commons/collections/functors/InvokerTransformer.java
可以通过反射调用任意函数,官方发布的新版本中增加了对相关反射调用的限制,同时对这些不安全的Java类的序列化支持增加了开关(也就是黑名单)。
java项目因为其可以加载很多依赖jar包,导致其反序列化可以寻找的攻击范围很广,从依赖扩展到jdk库,这也是java比php反序列化难的地方。
此外,简单学习了一些java语法后就开始分析漏洞,很多java语法特性以及概念不太熟悉,比如反射、嵌套类、JMX、JNDI等等,接下来打算好好弥补一下这方面的短板。
参考:
v1.5.2