首页 文章详情

fastjson1224的TemplatesImpl链反序列化分析

雷神众测 | 152 2022-01-12 23:09 0 0 0
UniSMS (合一短信)


STATEMENT

声明

由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,雷神众测及文章作者不为此承担任何责任。

雷神众测拥有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经雷神众测允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。


fastjson的序列化与反序列化

首先,分析fastjson的反序列化前我们先来了解一下fastjson,我们新建一个User类:


public class User {public Long id;public String name;public Long getId() {System.out.println("getid="+this.id);return id;}public void setId(Long id) {this.id = id;System.out.println("setid="+this.id);}public String getName() {System.out.println("getname="+this.name);return name;}public void setName(String name) {System.out.println("setname="+this.name);this.name = name;}}

fastjson提供了三种反序列化的方法,分别是:

String jsonString = "{\"id\":2,\"name\":\"guest\"}\n";// 反序列化代码示例1Object ob1 = JSON.parse(jsonString);System.out.println("ob1="+ob1);// 反序列化代码示例2Object ob2 = JSON.parseObject(jsonString);System.out.println("ob2="+ob2);// 反序列化代码示例3Object ob3 = JSON.parseObject(jsonString,User.class);System.out.println("ob3="+ob3);


那么这三个方法有什么区别呢,接下来,我们在代码中set一下参数的值,运行一遍看看:


public class main {public static void main(String[] args) {User guestUser = new User();guestUser.setId(2L);guestUser.setName("guest");// fastjson提供toJsonString接口实现序列化// fastjson提供parseObject来分别实现反序列化// 序列化示例String jsonString = JSON.toJSONString(guestUser);System.out.println("序列化数据="+jsonString);...}}



运行后发现ob1和ob2返回的是JSONObject,而ob3则是实际的类对象,并且ob3调用了User类中全部set方法,ob1与ob2则是set、get方法均未调用,那么接下来我们就来看看他们在调用set与get方法的区别
我们新写一段代码,首先传入User类的一句序列化后的字符串,而后使用三种方法来反序列化它,看看分别调用了什么方法


String parseString = "{\"@type\":\"User\",\"id\":2,\"name\":\"guest\"}";System.out.println("parse方法的调用结果");Object ob11 = JSON.parse(parseString);System.out.println("ob11="+ob11);System.out.println("----------------------");System.out.println("parseObject方法的调用结果");Object ob12 = JSON.parseObject(parseString);System.out.println("ob12="+ob12);System.out.println("----------------------");System.out.println("parseObject(xx,class)方法的调用结果");Object ob13 = JSON.parseObject(parseString,User.class);System.out.println("ob13="+ob13);

结果:

我们可以发现,parse和parseObject都返回了全部的set方法,并且parseObject返回了全部的get方法,parseObject(xx,class)返回了全部的set方法,那么我们是否可以得出结论,全部的方法都可以调用set方法呢?那么接下来我们就来看看调用到的set、get方法分别存在什么限制,这其实就是fastjson的特性



fastjson特性

首先,我们来看set与get方法的限制:

// com/alibaba/fastjson/util/JavaBeanInfo.javapublic class JavaBeanInfo{public static JavaBeanInfo build(Class clazz, Type type, PropertyNamingStrategy propertyNamingStrategy) {/*** 判断set方法函数*/for (Method method : methods) { //int ordinal = 0, serialzeFeatures = 0, parserFeatures = 0;String methodName = method.getName();// 长度比4大if (methodName.length() < 4) {continue;}// 非静态方法if (Modifier.isStatic(method.getModifiers())) {continue;}// 返回类型不能是void或者当前类// support builder setif (!(method.getReturnType().equals(Void.TYPE) || method.getReturnType().equals(method.getDeclaringClass()))) {continue;}Class[] types = method.getParameterTypes();// 不能有传入的参数if (types.length != 1) {continue;}// annotation = null,跳过JSONField annotation = method.getAnnotation(JSONField.class);if (annotation == null) {annotation = TypeUtils.getSuperMethodAnnotation(clazz, method);}if (annotation != null) {// ......略过}// 以set为开头if (!methodName.startsWith("set")) {continue;}char c3 = methodName.charAt(3);String propertyName;// 第4个字母大写if (Character.isUpperCase(c3) //|| c3 > 512 // for unicode method name) {if (TypeUtils.compatibleWithJavaBean) {propertyName = TypeUtils.decapitalize(methodName.substring(3));} else {propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);}} else if (c3 == '_') {propertyName = methodName.substring(4);} else if (c3 == 'f') {propertyName = methodName.substring(3);} else if (methodName.length() >= 5 && Character.isUpperCase(methodName.charAt(4))) {propertyName = TypeUtils.decapitalize(methodName.substring(3));} else {continue;}Field field = TypeUtils.getField(clazz, propertyName, declaredFields);if (field == null && types[0] == boolean.class) {String isFieldName = "is" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);field = TypeUtils.getField(clazz, isFieldName, declaredFields);}// filed = null,跳过JSONField fieldAnnotation = null;if (field != null) {// ......}// propertyNamingStrategy = null,跳过if (propertyNamingStrategy != null) {// ......}add(fieldList, new FieldInfo(propertyName, method, field, clazz, type, ordinal, serialzeFeatures, parserFeatures,annotation, fieldAnnotation, null));}/*** 判断get方法函数体:*/for (Method method : clazz.getMethods()) { // getter methodsString methodName = method.getName();// 长度不能小于4if (methodName.length() < 4) {continue;}// 不能是静态方法if (Modifier.isStatic(method.getModifiers())) {continue;}// 以get开头并且第4位是个大写字母if (methodName.startsWith("get") && Character.isUpperCase(methodName.charAt(3))) {// 不能有传入的参数if (method.getParameterTypes().length != 0) {continue;}// 返回值继承于Collection Map AtomicBoolean AtomicInteger AtomicLong之一if (Collection.class.isAssignableFrom(method.getReturnType()) //|| Map.class.isAssignableFrom(method.getReturnType()) //|| AtomicBoolean.class == method.getReturnType() //|| AtomicInteger.class == method.getReturnType() //|| AtomicLong.class == method.getReturnType() //) {// .........// 对get方法进行处理的函数,略过}

所以,根据以上代码,我们可以总结出调用到set、get方法的限制:

