0x00 简介
曾遇到一个反序列化漏洞,没有找到利用链,但是存在DOMDocument
类的加载
当我们实例化一个新的DOMDocument
类的时候,通过var_dump
,可以看到类的成员变量
但是当使用序列化函数,进行序列化的时候,无法将成员变量进行序列化
当然我们知道肯定不是继承自不允许反序列化的接口,比如大文件操作类SplFileObject
区别如下
但是,其他的内置类是可以序列化成员变量的
比如DateTime
,比如Exception
,当然这其中又存在一些差别
本文就以这三个类,进行分析
DOMDocument
DateTime
Exception
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
是否存在默认值
对比对象
DOMDocument
Exception
主要就是对这个default_properties_count
和default_properties_table
定义的类的结构体_zend_class_entry
,第六个和第八个参数
查找一下类的定义,先定义类,在定义7个参数,这些都会在编译时进入HashTable(EG)
看一下调试
1 | $e = new Exception('aaaa'); |
这也就说明可以序列化的普通属性有7
个
而DOM类型的类中并没有定义默认值
1 | $encoding = "UTF-8"; |
是否自定义了获取properties
对比对象
DOMDocument
DateTime
这里的关键点就是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
但是反序列化的时候是可以的,需要自己写一个脚本