初探php拓展层面(二)
字数 917 2025-08-26 22:11:22

PHP扩展开发:污点标记实现原理详解

1. 污点标记概述

污点标记是一种安全机制,用于跟踪不可信数据的传播。核心思想是:

  • 所有传入的数据(如HTTP请求参数)都被视为不可信的,需要打上标记
  • 被打上标记的数据在传播(如字符串拼接)时会保持标记
  • 经过安全处理函数(如addslashes)后可以清除标记

2. PHP变量结构与标记位置

2.1 PHP7中的实现

PHP7利用zend_refcounted_h结构体中的flags字段进行标记:

typedef union _zend_value {
    zend_long lval;
    double dval;
    zend_refcounted *counted;
    zend_string *str;
    zend_array *arr;
    zend_object *obj;
    zend_resource *res;
    zend_reference *ref;
    zend_ast_ref *ast;
    zval *zv;
    void *ptr;
    zend_class_entry *ce;
    zend_function *func;
    struct {
        uint32_t w1;
        uint32_t w2;
    } ww;
} zend_value;

typedef struct _zend_refcounted_h {
    uint32_t refcount; /* reference counter 32-bit */
    union {
        struct {
            ZEND_ENDIAN_LOHI_3(
                zend_uchar type,
                zend_uchar flags, /* used for strings & objects */
                uint16_t gc_info) /* keeps GC root number (or 0) and color */
        } v;
        uint32_t type_info;
    } u;
} zend_refcounted_h;

关键点:使用zend_uchar flags中未被使用的位作为污染标记位。

2.2 PHP5中的实现

PHP5由于结构体限制,采用在字符串末尾添加标记的方式:

typedef union _zvalue_value {
    long lval;
    double dval;
    struct {
        char *val;
        int len;
    } str;
    HashTable *ht;
    zend_object_value obj;
    zend_ast *ast;
} zvalue_value;

struct _zval_struct {
    zvalue_value value;
    zend_uint refcount__gc;
    zend_uchar type;
    zend_uchar is_ref__gc;
};

实现方式是在字符串末尾分配额外空间存储标记:

#define PHP_TAINT_MAGIC_LENGTH sizeof(unsigned)
#define PHP_TAINT_MAGIC_NONE 0x00000000
#define PHP_TAINT_MAGIC_POSSIBLE 0x6A8FCE84
#define PHP_TAINT_MAGIC_UNTAINT 0x2C5E7F2D

#define PHP_TAINT_MARK(zv, mark) *((unsigned *)(Z_STRVAL_P(zv) + Z_STRLEN_P(zv) + 1)) = (mark)
#define PHP_TAINT_POSSIBLE(zv) (*(unsigned *)(Z_STRVAL_P(zv) + Z_STRLEN_P(zv) + 1) == PHP_TAINT_MAGIC_POSSIBLE)
#define PHP_TAINT_UNTAINT(zv) (*(unsigned *)(Z_STRVAL_P(zv) + Z_STRLEN_P(zv) + 1) == PHP_TAINT_MAGIC_UNTAINT)

实现原理

  1. 使用erealloc为字符串分配额外空间
  2. 在字符串末尾+1的位置存储标记值
  3. 标记值可以是POSSIBLE(污染)、UNTAINT(已净化)或NONE(无标记)

3. HTTP请求参数标记实现

3.1 获取HTTP请求参数

PHP中HTTP请求参数存储在全局变量中:

#define TRACK_VARS_POST 0
#define TRACK_VARS_GET 1
#define TRACK_VARS_COOKIE 2
#define TRACK_VARS_SERVER 3
#define TRACK_VARS_ENV 4
#define TRACK_VARS_FILES 5
#define TRACK_VARS_REQUEST 6

获取GET参数的示例:

HashTable *ht;
zval *arr;
arr = PG(http_globals)[TRACK_VARS_GET];
ht = HASH_OF(arr);

3.2 遍历和标记参数

递归标记所有请求参数的实现:

PHP_RINIT_FUNCTION(ptaint) {
    if (PG(http_globals)[TRACK_VARS_GET] && 
        zend_hash_num_elements(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_GET]))) {
        php_taint_mark_arr(PG(http_globals)[TRACK_VARS_GET] TSRMLS_CC);
    }
    return SUCCESS;
}

static void php_taint_mark_arr(zval *symbol_table TSRMLS_DC) {
    zval **data;
    HashTable *ht = Z_ARRVAL_P(symbol_table);
    
    for (zend_hash_internal_pointer_reset(ht); 
         zend_hash_has_more_elements(ht) == SUCCESS; 
         zend_hash_move_forward(ht)) {
        
        if (zend_hash_get_current_data(ht, (void **)&data) == FAILURE)
            continue;
            
        if (Z_TYPE_PP(data) == IS_ARRAY) {
            php_taint_mark_arr(*data TSRMLS_CC);
        } 
        else if (Z_TYPE_PP(data) == IS_STRING) {
            Z_STRVAL_PP(data) = erealloc(Z_STRVAL_PP(data), 
                                        Z_STRLEN_PP(data) + 1 + PHP_TAINT_MAGIC_LENGTH);
            PHP_TAINT_MARK(*data, PHP_TAINT_MAGIC_POSSIBLE);
        }
    }
}

