fastjson反序列化漏洞_fastjson结合JdbcRowSetImpl反序列化
利⽤理解
前⾔
前⾔
最近看到⽹上有新出的fastjson反序列化利⽤,就好奇的琢磨了⼀下,查了⼀些资料然后整理出来,有不正确的地⽅还望指正。
反序列化的利⽤本质:
到⼀条有效的攻击链,攻击链的末端就是有代码执⾏能⼒的类,来达到我们想做的事情,⼀般都是⽤来RCE(远程命令执⾏)。
构造⼀个触发器,也就是通过什么⽅式来让攻击链执⾏你想要的代码。触发器可以通过很多⽅式,⽐如静态代码块、构造⽅法等等。fastjson是阿⾥巴巴开源的⼀款很优秀的JSON库。
JSON是⼀种⽤于交换的数据格式,可以在不同平台、不同语⾔之间传递数据,类似的还有XML。既然要传递,就涉及到序列化(将不同语⾔的对象转成json字符串)和反序列化(将json字符串还原成程序所需的对象)。
fastjson的简单⽤法
fastjson的简单⽤法
谈漏洞利⽤之前先简单的体验⼀下fastjson的⽤法。
# 先定义⼀个待序列化的User类:
package
#序列化与反序列化操作
package
通过JSON的toJSONString()⽅法将对象进⾏序列化,可以在在对象中指定哪些实例属性是需要被序列化的,以及序列化以后属性的顺序。通过JSON的parseObject()⽅法将JSON字符串反序列化成对象,可以直接作为原始类的实例来接收,也可以使⽤Object。还有parseArray()以及parse()反序列化⽅法,道理都差不多,只是解析出来的数据类型不⼀样。
本地模拟反序列化的利⽤
本地模拟反序列化的利⽤
前⾯说完了基本⽤法,只靠着前⾯那点东西是肯定实现不了反序列化利⽤的,因为你控制不了服务端会⽤把你传过去内容当成什么类型来解析,所以这⾥再引⼊⼀个@type。
看⼀段代码:
String
在上⾯提到的序列化的过程中加⼊了第⼆个参数,看参数名就知道是要在结果中写⼊被序列化的类的名字,看下序列化的结果:序列化后的json字符:
fastjson怎么用@type的值是序列化出来的类的名字,同理在反序列化的时候就会去解析这个type,即通过type执⾏了反序列化的类型,这就是我们可以利⽤的,这⾥是必要的第⼀步。
知道了这个第⼀步,就可以在本地模拟⼀个反序列化利⽤的过程:
package
这⾥直接给parseObject指定了⼀个JSON字符串,并且通过@type指定了当做T1类来解析。在T1类中写了⼀个⽆参构造⽅法,⽅法中通过exec执⾏了外部命令计算器,效果如图:
当然这只是本地演⽰,实际的开发中肯定不会有⼈在服务端写这么个直接执⾏系统命令的代码让你随便使。要知道我们通过序列号传递到服务端的只是“键值对”,也就是数据,具体的数据要怎么来解析还是得反序列化的服务端来实现。
什么是JNDI?
什么是JNDI?
说完了fastjson的简单使⽤,再来说说什么是JNDI。
JDNI是有sun提出的⼀个规范,全称是“命名和⽬录提供程序”(Naming and Directory Providers),⽤来解耦应⽤,让整个程序更便于扩展、部署以及应⽤。例如程序中可能会⽤到JDBC来操作数据库,由于数据库的配置是可能频繁变更的,就可以通过JDNI的⽅式把JDBC的配置从程序中解耦出来,当然这只是⼀个很⼩的⼀⽅⾯。
JDNI底层可以驱动⼀系列的远程对象,例如RMI、LDAP等等,具体见下图(侵删):
Naming Manger⽤于维护Naming和Directory的context objects对象以及对应的引⽤,可以通过Registry.bind()⽅法来创建,可以简单理解成有⼀对键值,键是“别名”,值是具体对应的“对象”,可以通过这个别名来到这个对象,这个“对象”可以直接是⼀个引⽤对象,例如⾃定义的类实例,也可以是⼀个Naming Reference。Naming Reference可以指定⼀个外部的远程对象,是很重要的⼀个功能。
什么是RMI?
什么是RMI?
RMI(Remote Method Invocation)远程⽅法调⽤,是专为Java环境设计的远程⽅法调⽤机制,远程服务器实现具体的Java⽅法并提供接⼝,客户端本地仅需根据接⼝类的定义,提供相应的参数即可调⽤远程⽅法。
1、运⾏了RMI的⼀端可以被称为“RMIServer”,⽤来提供可被远程调⽤的⽅法,这个过程称为“注册”,⼀般实现为
(192.168.76.133):
//JNDIServer.java
reference的功能已经在前⾯简单的介绍过,做为⼀个外部远程对象,⽬的是让客户端来获取RMI Server上的引⽤对象,指定了codebase 也就是远程的地址,以及要获取的factory类名,剩下的就是等客户端来主动请求。
请求的⽅式是通过http,就需要在192.168.76.133上运⾏http服务,并把已经编译好的serialize.Exploit.class放到响应的⽬录供客户端获取。具体的Exploit⾥⾯要放些什么后⾯会涉及到。
这⾥就体现了RMI的核⼼,RMI核⼼特点之⼀就是动态类加载,如果当前JVM中没有某个类的定义,它可以从远程URL去下载这个类的class。
2、运⾏了looup()⽅法的⼀端可以被称为 "RMIClient",根据RMI请求返回的结果来再次通过http获取所需的factory类并进⾏实例化,下⾯是⼀个本地模拟的实现(192.168.76.1):
// Test.java
模拟JNDI注⼊
模拟JNDI注⼊
上⾯已经简单实现了⼀个服务端提供RMI,⼀个客户端来请求服务器,剩下的就是实现那个最重要的Exploit:
//Exploit.java
编译成class,然后放到192.168.76.133/serialize⽬录下(因为我这⾥Exploit是在serialize包⾥⾯),同时运⾏JNDIServer注册RMI服务,最后在192.168.76.1上运⾏Test。
运⾏结果:
抓包:
可以看到192.168.76.1先对192.168.76.133发起RMI请求,然后⼜发起了HTTP请求,请求的是/serialize/Exploit.class,然后执⾏了Exploit构造⽅法。
⼤致的交互过程:
稍微总结⼀下就是,如果要成功利⽤这个looup()来做⼀些事,就必须要以下条件:
1、lookup()的参数是我们能控制的;
2、远程URL是我们能控制的;
JdbcRowSetImpl利⽤链
JdbcRowSetImpl利⽤链
上⾯简单的讲完了JNDI注⼊的⽅式,但是问题来了,RMIClient的代码都是我⾃⼰臆想的,什么地⽅提供了lookup()⽅法并且可以⾃定义rmi的URL呢?
这⾥就引出了利⽤链,可以理解成经过⼀系列的调⽤最终能执⾏lookup()并且传⼊了我们指定的URL,同时存在利⽤链的地⽅应该是JDK默认内置的库,或者很知名的很常见的库,因为这些更⼤的可能性会内置在⽬标应⽤当中,如果是个⼈开发的或者没多少⼈⽤的库就算存在利⽤链也没什么⽤,压根就没有⽤武之地。
这⾥要介绍的是JdbcRowSetImpl的利⽤链,是由@matthias_kaiser在2016年发现的,我这⾥只是简单的再根据⾃⼰的理解复述⼀下。
分析过程:
1、到JdbcRowSetImpl中调⽤了lookup()⽅法的函数;
在wset.JdbcRowSetImpl.class中的connect()中调⽤了looup(),并且looup的参数是通过getDataSourceName()⽅法获取到的,这⼀点很关键,需要确认这个get的结果是否是我们可控的。
1.1、确认getDataSourceName()⽅法的传值;
javadoc⾥⾯写明了DataSource是通过setDataSourceName来设置的,也就是dataSource属性的set和get⽅法;
⾄于怎么样来调⽤set⽅法,后⾯会提到。
2、到调⽤connect()⽅法的函数;
有三个⽅法中都调⽤了这个connect(),那么就需要⼀个⼀个的筛选,看看那些是我们能利⽤的。
3、分别分析这三个⽅法;
3.1、prepare()⽅法:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论