本文首发于先知社区:

https://xz.aliyun.com/t/7096

前言

作为一名安全研究人员(java安全菜鸡),知道拿到exp怎么打还不够,还得进一步分析exp构造原理与漏洞原理才行。本篇文章主要分析FastJson1.2.24中针对TemplatesImpl链的构造原理以及ysoserial中针对jdk7u21基于TemplatesImpl加动态代理链的构造原理。内容可能巨详细,希望没接触过这部分的同学可以耐心看下去。

1.TemplatesImpl初相识

FastJson1.2.24中基于com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl这条链的入口点在TemplatesImpl的getOutputperties函数。当然本篇文章不再描述具体如何到这一步,有兴趣的可以参考我之前的一篇文章。一步一步学习某Json1.2.47远程命令执行漏洞
因此在下图所示下断点在此,这里环境为jdk7u21。

这里首先将调用newTransformer(),首先定义类TransformerImpl的对象,其入口参数第一个为Translet的对象

因为此时调用了getTransletInstance(),跟进看看

其返回的是一个Translet类的对象,这里面判断_name 不能为null,否则就拿不到Tranlet对象,后面_class能不能为null,先留着,但是这里明显看到后面要用到_class

这里要用到_class,说明_class里面肯定是有东西的,要么是我们自己赋值,要么就是通过上面的defineTransletClasses()得到了,我们先跟进defineTransletClasses()看看

这里首先判断_bytecodes不能为null,为null将抛出错误信息

接着这里将拿到类加载器,通过它我们就可以对目标类进行加载,我们知道classloader除了调用loadclass来加载类以外,还可以调用findclass里的definedclass来通过加载字节码来在jvm中加载类,那么它肯定是classloader的子类,跟进看看其确实继承自ClassLoader,并且也看到了熟悉的defineclass函数,我们只要知道此时经过该类的definclass就能进行类的加载

继续往下看,下图中实际上取的是_bytecodes[i],并且i的范围也是我们可控的,这里我们知道defineClass是可以通过字节数组来在JVM创建类的,所以这里通过将恶意类的字节码放到_bytecodes里面就能够加载到JVM里

接着将拿到刚刚加载到JVM中的类的父类要求其父类必须是ABSTRACT_TRANSLET,不满足则放到_auxlclass里面,要是新加载的所有类的父类没有一个是ABSTRACT_TRANSLET的话,后面此时_transletIndex < 0处的判断就要报错,因为_transletIndex初始值为-1


此时我们已经知道通过defineTransletClasses函数我们可以通过defineclass来从_bytecodes中加载恶意类,所以我们肯定要让这里的_class为null

我们已经知道_class数组中存储的是加载进来的恶意类,下标_transletIndex就是该恶意类对应的下标,所以接着就调用newInstance()来实例化我们的恶意类,那么我们把要执行的命令放在恶意类的static区或者构造方法中都可以

其中我们的其中恶意类如下,至于要声明两个transform是由于这里是继承自抽象类,所以在其子类中必须实现,这里不声明的话idea也会提示让你实现,idea真香2333~

事实也证明如此,我们可以clac了

并且经过以上分析最终的payload可以缩减为以下形式:
整个调用过程挺短的,实际上就是
TemplatesImpl -> getOutputProperties()
TemplatesImpl -> newTransformer()
TemplatesImpl -> getTransletInstance()
此时在getTransletInstance()函数中将调用恶意类的构造函数,即
_class[_transletIndex].newInstance()导致RCE

2.AnnotationInvocationHandler完美链接

这一部分的分析主要就是通过最外层的readObject反序列化直达getOutputProperties()的调用,即newTransformer()的调用。而ysoserial中已经包含了该链的构造过程,其getobject函数就能拿到该链最外层的对象,而调试ysoserial也很容易,不传命令的话会默认传calc.exe

这里我们传入要执行的命令后调用createTemplatesImpl即可,那么我们可以看看yso中是如何构造该对象的

