9-java安全——关于构造CC2链的几个问题
Posted songly_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了9-java安全——关于构造CC2链的几个问题相关的知识,希望对你有一定的参考价值。
思考一下:CC2链的poc利用代码中为什么要向queue队列中添加至少2个元素?并且PriorityQueue队列中的queue属性被transient关键字修饰具有“不会序列化”语义,在反序列化过程中为什么还能从流中读取queue的数据,流中的数据又从何而来?
readObject方法是怎么从流中读取queue的数据
使用SerializationDumper工具解析对象序列化的数据,Fields字段中并没有看到PriorityQueue的queue属性,说明queue确实没有参与序列化,但是PriorityQueue重写了Serializable接口的writeObject和readObject方法实现自定义序列化与反序列化。
PriorityQueue集合中的成员属性如下:
public class PriorityQueue<E> extends AbstractQueue<E> implements java.io.Serializable
private static final long serialVersionUID = -7720805057305804111L;
private static final int DEFAULT_INITIAL_CAPACITY = 11;
private transient Object[] queue;
private int size = 0;
private final Comparator<? super E> comparator;
private transient int modCount = 0;
可以看到集合中的静态变量和被transient关键字修饰的成员变量不会参与序列化,最终只有size和comparator两个成员会参与序列化与反序列化过程。
通过分析PriorityQueue类的writeObject方法,发现对queue属性里的内容进行了序列化。
有同学可能会有疑惑:java序列化机制不允许被transient关键字修饰的成员属性参与序列化,那为啥还是能调用writeObject方法进行序列化?
这句话没有错,但是有一个前提,如果使用默认的方式进行序列化(实现Serializable接口但不重写writeObject和readObject方法),transient关键字修饰的成员属性确实不会参与序列化,并且成员属性的内容也不会序列化。但是重写了writeObject和readObject方法就可以实现自定义序列化,调用writeObject方法并传入queue属性,这样的话queue中的内容依然会参与序列化。
那问题来了,queue属性里的内容到底写到了哪里呢?继续跟进分析writeObject方法做了什么事情?
可以看到writeObject方法会传入一个TemplatesImpl对象的参数
然后调用了writeObject0方法,经过一些列调用,会调用writeOrdinaryObject方法,该方法内部bout流调用了writeByte方法写入TC_OBJECT标记,其值为115,十六进制为0x73。
接着调用writeClassDesc方法,该方法内部调用了writeNonProxyDesc方法写入类描述信息,也就是写入TC_CLASSDESC标记,其值为114,十六进制为0x72
接着调用了writeNonProxy方法开始写入对象的信息:
void writeNonProxy(ObjectOutputStream out) throws IOException
//写入当前对象所属类名
out.writeUTF(name);
//写入suid
out.writeLong(getSerialVersionUID());
byte flags = 0;
if (externalizable)
flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
int protocol = out.getProtocolVersion();
if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1)
flags |= ObjectStreamConstants.SC_BLOCK_DATA;
else if (serializable)
flags |= ObjectStreamConstants.SC_SERIALIZABLE;
if (hasWriteObjectData)
flags |= ObjectStreamConstants.SC_WRITE_METHOD;
if (isEnum)
flags |= ObjectStreamConstants.SC_ENUM;
out.writeByte(flags);
//写入属性字段个数
out.writeShort(fields.length);
//写入字段信息
for (int i = 0; i < fields.length; i++)
ObjectStreamField f = fields[i];
out.writeByte(f.getTypeCode());
out.writeUTF(f.getName());
if (!f.isPrimitive())
out.writeTypeString(f.getTypeString());
写入的类名为com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,写入的suid为0x09 57 4f c1 6e ac ab 33,接着依次写入对象的成员属性信息。
queue数组中第一个元素写入完毕,接着writeObject方法写入第二个元素,由于这两个元素都属于同一对象,第二次写入的时候并不会再次写入该对象的内容,而是会写入该对象的newHandle(对象引用),TemplatesImpl对象的newHandle值就是0x007e0012,TC_REFERENCE标记表示这是对一个对象引用的引用。
objectAnnotation字段的位置就是queue数据序列化的数据,如下所示:
objectAnnotation
TC_BLOCKDATA - 0x77
Length - 4 - 0x04
Contents - 0x00000003
//writeObject开始写入queue中第一个元素数据
TC_OBJECT - 0x73
TC_CLASSDESC - 0x72
className
Length - 58 - 0x00 3a
Value - com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl - 0x636f6d2e73756e2e6f72672e6170616368652e78616c616e2e696e7465726e616c2e78736c74632e747261782e54656d706c61746573496d706c
serialVersionUID - 0x09 57 4f c1 6e ac ab 33
newHandle 0x00 7e 00 0e
classDescFlags - 0x03 - SC_WRITE_METHOD | SC_SERIALIZABLE
fieldCount - 9 - 0x00 09
Fields
0:
Int - I - 0x49
fieldName
Length - 13 - 0x00 0d
Value - _indentNumber - 0x5f696e64656e744e756d626572
1:
Int - I - 0x49
fieldName
Length - 14 - 0x00 0e
Value - _transletIndex - 0x5f7472616e736c6574496e646578
2:
Boolean - Z - 0x5a
fieldName
Length - 21 - 0x00 15
Value - _useServicesMechanism - 0x5f75736553657276696365734d656368616e69736d
3:
Object - L - 0x4c
fieldName
Length - 25 - 0x00 19
Value - _accessExternalStylesheet - 0x5f61636365737345787465726e616c5374796c657368656574
className1
TC_REFERENCE - 0x71
Handle - 8257546 - 0x00 7e 00 0a
4:
Object - L - 0x4c
fieldName
Length - 11 - 0x00 0b
Value - _auxClasses - 0x5f617578436c6173736573
className1
TC_STRING - 0x74
newHandle 0x00 7e 00 0f
Length - 59 - 0x00 3b
Value - Lcom/sun/org/apache/xalan/internal/xsltc/runtime/Hashtable; - 0x4c636f6d2f73756e2f6f72672f6170616368652f78616c616e2f696e7465726e616c2f78736c74632f72756e74696d652f486173687461626c653b
5:
Array - [ - 0x5b
fieldName
Length - 10 - 0x00 0a
Value - _bytecodes - 0x5f62797465636f646573
className1
TC_STRING - 0x74
newHandle 0x00 7e 00 10
Length - 3 - 0x00 03
Value - [[B - 0x5b5b42
6:
Array - [ - 0x5b
fieldName
Length - 6 - 0x00 06
Value - _class - 0x5f636c617373
className1
TC_REFERENCE - 0x71
Handle - 8257547 - 0x00 7e 00 0b
7:
Object - L - 0x4c
fieldName
Length - 5 - 0x00 05
Value - _name - 0x5f6e616d65
className1
TC_REFERENCE - 0x71
Handle - 8257546 - 0x00 7e 00 0a
8:
Object - L - 0x4c
fieldName
Length - 17 - 0x00 11
Value - _outputProperties - 0x5f6f757470757450726f70657274696573
className1
TC_STRING - 0x74
newHandle 0x00 7e 00 11
Length - 22 - 0x00 16
Value - Ljava/util/Properties; - 0x4c6a6176612f7574696c2f50726f706572746965733b
classAnnotations
TC_ENDBLOCKDATA - 0x78
superClassDesc
TC_NULL - 0x70
//TemplatesImpl对象的引用
newHandle 0x00 7e 00 12
classdata
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
values
_indentNumber
(int)0 - 0x00 00 00 00
_transletIndex
(int)-1 - 0xff ff ff ff
_useServicesMechanism
(boolean)false - 0x00
_accessExternalStylesheet
(object)
TC_STRING - 0x74
newHandle 0x00 7e 00 13
Length - 3 - 0x00 03
Value - all - 0x616c6c
_auxClasses
(object)
TC_NULL - 0x70
_bytecodes
(array)
TC_ARRAY - 0x75
TC_CLASSDESC - 0x72
className
Length - 3 - 0x00 03
Value - [[B - 0x5b5b42
serialVersionUID - 0x4b fd 19 15 67 67 db 37
newHandle 0x00 7e 00 14
classDescFlags - 0x02 - SC_SERIALIZABLE
fieldCount - 0 - 0x00 00
classAnnotations
TC_ENDBLOCKDATA - 0x78
superClassDesc
TC_NULL - 0x70
newHandle 0x00 7e 00 15
Array size - 1 - 0x00 00 00 01
Values
Index 0:
(array)
TC_ARRAY - 0x75
TC_CLASSDESC - 0x72
className
Length - 2 - 0x00 02
Value - [B - 0x5b42
serialVersionUID - 0xac f3 17 f8 06 08 54 e0
newHandle 0x00 7e 00 16
classDescFlags - 0x02 - SC_SERIALIZABLE
fieldCount - 0 - 0x00 00
classAnnotations
TC_ENDBLOCKDATA - 0x78
superClassDesc
TC_NULL - 0x70
newHandle 0x00 7e 00 17
Array size - 1455 - 0x00 00 05 af
Values
//省略......
_class
(array)
TC_NULL - 0x70
_name
(object)
TC_STRING - 0x74
newHandle 0x00 7e 00 18
Length - 13 - 0x00 0d
Value - TemplatesImpl - 0x54656d706c61746573496d706c
_outputProperties
(object)
TC_NULL - 0x70
objectAnnotation
TC_BLOCKDATA - 0x77
Length - 1 - 0x01
Contents - 0x00
TC_ENDBLOCKDATA - 0x78
//写入queue中第二个元素
TC_REFERENCE - 0x71
Handle - 8257554 - 0x00 7e 00 12
TC_ENDBLOCKDATA - 0x78
在java序列化规范中,从objectAnnotation表示写入序列化时自定义的数据,因此queue中的元素都会写入到objectAnnotation字段开始的位置。第二次写入queue数组的元素并不会直接写入数据,而是直接写入TemplatesImpl对象的引用。
TC_REFERENCE标记表示(Handle - 8257554 - 0x00 7e 00 12)这段数据是“引用了”一个序列化对象的引用,Handle表示该序列化对象的引用(TemplatesImpl对象的newHandle引用),也就是说当其他地方需要引用该序列化对象时,就会使用TC_REFERENCE来标记Handle。
通过分析writeObject方法我们知道queue队列中的数据仍然可以参与自定义序列化与反序列化,需要注意的是必须自己定义和控制数据解析的规则,说白了就是你怎么序列化的,就必须怎么反序列化,如果不遵守规范数据就会出错。
关于queue队列中为什么要添加2个元素的问题?原因在于,queue队列在反序列化过程中readObject方法中size属性会记录队列中元素的个数,接着调用了一个heapify方法,且该方法内部的for循环会判断size的个数,如果size值小于2的话不会调用siftDown方法,也不会调用comparator比较器的compare方法进行排序,最终不会触发之前构造的利用链。
private void heapify()
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
以上是关于9-java安全——关于构造CC2链的几个问题的主要内容,如果未能解决你的问题,请参考以下文章