初探php扩展层面(三)
字数 898 2025-08-26 22:11:28
PHP扩展开发:污点标记与传播机制深入解析
一、污点标记基础概念
1.1 污点标记原理
污点标记(Taint)是一种安全机制,用于追踪用户输入的不可信数据在程序中的传播过程。在PHP扩展层面实现污点标记需要:
- 对所有用户输入变量打上标记
- 跟踪标记在程序执行过程中的传播
- 在危险操作点检测标记存在情况
1.2 污点清除与保留规则
- 清除标记:经过安全处理函数如
mysql_real_escape_string、addslashes、htmlentities等 - 保留标记:经过可能保留危险性的函数如
base64_decode、strtolower或字符串拼接操作 - 中间态处理:对于全局转义后又经过
stripslashes的情况,可设计中间态机制
二、操作数类型详解
PHP虚拟机操作数类型定义(zend_compile.h):
#define IS_CONST (1<<0) // 字面量,编译时可确定且不会改变的值
#define IS_TMP_VAR (1<<1) // 临时变量,如表达式计算结果
#define IS_VAR (1<<2) // PHP变量(非脚本中显式定义的)
#define IS_UNUSED (1<<3) // 未使用的操作数
#define IS_CV (1<<4) // 编译变量(脚本中通过$var_name定义的)
三、污点传播实现机制
3.1 操作数获取方法
根据不同操作数类型采用不同获取策略:
- IS_TMP_VAR:使用
get_zval_ptr_tmp获取 - IS_VAR:从
opline->var.ptr获取 - IS_CV:从
EG(active_symbol_table)中查找 - IS_CONST:直接获取
opline->op1.zv
3.2 关键函数实现
临时变量获取实现
#define PTAINT_T(offset) (*EX_TMP_VAR(execute_data, offset))
static zend_always_inline zval *_get_zval_ptr_tmp(zend_uint var,
const zend_execute_data *execute_data, zend_free_op *should_free TSRMLS_DC) {
return should_free->var = &EX_T(var).tmp_var;
}
变量获取实现
static zval *ptaint_get_zval_ptr_var(zend_uint var,
const zend_execute_data *execute_data, zend_free_op *should_free TSRMLS_DC) {
zval *ptr = PTAINT_T(var).var.ptr;
return should_free->var = ptr;
}
编译变量查找
static zval **ptaint_get_zval_cv_lookup(zval ***ptr, zend_uint var,
int type TSRMLS_DC) {
zend_compiled_variable *cv = &CV_DEF_OF(var);
if (!EG(active_symbol_table) ||
zend_hash_quick_find(EG(active_symbol_table), cv->name,
cv->name_len+1, cv->hash_value, (void **)ptr) == FAILURE) {
// 处理未定义变量情况
}
return *ptr;
}
四、危险操作检测实现
4.1 OPCode Hook示例
static int hook_include_or_eval(ZEND_OPCODE_HANDLER_ARGS) {
zend_op *opline = execute_data->opline;
zval *op1 = NULL;
zend_free_op free_op1;
switch(PTAINT_OP1_TYPE(opline)) {
case IS_TMP_VAR:
op1 = ptaint_get_zval_ptr_tmp(PTAINT_OP1_GET_VAR(opline),
execute_data, &free_op1 TSRMLS_CC);
break;
case IS_VAR:
op1 = ptaint_get_zval_ptr_var(PTAINT_OP1_GET_VAR(opline),
execute_data, &free_op1 TSRMLS_CC);
break;
case IS_CONST:
op1 = PTAINT_OP1_GET_ZV(opline);
break;
case IS_CV:
op1 = ptaint_get_zval_ptr_cv(PTAINT_OP1_GET_VAR(opline), 0);
}
if(op1 && Z_TYPE_P(op1) == IS_STRING && PHP_TAINT_POSSIBLE(op1)) {
if(opline->extended_value == ZEND_EVAL) {
zend_error(E_WARNING, "(eval): Variables are not safely processed");
} else {
zend_error(E_WARNING,
"(include or require): Variables are not safely processed");
}
}
return ZEND_USER_OPCODE_DISPATCH;
}
五、内部函数Hook机制
5.1 函数重定向实现
static void ptaint_override_func(char *name, uint len,
php_func handler, php_func *stash TSRMLS_DC) {
zend_function *func;
if(zend_hash_find(CG(function_table), name, len,
(void **)&func) == SUCCESS) {
if(stash) {
*stash = func->internal_function.handler;
}
func->internal_function.handler = handler;
}
}
5.2 函数Hook示例(strtoupper)
PHP_FUNCTION(ptaint_strtoupper) {
zval *str;
int tainted = 0;
php_func strtoupper;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &str) == FAILURE) {
return;
}
if(IS_STRING == Z_TYPE_P(str) && PHP_TAINT_POSSIBLE(str)) {
tainted = 1;
}
PTAINT_O_FUNC(strtoupper)(INTERNAL_FUNCTION_PARAM_PASSTHRU);
if(tainted && IS_STRING == Z_TYPE_P(return_value) &&
Z_STRLEN_P(return_value)) {
Z_STRVAL_P(return_value) = erealloc(Z_STRVAL_P(return_value),
Z_STRLEN_P(return_value) + 1 + PHP_TAINT_MAGIC_LENGTH);
PHP_TAINT_MARK(return_value, PHP_TAINT_MAGIC_POSSIBLE);
}
}
六、关键数据结构
6.1 内部函数结构
typedef struct _zend_internal_function {
zend_uchar type;
const char *function_name;
zend_class_entry *scope;
zend_uint fn_flags;
union _zend_function *prototype;
zend_uint num_args;
zend_uint required_num_args;
zend_arg_info *arg_info;
void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
struct _zend_module_entry *module;
} zend_internal_function;
6.2 需要Hook的函数列表
static struct ptaint_overridden_fucs {
php_func strval;
php_func sprintf;
php_func vsprintf;
php_func explode;
php_func implode;
php_func trim;
php_func rtrim;
php_func ltrim;
php_func strstr;
php_func str_pad;
php_func str_replace;
php_func substr;
php_func strtolower;
php_func strtoupper;
} ptaint_origin_funcs;
七、实现注意事项
- 版本兼容性:不同PHP版本中opcode处理方式可能有差异
- 性能考虑:污点跟踪会增加运行时开销,需优化关键路径
- 覆盖处理:Hook函数时需保存原函数指针,避免覆盖重要功能
- 多参数处理:对于多参数函数需分别检查每个参数的污点状态
- 错误处理:完善的错误报告机制帮助定位问题