1. 环境准备:
操作系统: Ubuntu 16.04 64位
PHP版本: 5.5.14
gcc编译组件
编译PHP: 先到PHP官网下在我们需要的PHP目标版本源文件.
http://php.net/releases/
安装GCC: apt install gcc make automake libxml2-dev gdb
解压源码包: tar xvf php-5.5.14.tar.gz
生成Makefile文件: 进入刚才我们解压好的源码目录执行:./configure --enable-debug --prefix=$HOME/php/php-5.5.14;
prefix表示要安装的目录,因为这里我们是搭建调试环境,建议给不同的版本设定不同的目录避免冲突; --enable-debug生成debug版本的程序.
编译安装:make && make installl
安装完毕后我们进入安装目录: cd $HOME/php/php-5.5.14/; 执行./bin/php -v输出版本号:
2. 几个数据结构:
Zval是PHP中最重要的数据结构之一(另一个比较重要的数据结构是hash table),它包含了PHP中的变量值和类型的相关信息,是一个struct,基本结构为:
可以看到zval实际上是_zval_struct结构的一个别名,它包含了四个成员:
value -> zvalue_value类型的一个变量,包含变量的值或引用
refcount__gc -> 变量的引用计数
type -> 变量的类型,类型请查看源文件定义
is_ref__gc -> 标记变量是否是引用变量。对于普通的变量,该值为0,而对于引用型的变量,该值为1。
在源文件中我们可以看到zval.type支持以下几种类型:
zvalue_value: 它是一个联合体,保存真实的变量或引用.
HashTable结构:
Bucket结构:HashTable相当于Array对象,而Bucket相当于Array对象里的某个元素。对于多维数组实际就是HashTable的某个Bucket里存储着另一个HashTable。
3. PHP对象序列化后的数据
通过下面的脚本我们可以生成一个DateTimeZone的一个对象,并打印这个对象序列化后的数据:
从输出的部分看:O表示对象, :12为对象标签的长度, :2表示对象包含两个键值对; 第一个元素键为timezone_type,值是一个Int类型的1, 第二个元素键为timezone,值是一个字符串.这里就是一个DateTimeZone对象序列化后的数据.
当PHP在进行反序列化的时候会将上面字符串解析并填充到zval与zvalue_value等数据结构中.
4. 反序列化
PHP中对应的反序列化函数是:unserialize,在原代码中对应的函数是php_var_unserialize.下面我们通过gdb来看一下,DateTimeZone在内存中的结构.
通过源码我们可以看出来zend_hash_find从HashTable中找出指向timezone的zval指针然后保存到了z_timezone. 然后timezone_initialize会利用z_timezone对tzobj进行初始化,用gdb来看看内存中的数据: 执行next单步指令,将程序运行到return SUCCESS这一行, 然后观察两个参数.
5. 反序列化利用
运行后发现一些有趣的东西, 看到了一些不能识别的字符. 对上反序列化字符串可以发现:s:6:"+08:00"被替换成了i:1123123123,
这个时候应该已经可以猜到了, DataTimeZone在重建的时候把最后一个对象认为是一个指向字符串的指针了(这个值可以随意修改指向当前进程空间的任何位置). 再看看timezone_initialize函数:
本来i:1123123123应该指向一个合法的表示时区的字符串,但现在指针的值经过修改后指向了一个内存空间,可以看到timelib_parse_zone函数如果解析失败,php_error_docref就会将原本应该指向一个合法字符串的指针指向的数据给打印到终端.
领取专属 10元无门槛券
私享最新 技术干货