这里首先通过class.forname获得了三个我们构造该链要用到的类,然后将要执行的命令和这三个类传入createTemplatesImpl 来返回一个经过精心构造的TemplatesImpl对象

这里首先newInstance()获得一个初始的TemplatesImpl对象,用于后面的装饰,然后创建首先创建一个CtClass的容器,我们可以用它按需读取类文件来构造 CtClass 对象,并且保存 CtClass 对象以便以后使用

Java 字节码以二进制的形式存储在 .class 文件中,每一个 .class 文件包含一个 Java 类或接口。Javaassist 就是一个用来 处理 Java 字节码的类库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以去生成一个新的类对象,通过完全手动的方式。
由上面的解释也可以看出来这是一个功能强大的类。这里将首先会插入一个最原始的模板类,这个也就是作为我们用来执行命令的恶意类

通过以下这句代码我们就能够获得恶意模板类的对象,通过它我们就能够对该类的结构进行修改

然后将为该恶意模板类创建静态代码块,并插入rce的代码

这里rce的代码可以后面自己改,因此也可以自己根据需求定制

接着就是为该类起名字和设置该类的父类为abstranlet

接着就是获取该经过加工以后的类的字节码,以字节数组的形式保存,并通过反射的方式来设置templatesImple对象的_bytecodes变量值

其中setFieldValue函数第一个参数就是我们要设置的对象,第二个为属性,第三个参数为要设置的值

以上就完成了templatesImple的构造和恶意类的构造,但是如果不结合fastjson的反序列化特点的话就要找到一个新的readobject来链接到该templatesImple触发点,我们直接在hashset的readObject的中下断点进行调试,因为最终返回的是linkedHashSet的对象,因此入口点即在HashSet的readObject()函数

这里实际上将hashSet中的对象调用readObject()函数反序列化读出来然后放到有序列表的map中

由map.put就即将进入漏洞触发分析,因为后面要用到动态代理。所以这里简单分析一下这个技术:
首先要定义被代理的接口及其实现该接口的子类

接着要定义代理类,需继承自InvocationHandler,也就是位于被代理类处理顺序之前的类,在其构造函数中传入被代理类的对象,当调用被代理类的函数时将触发代理类的invoke函数,此处是重点,通过反射机制来实现

定义完被代理类以及代理类之后,还需通过Proxy类将两者进行绑定方可使用,这里要用Proxy.newProxyInstance来创建代理对象,通过其即可完成被代理的类与动态代理的绑定,然后通过该proxy对象就可能对被代理的类的函数进行调用,从而触发动态代理

运行结果如下图所示

在invoke处下个断点也可以清晰的看到此时method为hello,this.subject为SubjectImpl对象,args为world,即通过为被代理类绑定代理将可以在代理中运行新的代码块

了解了动态代理技术之后,就可以顺理成章地引入AnnotationInvocationHandler了,它就是一个动态代理,其继承自InvocationHandler

在其构造函数中有两个成员变量,两个均可控,并且在yso的payload中也通过反射机制为其this.type赋值为Templates类,并在newInstance中为memberValues赋值为只有一个键为f5a5a608,值为foo的map,当然后面将会对该键对应的值进行覆盖,放入恶意templatesImpl的对象,至于为什么要这样赋值后面说

目前我们只要知道这里是让AnnotationInvocationHandler作为Templates接口的代理。回到yso的paylaod,继续往下看,这里在linkedhashset中放了两个对象,其中linkedhashset是继承自hashset类的,放入的元素第一个是恶意的templateImpl,第二个是已经绑定了代理的对象

那么因为这里为了调试我们之前已经直接在hashset的readobject处下过断点

此时第一次反序列化得到的即为放入的恶意templateImpl类的对象,然后将其放到map中

第二次反序列化得到的即为proxy对象,为Templates类,这里的map.put即使新的入口点