  • set方法条件

  1. 方法名长度大于4且以set开头,且第四个字母要是大写

  2. 非静态方法

  3. 返回类型为void或当前类

  4. 参数个数为1个


  • get方法条件

  1. 方法名长度大于等于4,且以get开头且第4个字母为大写

  2. 非静态方法

  3. 无传入参数

  4. 返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong


并且,parseobject()这个方法中多了一步toJSON操作,会调用全部的get方法:

这样,上面调用到的set、get方法就全部说通了。


下面我们看看fastjson的另一种特性,就是@type这个参数,开发者本意是序列化时使用SerializerFeature.WriteClassName会写入类型信息,从而使反序列化时不会丢失类型信息,并且反序列化时自动进行类型识别,但是这就造成了一个问题,type参数可以指定反序列化任意的类,然后去调用set、get方法,我们来看看实际的代码是如何运作的


在序列化是写入类型信息,此时的序列化后的字符串就有@type参数:

我们在看看输入@type参数,他的反序列化结果:

看似这种情况很正常,反序列化使用到set、get方法恢复数据,但是在可以调用任意类的情况下,这就很恐怖了,如果set或者get方法中有可利用的点的情况下,就会造成RCE。

jdk7u21TemplatesImpl链

说到fastjson的TemplatesImpl链,就不得不提jdk7u21的TemplatesImpl链,fastjson的这种利用方法其实和jdk7u21的TemplatesImpl链很像,jdk7u21由以下代码出发RCE


import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
public class jdk7u21_nekoc {public static void main(String[] args) throws Exception {TemplatesImpl calc = (TemplatesImpl) Gadgets.createTemplatesImpl("/System/Applications/Calculator.app/Contents/MacOS/Calculator");calc.getOutputProperties();}}

gadegets类是在ysoserial里拔出来的,跟进触发漏洞的calc.getOutputProperties();方法,发现最终由obj.newinstance触发恶意代码,由于newinstance示例化会默认触发static及构造方法,所以payload可以写在这两个中的其一,并且,这里注意,getOutputProperties方法正好符合fastjson中对get方法名字的限制。
我们这里不再具体分析jdk7u21,我们来跟进看一下该链子的一些限制条件,跟进代码:

return newTransformer().getOutputProperties();
首先,我们发现:

com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl.java中,存在两个限制条件:

public class TemplatesImpl {private Translet getTransletInstance()throws TransformerConfigurationException {try {// 第一个条件 _name不为nullif (_name == null) return null;
// 第二个条件 _class不为null,进到defineTransletClasses里if (_class == null) defineTransletClasses();// ......}}


第一个条件就是_name不为null,第二个条件是_class不为null,这样才能进到defineTransletClasses方法,我们继续来看看defineTransletClasses方法里边的判断:

private void defineTransletClasses()throws TransformerConfigurationException {
// 第三个条件:_bytecodes不为nullif (_bytecodes == null) {ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);throw new TransformerConfigurationException(err.toString());}
// 7u21TransletClassLoader loader = (TransletClassLoader)AccessController.doPrivileged(new PrivilegedAction() {public Object run() {// 这里应该是第四个条件,这里7u21和高版本的7u80有区别return new TransletClassLoader(ObjectFactory.findClassLoader());}});// 7u80TransletClassLoader loader = (TransletClassLoader)AccessController.doPrivileged(new PrivilegedAction() {public Object run() {// 这里就是第四个限制条件,_tfactory参数存在一个getExternalExtensionsMap方法return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());}});

try {final int classCount = _bytecodes.length;_class = new Class[classCount];
if (classCount > 1) {_auxClasses = new Hashtable();}
for (int i = 0; i < classCount; i++) {_class[i] = loader.defineClass(_bytecodes[i]);final Class superClass = _class[i].getSuperclass();
// Check if this is the main class// 第五个限制条件:_bytecodes必须是ABSTRACT_TRANSLET子类if (superClass.getName().equals(ABSTRACT_TRANSLET)) {_transletIndex = i;}else {_auxClasses.put(_class[i].getName(), _class[i]);}}
if (_transletIndex < 0) {ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);throw new TransformerConfigurationException(err.toString());}}catch(Exception e){//...}}}

我们跟进代码首先会发现第三个限制条件,即_bytecodes不为null,继续向下看,这里的代码版本不同,内容就不一样,在7u21中,不存在有关_tfactory的限制,在7u80中,就存在该限制,这里要求_tfactory参数存在一个getExternalExtensionsMap方法,为了版本通用,所以在poc里加上对tfactory的限制条件,继续向下看,我们可以看到第五个条件,即_bytecodes必须是ABSTRACT_TRANSLET子类,并且,payload要写在_bytecode对应的类的静态方法或者构造方法里,这样,我们就集齐了全部的限制条件,小结一下就是:

  1. name != null

  2. _class == null

  3. _bytecodes != null

  4. _tfactory需要有一个getExternalExtensionsMap方法

  5. _bytecodes的类必须是ABSTRACT_TRANSLET的子类

  6. payload要在_bytecode对应的类的静态方法或者构造方法里

1224 TemplatesImpl链

jdk7u21在fastjson1224的应用还需要一点点条件,用parseObject()时,必须用JSON.parseObject(jsonString, Feature.SupportNonPublicField);这样的格式,必须要有Feature.SupportNonPublicField参数才可以,payload中有部分参数是private属性,需要在这里设置一下才能被接受;使用parse()时,需要JSON.parse(jsonString,Feature.SupportNonPublicField); 这样的格式。
在编写payload时,需要用到javasist类,这里不再描述该类。
最终的部分payload:

看到最后的代码可能会有些疑问,这里的_tfactory为什么是空而不是一个有getExternalExtensionsMap方法的类对象?_OutputProperties是怎么调用到getOutputProperties方法的?
我们来看看这两个问题的解答。
首先是第一个,_tfactory为什么是空而不是一个有getExternalExtensionsMap方法的类对象呢,我们跟一下代码,不难发现,解析这几个参数的时候,如果发现参数值为空对象,就会新建一个该参数应有的格式的对象实例,将其赋值给该参数

那么,_tfactory应有的格式为啥是它呢?咱们看一下定义就知道了

所以,_tfactory其实本身就是TransformerFactoryImpl类的对象,赋值为空就系那个回家了一样,完全是可以的。
接下来我们看看第二个问题,_OutputProperties是怎么调用到getOutputProperties方法的呢,我们跟进代码就可以看到,在处理参数的时候,代码会将_OutputProperties前面的下划线替换为空,此时反序列化时就回去调用他的get方法,即getOutputProperties,所以其实poc生成中得_OutputProperties有没有下划线都是可以的,这样我们就在fastjson1224版本中完成了TemplatesImpl链的分析。


征稿通知

知识应该被分享,安全更需携手共进

征稿持续进行中!愿意分享知识经验的小伙伴们可以把自己的知识沉淀稿件投稿至微信:

稿件一经发布将有丰厚的稿费!




RECRUITMENT

招聘启事

安恒雷神众测SRC运营(实习生)
————————
【职责描述】
1.  负责SRC的微博、微信公众号等线上新媒体的运营工作,保持用户活跃度,提高站点访问量;
2.  负责白帽子提交漏洞的漏洞审核、Rank评级、漏洞修复处理等相关沟通工作,促进审核人员与白帽子之间友好协作沟通;
3.  参与策划、组织和落实针对白帽子的线下活动,如沙龙、发布会、技术交流论坛等;
4.  积极参与雷神众测的品牌推广工作,协助技术人员输出优质的技术文章;
5.  积极参与公司媒体、行业内相关媒体及其他市场资源的工作沟通工作。

【任职要求】 
 1.  责任心强,性格活泼,具备良好的人际交往能力;
 2.  对网络安全感兴趣,对行业有基本了解;
 3.  良好的文案写作能力和活动组织协调能力。


简历投递至 

bountyteam@dbappsecurity.com.cn

设计师(实习生)

————————

【职位描述】
负责设计公司日常宣传图片、软文等与设计相关工作,负责产品品牌设计。

【职位要求】
1、从事平面设计相关工作1年以上,熟悉印刷工艺;具有敏锐的观察力及审美能力,及优异的创意设计能力;有 VI 设计、广告设计、画册设计等专长;
2、有良好的美术功底,审美能力和创意,色彩感强;

3、精通photoshop/illustrator/coreldrew/等设计制作软件;
4、有品牌传播、产品设计或新媒体视觉工作经历;

【关于岗位的其他信息】
企业名称:杭州安恒信息技术股份有限公司
办公地点:杭州市滨江区安恒大厦19楼
学历要求:本科及以上
工作年限:1年及以上,条件优秀者可放宽


简历投递至 

bountyteam@dbappsecurity.com.cn

安全招聘

————————

公司:安恒信息
岗位:Web安全 安全研究员
部门:战略支援部
薪资:13-30K
工作年限:1年+
工作地点:杭州(总部)、广州、成都、上海、北京

工作环境:一座大厦,健身场所,医师,帅哥,美女,高级食堂…

【岗位职责】
1.定期面向部门、全公司技术分享;
2.前沿攻防技术研究、跟踪国内外安全领域的安全动态、漏洞披露并落地沉淀;
3.负责完成部门渗透测试、红蓝对抗业务;
4.负责自动化平台建设
5.负责针对常见WAF产品规则进行测试并落地bypass方案

【岗位要求】
1.至少1年安全领域工作经验;
2.熟悉HTTP协议相关技术
3.拥有大型产品、CMS、厂商漏洞挖掘案例;
4.熟练掌握php、java、asp.net代码审计基础(一种或多种)
5.精通Web Fuzz模糊测试漏洞挖掘技术
6.精通OWASP TOP 10安全漏洞原理并熟悉漏洞利用方法
7.有过独立分析漏洞的经验,熟悉各种Web调试技巧
8.熟悉常见编程语言中的至少一种(Asp.net、Python、php、java)

【加分项】
1.具备良好的英语文档阅读能力;
2.曾参加过技术沙龙担任嘉宾进行技术分享;
3.具有CISSP、CISA、CSSLP、ISO27001、ITIL、PMP、COBIT、Security+、CISP、OSCP等安全相关资质者;
4.具有大型SRC漏洞提交经验、获得年度表彰、大型CTF夺得名次者;
5.开发过安全相关的开源项目;
6.具备良好的人际沟通、协调能力、分析和解决问题的能力者优先;
7.个人技术博客;
8.在优质社区投稿过文章;


岗位:安全红队武器自动化工程师
薪资:13-30K
工作年限:2年+
工作地点:杭州(总部)

【岗位职责】
1.负责红蓝对抗中的武器化落地与研究;
2.平台化建设;
3.安全研究落地。

【岗位要求】
1.熟练使用Python、java、c/c++等至少一门语言作为主要开发语言;
2.熟练使用Django、flask 等常用web开发框架、以及熟练使用mysql、mongoDB、redis等数据存储方案;
3:熟悉域安全以及内网横向渗透、常见web等漏洞原理;
4.对安全技术有浓厚的兴趣及热情,有主观研究和学习的动力;
5.具备正向价值观、良好的团队协作能力和较强的问题解决能力,善于沟通、乐于分享。

【加分项】
1.有高并发tcp服务、分布式等相关经验者优先;
2.在github上有开源安全产品优先;
3:有过安全开发经验、独自分析过相关开源安全工具、以及参与开发过相关后渗透框架等优先;
4.在freebuf、安全客、先知等安全平台分享过相关技术文章优先;
5.具备良好的英语文档阅读能力。


简历投递至

bountyteam@dbappsecurity.com.cn

岗位:红队武器化Golang开发工程师

薪资:13-30K
工作年限:2年+
工作地点:杭州(总部)

【岗位职责】
1.负责红蓝对抗中的武器化落地与研究;
2.平台化建设;
3.安全研究落地。

【岗位要求】
1.掌握C/C++/Java/Go/Python/JavaScript等至少一门语言作为主要开发语言;
2.熟练使用Gin、Beego、Echo等常用web开发框架、熟悉MySQL、Redis、MongoDB等主流数据库结构的设计,有独立部署调优经验;
3.了解docker,能进行简单的项目部署;
3.熟悉常见web漏洞原理,并能写出对应的利用工具;
4.熟悉TCP/IP协议的基本运作原理;
5.对安全技术与开发技术有浓厚的兴趣及热情,有主观研究和学习的动力,具备正向价值观、良好的团队协作能力和较强的问题解决能力,善于沟通、乐于分享。

【加分项】
1.有高并发tcp服务、分布式、消息队列等相关经验者优先;
2.在github上有开源安全产品优先;
3:有过安全开发经验、独自分析过相关开源安全工具、以及参与开发过相关后渗透框架等优先;
4.在freebuf、安全客、先知等安全平台分享过相关技术文章优先;
5.具备良好的英语文档阅读能力。

简历投递至

bountyteam@dbappsecurity.com.cn



END

长按识别二维码关注我们


good-icon 0
favorite-icon 0
收藏
回复数量: 0
    暂无评论~~
    Ctrl+Enter