CC链 学习
URLDNS入门链
正常手动触发DNS请求
1 | String dnsLogDomain = "9707e182-0458-4f42-bec8-dc1fcc12555d.dnshook.site"; |
URL.class默认hashCode字段为-1,重写了hashCode方法,而且实现了java.io.Serializable接口
1 | public final class URL implements java.io.Serializable{ |
tips
1 | // 假设多个线程同时调用URL对象的hashCode() |
这里的handler是个抽象类,里面重载了从Object继承的hashCode方法public native int hashCode();
在这里面InetAddress addr = getHostAddress(u);会对url进行解析host的ip address,所以会有dns请求,请求完后,在URL类中,hashCode被重新赋值,hashCode = handler.hashCode(this);,不再为-1
1 | public abstract class URLStreamHandler { |
想办法通过反序列化自动触发
目标是反序列化某个对象的时候,能对一个URL类的hashCode方法调用
首先HashMap可以被序列化,其次他重写了readObject方法,会对key进行hash->hashCode操作
1 | public class HashMap<K,V> extends AbstractMap<K,V> |
由于我们要把一个URL对象放入HashMap中,而put的时候也会对key进行一次hash操作,所以我们需要在put前,把URL对象默认的hashCode=-1改成别的,put进去之后再改为-1,这样就能在反序列化的时候触发一次,而不是本地put的时候就触发了
CC链图解&复盘

中途核心:
- LazyMap
- ChainedTransformer
- InstantiateTransformer
CC1
环境配置 https://xz.aliyun.com/news/12115
TransformedMap::checkSetValue()
3.1-3.2.1 jdk版本小于u71
真是要忘光了,这玩意就CC1最绕
终点是InvokerTransformer::transform()可以任意代码执行,调用对象以及参数都要可控
往上找一层分别是LazyMap::get()和TransformedMap::checkSetValue()

tips
TransformedMap::put()也能触发TransformedMap::transformValue()最后触发tranform()
1 | public Object put(Object key, Object value) { |
TransformedMap::checkSetValue()在setValue()中使用,看这里的

如果要走EntrySetMapIterator::setValue(),就得找个readObject里面能遍历entry且调用setValue的
正好刚刚找到的AnnotationInvocationHandler::readObject里面就有这样的操作

CC1v2
AnnotationInvocationHandler本身是一个InvocationHandler,可以用于动态代理,当proxy的对象执行方法时,会自动调用handler的invoke方法
用一个memberValues为LazyMap的handler去代理一个Map
然后把这个proxyMap放到entranceInvocationHandler里面
在entranceInvocationHandler.readObject的时候,会触发proxyMap.entrySet()=>handler.invoke()=>memberValues.get()
1 | InvocationHandler proxyAnnotationInvocationHandler = (InvocationHandler) constructor.newInstance(Target.class, decorateMap); |
CC3
前面入口用proxyhandler进入LazyMap.get()打TemplatesImpl
3.1-3.2.1,jdk7u21及以前
我们先简单回顾一下TemplatesImpl是如何加载字节码的
Java程序编译和执行流程:
1 | .java 源码 → javac 编译 → .class 字节码 → JVM 加载并执行 |
Java字节码指的是JVM执行使用的一类指令,格式为二进制文件,通常被储存在.class文件中
在类加载中我们知道Java加载都需要经过:
1 | ClassLoader.loadClass -> ClassLoader.findClass -> ClassLoader.defineClass |
其中:
- loadClass的作用是从已经加载的类缓存、父加载器等位置寻找类(双亲委派机制),在前面没有找到的情况下,执行findClass
- findClass的作用就是根据基础URL制定的方式来查找类,读取字节码后交给defineClass
- defineClass的作用是处理前面传入的字节码,将其处理成真正的类
恶意类加载核心
TemplatesImpl利用链的核心就是可以恶意加载字节码,因为该类中存在一个内部类TransletClassLoader,该类继承了ClassLoader并且重写了loadClass,我们可以通过这个类加载器进行加载字节码
1 | TemplatesImpl::newTransformer()->TemplatesImpl::getTransletInstance() |
这个getTransletInstance()很关键,完成了两个工作
- 加载类:
defineTransletClasses(),在这里面通过自己写的TransletClassLoader中的defineClass走到原生的ClassLoader::defineClass(),但是单单加载类是无法执行自动执行类的构造代码块、静态代码块、构造方法的,实例化才行 - 实例化一个对象:
_class[_transletIndex].newInstance(),这让我们能够在类初始化时实现恶意代码执行
1 | private Translet getTransletInstance() |
为了能够成功的走完defineTransletClasses,获得_transletIndex = i;,在接下来实例化时才不会报错NullPointer
我们defineClass加载的类必须有superClass.getName().equals(ABSTRACT_TRANSLET)——即需要extends AbstractTranslet
接下来就是从TemplatesImpl::newTransformer()往回找链子就行
- TrAXFilter,这个只需要调用它的构造方法就行了,这个无法序列化,怎么办?
- TemplatesImpl::getOutputProperties,这个能序列化,但是链子往前找到头了
instantiateTransformer,它的transform方法可以动态创建对象,可序列化
既然是transform方法,那就和cc1前半段一样了
1 | ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{ |
CC6
HashMap.readObject()=>TiedMapEntry.hashCode()
3.1-3.2.1,jdk1.7,1.8
在JDK-8u71版本之后,cc-1的关键漏洞点AnnotationInvocationHandler.java的readObject方法做出了修改
删除了原来for遍历entry最里面的memberValue.setValue()
但是LazyMap.get仍然可以触发
由于put的时候会走到hash(tiedMapEntry)=>TiedMapEntry::hashCode()=>TidMapEntry::getValue()=>LazyMap.get()导致提前触发
1 | public V put(K key, V value) { |
所以需要在put前让tiedMapEntry的key不为装饰的lazyMap,put完后再改回去
1 | SerUtils.setFieldValue(tiedMapEntry, "map", new HashMap<Object, Object>()); |
CC5
BadAttributeValueExpException反序列化去触发LazyMap.get()
CC2
没啥用其实,记着CC4就好了
PriorityQueue.readObject()=>TransformingComparator.compare()
只能打Commons-collection4:4.0
CC4
CommonsCollections4 除4.0的其他版本去掉了InvokerTransformer的Serializable 继承
CC4只是将CC2中的InvokerTransformer替换为了InstantiateTransformer
ShiroCB
ClassLoader.loadClass 无法加载数组类
Class.forName可以加载数组类
用TemplatesImpl.newTransformer可以不使用Transformer数组
无依赖CB打法,需要注意comparator如果为null的话就相当于还是依赖cc

完全jdk自带comparator打法
我们可以beanComparator可以传入一个comparator
思路就是找到一个jdk原生的comparator(使用了comparator),而且使用了序列化接口
然后就找交集,找了一个Attra
- 标题: CC链 学习
- 作者: OneZ3r0
- 创建于 : 2026-01-15 14:15:11
- 更新于 : 2026-05-05 18:31:44
- 链接: https://blog.onez3r0.top/2026/01/15/java-cc-learning/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。