跟进map.put看看,我们知道一个Map中不能包含相同的key,每个key只能映射一个value,那么能不能插入新的值,put内部肯定是有一定的判断逻辑的,那么这里面就包含了动态代理的触发点

put函数首先要对要放入的key计算一个hash,此时key为proxy对象,跟进此函数看看

其将会调用key.hashCode函数,那么我们知道当调用proxy对象的函数时将触发动态代理类的invoke函数,因此此时此时从下图①或②中都能够看到已经成功通过proxy对象进入到动态代理类AnnotationInvocationHandler当中

此时判断我们调用的是hashcode函数,将会进一步调用hashCodeImpl函数

在这个函数内部才是真正对map的key进行一个hash的计算

这里实际上将用127乘对AnnotationInvocationHandler的memberValues的键计算的hash以及值计算的hash,那么之前我们分析yso的payload时知道赋值给membervalue的键为键为f5a5a608,值为恶意的templateImpl对象,那么此时这个循环将执行一次并且计算key的hascode为0,那么实际上var1的值即为membervalue的键对应值的hascode,其值为恶意的TemplatesImpl对象

此时计算得到的hash为106298691,那么为什么要这么设置呢,之后就可以明白

计算完的hash还要经过移位操作然后得到最终的hash值为104622798


回到map.put函数的if判断,那么此时e.hash就是计算map第一个键的hash,而map第一个键就是恶意的TemplatesImpl对象,因此计算其hash肯定为104622798

所以之所以yso的payload要这么设置正是因为如此,也就是map的键为什么要设置为f5a5a608的原因,继续往下看

这里用Entry来对要放入的map的中的键进行遍历,其Map.Entry是Map声明的一个内部接口,此接口为泛型,定义为Entry<K,V>,它表示Map中的一个实体(一个key-value对),接着看这个判断:

其中有&&连接两部分
① e.hash == hash
② ((k = e.key) == key || key.equals(k))
那么此时先理清key和k都是什么,这里的key和k如下图所示,这里的key就是Templates类型的proxy对象,k就是之前第一次放入map中的恶意的Templates对象,此时作为equals函数的入口参数,实际上调用的被代理对象的equals方法,那么这里正和我们的思路,那么想要触发动态代理,我们知道e.key是TemplatesImpl的对象,key是Templates类型的proxy对象,那么判断肯定不成立,那么就能够执行或逻辑右边的表达式,那么此时条件①的已经满足,因此直接调用key.equals(k)

那么实际上这里就跳又到annotation这里了,就是我们之前设置的动态代理类,直接到invoke函数处,判断调用的是equals函数

这里将会又再次跳到equalsImpl函数,其中入口参数var3[0]为传入的TemplateImpl恶意类,继续跟进

此时1处templateImpl肯定不等于annotation并且2处这里this.type在yso的payload中设置为下图所示,通过反射令其type为Templates类

所以这里就是判断templateImpl是不是Templates类的对象,因为Templates是TemplatesImpl的父类,那么这肯定为true

注:
class.inInstance(obj)
这个对象能不能被转化为这个类
1.一个对象是本身类的一个对象
2.一个对象能被转化为本身类所继承类(父类的父类等)和实现的接口(接口的父接口)强转
3.所有对象都能被Object的强转
4.凡是null有关的都是false ,即class.inInstance(null)



此时将会调用getMemberMethods()函数,这个函数内部实际上就是返回annotation这个类的type变量对象的类的所有方法,那么这里实际上返回的就是Templates这个类的两个方法了
①.Transformer newTransformer()
②.Properties getOutputProperties()

接下来就到了最终的漏洞触发点,我感觉叫链接点比较好,在这里通过反射机制来调用newTransformer(),其中var1就是我们之前构造的恶意的TemplatesImpl类

这里我们来回顾一下getOutputProperties处,其中下面的newTransformer和上面反射的newTransformer完美的符合在一起

