初探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)
实现原理:
- 使用
erealloc为字符串分配额外空间 - 在字符串末尾+1的位置存储标记值
- 标记值可以是
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);
}
}
}
关键步骤:
- 在请求初始化阶段(
RINIT)调用标记函数 - 递归处理数组和字符串
- 对字符串参数:
- 使用
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. 实现注意事项
-
PHP版本兼容性:
- PHP7使用
flags字段更高效 - PHP5需要在字符串末尾添加标记
- PHP7使用
-
内存管理:
- 使用
erealloc扩展字符串空间 - 确保不会破坏原有字符串结构
- 使用
-
递归处理:
- 需要处理嵌套数组的情况
- 只对字符串类型打标记
-
性能考虑:
- 标记操作在请求初始化阶段完成
- 尽量减少内存分配次数
7. 扩展开发基础
创建扩展的基本步骤:
- 定义扩展基本信息
- 实现函数和钩子
- 注册PHP函数
- 实现模块初始化和请求初始化
示例扩展结构:
// 定义函数
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
};