0x00 简介
曾遇到一个反序列化漏洞,没有找到利用链,但是存在DOMDocument类的加载
当我们实例化一个新的DOMDocument类的时候,通过var_dump,可以看到类的成员变量
但是当使用序列化函数,进行序列化的时候,无法将成员变量进行序列化
当然我们知道肯定不是继承自不允许反序列化的接口,比如大文件操作类SplFileObject
区别如下

但是,其他的内置类是可以序列化成员变量的
比如DateTime,比如Exception,当然这其中又存在一些差别
本文就以这三个类,进行分析
DOMDocumentDateTimeException
0x01 前置知识
只读属性
这里只有dom类型的类才存在只读属性,但是在php中,是php8才具有的性质
不知道怎么设置的,在php7中也存在关键字readonly,但是没找到怎么设置参数
但是在dom类中,不仅仅存在只读属性,常规属性同样不能被序列化,这里猜测,和只读没关系

看到一个个人设置只读属性的例子,并不是通过什么关键字来设置的
1 |
|
当然,存在这个属性的参数是没办法通过反序列化赋值的,但是其他的参数可以


这里还需要一些关于C语言的前置知识
宏定义
定义方式如下,简单说调用宏定义的时候,就是替换成定义的内容

结构体
借用key师傅的文档一下
PHP7中存在一个zval的结构体,可以用来表示任意变量类型
1 | struct _zval_struct { |
u1
1 | union { |
u2
1 | union { |
u1中的type表示的数据类型

值一半存储在value中
1 | typedef union _zend_value { |
0x02 调试
这里为了实现对比,实验数据用四个类
DOMDocument:无法序列化DateTime:可以序列化Exception:可以序列化- 自定义类:可以序列化
公共流程
走一下一个类的序列化的基本流程,测试代码
1 | $now = new DateTime(); |
在序列化函数处下断点,经过一番调试,这里直接给出正确的数据获取路径了

跟进 php_var_serialize函数,然后继续跟进php_var_serialize_intern函数
此时的buf中都是空

此时的结构体的type是8,就是我们序列化的类代表是obj

然后会进入对应的IS_OBJECT分支,此分支用来处理待序列化的类中是否存在__sleep魔术方法,或者是否重写了serialize方法

都不存在的化,就没return,会进入IS_ARRAY分支
由于我们的数据类型并不是数组,所以进入else分支,通过php_var_serialize_class_name函数完成类名的序列化操作
此时的buf中已经有了数据

差异流程
走到这里差异就逐步体现出来了,这里需要提到两个关键参数
default_properties_count、default_properties_table和nNumOfElements
是否存在默认值
对比对象
DOMDocumentException
主要就是对这个default_properties_count和default_properties_table
定义的类的结构体_zend_class_entry,第六个和第八个参数


查找一下类的定义,先定义类,在定义7个参数,这些都会在编译时进入HashTable(EG)

看一下调试
1 | $e = new Exception('aaaa'); |

这也就说明可以序列化的普通属性有7个

而DOM类型的类中并没有定义默认值
1 | $encoding = "UTF-8"; |

是否自定义了获取properties
对比对象
DOMDocumentDateTime
这里的关键点就是Z_OBJPROP_P,这个宏定义
我们去查看其到底替换成了什么
(zval).value.obj->handlers->get_properties(&(zval))

而这个DateTime类对这个函数进行了定义
对应的就是date_object_get_properties函数

看调试,成功进入此方法

而此处的zend_std_get_properties方法,就是在未定义的时候直接进入的

在这里面出现了第一个关键参数default_properties_count
但是date的这个参数为0,所以并不能将所有的类成员变量都序列化

return之后,存在自定义方法的开始对开始对props,进行更新
这里的更新主要是三个参数

这里是第二个关键参数nNumOfElements

在获取个数的时候,涉及到了另外一个宏定义zend_hash_num_elements

就是直接获取之前的参数值

然后就开始了正常的拼接流程

对吼序列化出来的数据,也只是会有之前的三个

而dom类型的类中并没有定义get_properties

所以最终获取到的参数个数是0

自定义的类
1 | class A{ |
自定义的类的参数,就相当于是普通属性

0x03 end
但是反序列化的时候是可以的,需要自己写一个脚本