此时由函数调用栈也可以看到此时回到了TemplateImpl这个类中,至此利用链分析结束

3.从jdk7u25和jdk7u21的对比中分析修复

jdk7u25是jdk7u21的后一个版本,运行后结果如下图所示

那么上面分析的利用AnnotationInvocationHandler作为动态代理打到newTransformer在jdk7u25中已经被修复,运行时将会报错如上图所示,其中是在反序列化的过程中有一步是通过反射机制调用了AnnotationInvocationHandler的readObject函数

我们知道在yso的payload中通过反射机制来给AnnotationInvocationHandler的type赋值为Templates类

那么再次执行下图代码:

当执行hashset中第二个proxy对象的readObject时,实际上将会在其中调用AnnotationInvocationHandler的readObject函数来恢复动态代理,最终到readObject函数处

此时的type为javax.xml.transform.Templates,进一步调用AnnotationType.getInstance

跟进看看getInstance函数

此时进一步调用Templates.getAnnotationType()函数

此时返回为null,继续返回getInstance中

此时var1为null,那么将Templates实例传入Annotationtype的构造函数

此时在AnnotationType的构造函数将调用isAnnotation对var1进行判断

那么明显Templates类跟annotation没关系,可以看到annotation和其他两种数据类型就是java类类型里面定义的,属于class类,即引用数据类型。比如最常见的枚举,枚举类型是Java 5中新增特性的一部分,它是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型却又比类类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性、安全性以及便捷性。这里实际getModifiers()就是取Templates的修饰符,而Templates是接口类型的,所以两者无关,所以二进制位不可能有所重合,即肯定&&后为0。

所以此处必定进入if抛出错误

所以回到AnnotationInvocationHandler的readObject中将捕获到该错误从而抛出Non-annotation type in annotation serial stream

再回到jdk7u21里面,我们可以看到这里虽然在annotationtype中也捕获到了type不满足条件,并抛出了错误

但是catch以后直接retuen了,并没有再次抛出错误,让上层捕获,因此流程将继续走下去

所以反序列化将会继续执行,并且能够恢复我们的动态代理Templates,感觉是个逻辑错误,开发人员可能一不注意就会犯错,要发现这些点对于安全研究人员来说开发技能也是必备的。

总结

前前后后分析下来也花了几个晚上,真是学到了不少。整个漏洞利用中包含了很多java中的技术点,最大的感受就是Java的反射特性真的是太重要了,可以说是无处不在2333。挖掘漏洞需要大量调试和分析,补漏洞在这里抛出一个错误就可以让漏洞消失,当然调试的过程中也更加熟悉了java这门语言。