关键步骤

  1. 在请求初始化阶段(RINIT)调用标记函数
  2. 递归处理数组和字符串
  3. 对字符串参数:
    • 使用erealloc扩展字符串存储空间
    • 在末尾添加污染标记PHP_TAINT_MAGIC_POSSIBLE

4. 关键宏定义解析

// 标记长度(unsigned类型的大小)
#define PHP_TAINT_MAGIC_LENGTH sizeof(unsigned)

// 标记值定义
#define PHP_TAINT_MAGIC_NONE 0x00000000
#define PHP_TAINT_MAGIC_POSSIBLE 0x6A8FCE84
#define PHP_TAINT_MAGIC_UNTAINT 0x2C5E7F2D

// 设置标记(在字符串末尾+1的位置)
#define PHP_TAINT_MARK(zv, mark) *((unsigned *)(Z_STRVAL_P(zv) + Z_STRLEN_P(zv) + 1)) = (mark)

// 检查标记
#define PHP_TAINT_POSSIBLE(zv) (*(unsigned *)(Z_STRVAL_P(zv) + Z_STRLEN_P(zv) + 1) == PHP_TAINT_MAGIC_POSSIBLE)
#define PHP_TAINT_UNTAINT(zv) (*(unsigned *)(Z_STRVAL_P(zv) + Z_STRLEN_P(zv) + 1) == PHP_TAINT_MAGIC_UNTAINT)

5. 哈希表遍历相关函数

// 重置哈希表内部指针
zend_hash_internal_pointer_reset(ht);

// 检查是否还有元素
zend_hash_has_more_elements(ht) == SUCCESS;

// 移动指针到下一个元素
zend_hash_move_forward(ht);

// 获取当前键名
zend_hash_get_current_key(ht, &str_index, &num_index, 0);

// 获取当前值
zend_hash_get_current_data(ht, (void **)&data);

6. 实现注意事项

  1. PHP版本兼容性

    • PHP7使用flags字段更高效
    • PHP5需要在字符串末尾添加标记
  2. 内存管理

    • 使用erealloc扩展字符串空间
    • 确保不会破坏原有字符串结构
  3. 递归处理

    • 需要处理嵌套数组的情况
    • 只对字符串类型打标记
  4. 性能考虑

    • 标记操作在请求初始化阶段完成
    • 尽量减少内存分配次数

7. 扩展开发基础

创建扩展的基本步骤:

  1. 定义扩展基本信息
  2. 实现函数和钩子
  3. 注册PHP函数
  4. 实现模块初始化和请求初始化

示例扩展结构:

// 定义函数
PHP_FUNCTION(confirm_foobar_compiled);

// 定义模块和请求初始化函数
PHP_MINIT_FUNCTION(ptaint);
PHP_RINIT_FUNCTION(ptaint);

// 定义模块结构
zend_module_entry ptaint_module_entry = {
    STANDARD_MODULE_HEADER,
    "ptaint",
    NULL,
    PHP_MINIT(ptaint),
    NULL,
    PHP_RINIT(ptaint),
    NULL,
    NULL,
    "0.1",
    STANDARD_MODULE_PROPERTIES
};

8. 参考资源

  1. PHP变量内部实现
  2. PHP扩展开发指南
  3. PHP代码安全分析
PHP扩展开发:污点标记实现原理详解 1. 污点标记概述 污点标记是一种安全机制,用于跟踪不可信数据的传播。核心思想是: 所有传入的数据(如HTTP请求参数)都被视为不可信的,需要打上标记 被打上标记的数据在传播(如字符串拼接)时会保持标记 经过安全处理函数(如 addslashes )后可以清除标记 2. PHP变量结构与标记位置 2.1 PHP7中的实现 PHP7利用 zend_refcounted_h 结构体中的 flags 字段进行标记: 关键点 :使用 zend_uchar flags 中未被使用的位作为污染标记位。 2.2 PHP5中的实现 PHP5由于结构体限制,采用在字符串末尾添加标记的方式: 实现方式是在字符串末尾分配额外空间存储标记: 实现原理 : 使用 erealloc 为字符串分配额外空间 在字符串末尾+1的位置存储标记值 标记值可以是 POSSIBLE (污染)、 UNTAINT (已净化)或 NONE (无标记) 3. HTTP请求参数标记实现 3.1 获取HTTP请求参数 PHP中HTTP请求参数存储在全局变量中: 获取GET参数的示例: 3.2 遍历和标记参数 递归标记所有请求参数的实现: 关键步骤 : 在请求初始化阶段( RINIT )调用标记函数 递归处理数组和字符串 对字符串参数: 使用 erealloc 扩展字符串存储空间 在末尾添加污染标记 PHP_TAINT_MAGIC_POSSIBLE 4. 关键宏定义解析 5. 哈希表遍历相关函数 6. 实现注意事项 PHP版本兼容性 : PHP7使用 flags 字段更高效 PHP5需要在字符串末尾添加标记 内存管理 : 使用 erealloc 扩展字符串空间 确保不会破坏原有字符串结构 递归处理 : 需要处理嵌套数组的情况 只对字符串类型打标记 性能考虑 : 标记操作在请求初始化阶段完成 尽量减少内存分配次数 7. 扩展开发基础 创建扩展的基本步骤: 定义扩展基本信息 实现函数和钩子 注册PHP函数 实现模块初始化和请求初始化 示例扩展结构: 8. 参考资源 PHP变量内部实现 PHP扩展开发指南 PHP代码安全分析