从0到1掌握某Json-TemplatesImpl链与ysoserial-jdk7u21的前因后果的更多相关文章

  1. 在Asp.Net Core 3.0中如何使用 Newtonsoft.Json 库序列化数据

    在.Net Core 3.0中 内置了一套Json序列化/反序列化方案,默认可以不再依赖,不再支持   Newtonsoft.Json. 但是.NET Core 3.0 System.Text.Jso ...

  2. MySQL 8.0: From SQL Tables to JSON Documents (and back again)

    MySQL 8.0: From SQL Tables to JSON Documents (and back again) | MySQL Server Bloghttps://mysqlserver ...

  3. .NET2.0下的对象生成JSON数据

    前言:今天研究了下在.NET2.0环境下开发Ajax程序经常用到的一个数据类型JSON, 一.什么是JSON? 自己也写不了句子不是很专业,下面是百度百科的关于JSON的介绍: JSON(JavaSc ...

  4. asp.net 2.0里也可以用JSON的使用方法

    本人找到一份,可以在asp.net2.0里实现JSON方式传送数据的方法.但是原方法,不能在数据中带有{.}.[.]."等,所以我做特意做了转意. 全部代码如下. /// <summa ...

  5. Flash as3.0 保存MovieClip运动轨迹到json文件

    //放在第一帧调用 import flash.events.Event; import flash.display.MovieClip; stage.addEventListener(Event.EN ...

  6. Spring5.0.x SSM项目中Json转换器 的配置

    json作为前后端交互的重要手段,在springMVC中有自带的转换器可以免去平时那些繁琐的事情: pom文件添加:spring5.0以上用Jackson2.9以上的版本 <dependency ...

  7. mysql8.0 新特性,对json类型的常用操作

    mysql8 新特性-json数据类型操作 -- 根据key(可多个)获取value SELECT JSON_EXTRACT('{"id": 14, "name" ...

  8. [.Net Core 3.0+/.Net 5] System.Text.Json中时间格式化

    简介 .Net Core 3.0开始全新推出了一个名为System.Text.Json的Json解析库,用于序列化和反序列化Json,此库的设计是为了取代Json.Net(Newtonsoft.Jso ...

  9. 如何在.Net Core 2.0 App中读取appsettings.json

    This is something that strangely doesn’t seem to be that well documented and took me a while to figu ...

  10. Autofac4.0以上的版本通过json配置文件方式实现IOC的MVC5设置

    我们知道java用到了spring来实现IOC,而我们学习的.net也有.net spring.但是.net spring现在没人维护了,进公司后发现公司使用到了autofac.但是用的是3.X的版本 ...

随机推荐

  1. Java动态编译优化——提升编译速度(N倍)

    一.前言 最近一直在研究Java8 的动态编译, 并且也被ZipFileIndex$Entry 内存泄漏所困扰,在无意中,看到一个第三方插件的动态编译.并且编译速度是原来的2-3倍.原本打算直接用这个 ...

  2. JS(JavaScript)的深入了解1(更新中···)

    面向对象 1.单列模式 2.工厂模式 3.构造函数 (1) 类Js天生自带的类Object 基类Function Array Number Math Boolean Date Regexp Strin ...

  3. centos虚拟机Ping不通网关

    centos虚拟机Ping不通网关 今天在VMware中安装了centos mini版本,安装完成后,用xshell连接一直连不上,本来以为是mini版本没有安装ssh server,于是就用命令: ...

  4. es6笔记 day1---let和const的应用

    ES6 -> ECMA标准 ES7  ES8 最早是由ECMA-262版本实现的 ---------------------------------------- ES6 也称为ES2015,2 ...

  5. IntelliJ IDEA+springboot+jdbctemplet+easyui+maven+oracle搭建简易开发框架(一)

    前言: 这两天为了巩固easyui的各个控件用法,搭建了一个简易的框架用于开发,大家可以用来参考,如果发现文章中有哪些不正确不合理的地方,也请各位不吝赐教,感激不尽.文章最下面有源码,可以用于参考.整 ...

  6. 工厂设计模式灵魂拷问-Java实现

    show me the code and take to me,做的出来更要说的明白 GitHub项目JavaHouse同步收录 喜欢就点个赞呗! 你的支持是我分享的动力! 引入 我们经常听到工厂模式 ...

  7. CommandPattern(命令模式)-----Java/.Net

    命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式.请求以命令的形式包裹在对象中,并传给调用对象.调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该 ...

  8. 初次在cmd使用git命令上传项目至github方法(笔记)

    在一切开始之前,先推荐一个git简易工具书--Git_Cheat_Sheet,非常适合新手.自行搜索即可,也有热心者提供了中文版. 一.下载 Git 从Git官网下载Git安装包 https://gi ...

  9. The Annual Summary Of 2019

    Time is flying, it arrives at the end of year again. This is my first year working in PinDuoDuo inc ...

  10. 认识Web应用框架

    Web应用框架 Web应用框架(Web application framework)是一种开发框架,用来支持动态网站.网络应用程序及网络服务的开发.类型可以分为基于请求(request-based)的 ...