从0开始的PHP RASP的学习
字数 1817 2025-08-22 12:23:36
PHP RASP 实现原理与开发指南
1. RASP 基础概念
RASP (Runtime Application self-protection) 是一种在运行时检测攻击并进行自我保护的技术。PHP RASP 的核心设计思路是:
- 跟踪所有客户端可控的变量(HTTP 请求参数、Header 等)
- 监控这些变量在程序中的流动和使用
- 选取敏感函数(如
require,mysqli->query等)添加安全检查代码 - 当被跟踪的信息流入敏感函数时触发安全检查
- 检查通过则执行原函数,不通过则阻断执行并返回 403
2. 技术实现方案
2.1 污点分析模式 (Taint)
- 跟踪参数传递过程,判断清除或保留标记
- 适用于检测未知攻击模式
- 主要检测:
- 命令执行
- XSS
- SQL 注入
2.2 Payload 模式
- 重命名函数 + PHP WAF 实现
- 特征捕获检测
- 忽略参数传递过程,只分析最后作用于敏感函数的参数是否恶意
- 适用于上线前 Fuzz 检测
3. PHP 核心机制
3.1 PHP 生命周期
PHP 程序执行的四个阶段:
- 模块初始化 (MINIT) -
PHP_MINIT_FUNCTION - 请求初始化 (RINIT) -
PHP_RINIT_FUNCTION - 请求处理
- 请求结束 (RSHUTDOWN) -
PHP_RSHUTDOWN_FUNCTION - 模块结束 (MSHUTDOWN) -
PHP_MSHUTDOWN_FUNCTION
3.2 PHP Opcode
PHP 代码执行流程:
PHP 代码 → 词法/语法分析 → 生成 AST → 转换为 Opcode → Zend 虚拟机执行
Opcode 是 PHP 代码在 Zend 虚拟机中执行的指令形式。
4. 函数实现机制
PHP 中函数的存储结构 (zend_function):
union _zend_function {
zend_uchar type;
struct {
// 公共头部
zend_uchar type;
zend_uchar arg_flags[3];
uint32_t fn_flags;
zend_string *function_name;
zend_class_entry *scope;
union _zend_function *prototype;
uint32_t num_args;
uint32_t required_num_args;
zend_arg_info *arg_info;
} common;
zend_op_array op_array; // 用户自定义函数
zend_internal_function internal_function; // 内部函数
};
5. 环境搭建
5.1 开发环境准备
-
下载 PHP 源码:
wget https://github.com/php/php-src/archive/php-7.0.33.zip -
创建扩展骨架:
./ext_skel --extname=passer6y -
修改
config.m4文件,去掉相关行的dnl注释 -
声明和实现扩展函数:
// php_passer6y.h PHP_FUNCTION(passer6y_helloworld); // passer6y.c PHP_FUNCTION(passer6y_helloworld) { char *arg = NULL; int arg_len, len; char *strg; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) { return; } php_printf("my first ext,Hello World!\n"); RETURN_TRUE; } -
编译安装扩展:
apt-get install php7.0-dev phpize ./configure --with-php-config=/usr/bin/php-config7.0 make && make install
5.2 GDB 调试环境
-
编译带调试信息的 PHP:
./configure --prefix=/opt/php_debug/ --enable-debug --enable-cli --without-pear --enable-embed --enable-inline-optimization --enable-shared --enable-opcache --enable-fpm --with-gettext --enable-mbstring --with-iconv=/usr/local/libiconv make && make install -
创建调试用 PHP:
ln -s /opt/php_debug/bin/php /usr/bin/php_debug ln -s /opt/php_debug/bin/phpize /usr/bin/phpize_debug
5.3 VLD 扩展安装
查看 PHP 代码的 Opcode:
wget http://pecl.php.net/get/vld-0.14.0.tgz
tar zxvf vld-0.14.0.tgz
cd vld-0.14.0/
./configure --with-php-config=/usr/bin/php-config7.0 --enable-vld
make && make install
在 php.ini 中添加:
extension=vld.so
使用 VLD:
php -dvld.active=1 -dvld.execute=0 test.php
6. 函数 Hook 技术
6.1 重命名函数
在 MINIT 阶段重命名内部函数:
static zend_always_inline Bucket *rename_hash_key(HashTable *ht, zend_string *orig_name, zend_string *new_name, int type) {
// 检查新名称是否已存在
if (zend_hash_exists(ht, new_name)) {
zend_error(E_ERROR, "function/class '%s' already exists", ZSTR_VAL(new_name));
return NULL;
}
// 查找原函数
// ... 查找逻辑
// 重命名函数
zend_string_release(p->key);
p->key = zend_string_init_interned(ZSTR_VAL(new_name), ZSTR_LEN(new_name), 1);
p->h = h = zend_string_hash_val(p->key);
if (type == XMARK_IS_FUNCTION) {
zend_string_release(p->val.value.func->common.function_name);
zend_string_addref(p->key);
p->val.value.func->common.function_name = p->key;
}
return p;
}
6.2 Hook Opcode
对于语言特性(如 echo, eval)需要通过 Hook Opcode 实现:
// passer6y.h
int fake_echo(ZEND_OPCODE_HANDLER_ARGS);
// passer6y.c
int fake_echo(ZEND_OPCODE_HANDLER_ARGS) {
php_printf("hook success");
return ZEND_USER_OPCODE_RETURN;
}
// MINIT 函数中注册
zend_set_user_opcode_handler(ZEND_ECHO, fake_echo);
关键 Opcode 需要 Hook:
INCLUDE_OR_EVAL- 包含eval,include,require等DO_ICALL- 如system等内部函数调用DO_FCALL- 变量函数调用,如$a="system"; $a("whoami");
7. 污点标记实现
7.1 标记函数实现
PHP_FUNCTION(xmark) {
zval *z_str;
if (!XMARK_G(enable)) {
RETURN_FALSE;
}
if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &z_str) == FAILURE) {
return;
}
ZVAL_DEREF(z_str); // 解引用
if (IS_STRING != Z_TYPE_P(z_str) || Z_STRLEN_P(z_str) == 0) {
RETURN_FALSE;
}
if (xmark_zstr(z_str) == FAILURE) {
RETURN_FALSE;
}
RETURN_TRUE;
}
static zend_always_inline int xmark_zstr(zval *z_str) {
if (!XCHECK_FLAG(Z_STR_P(z_str))) {
zend_string *str = zend_string_init(Z_STRVAL_P(z_str), Z_STRLEN_P(z_str), 0);
ZSTR_LEN(str) = Z_STRLEN_P(z_str);
zend_string_release(Z_STR_P(z_str));
XMARK_FLAG(str);
ZVAL_STR(z_str, str);
}
return SUCCESS;
}
7.2 标记位定义
#if PHP_VERSION_ID < 70300
# define IS_XMARK_FLAG (1<<6)
# define XMARK_FLAG(str) (GC_FLAGS((str)) |= IS_XMARK_FLAG)
# define XCLEAR_FLAG(str) (GC_FLAGS((str)) &= ~IS_XMARK_FLAG)
# define XCHECK_FLAG(str) (GC_FLAGS((str)) & IS_XMARK_FLAG)
#else
# define IS_XMARK_FLAG (1<<5)
# define XMARK_FLAG(str) GC_ADD_FLAGS(str, IS_XMARK_FLAG)
# define XCLEAR_FLAG(str) GC_DEL_FLAGS(str, IS_XMARK_FLAG)
# define XCHECK_FLAG(str) (GC_FLAGS((str)) & IS_XMARK_FLAG)
#endif
8. 威胁判断流程
-
Source 点标记 - 标记所有输入源:
prvd_xmark($_GET, true); prvd_xmark($_POST, true); prvd_xmark($_COOKIE, true); prvd_xmark($_FILES, true); prvd_xmark($_REQUEST, true); foreach ($_SERVER as $key => &$value) { if (stripos($key, 'HTTP_') === 0) { prvd_xmark($value); } } -
Filter 点传递 - 在数据处理过程中传递标记:
function base64_decode($data, ...$args) { $result = call_user_func(PRVD_RENAME_PREFIX."base64_decode", $data, ...$args); if (PRVD_TAINT_ENABLE && prvd_xcheck($data)) { prvd_xmark($result); } return $result; } -
Sink 点检测 - 在敏感函数处检测标记:
static int php_do_fcall_handler(zend_execute_data *execute_data) { // 获取函数名和参数 // 检查是否为危险函数 // 检查参数是否被标记 // 根据检查结果决定是否阻断执行 }
9. 实现示例
9.1 敏感函数 Hook 示例
static int php_do_fcall_handler(zend_execute_data *execute_data) {
const zend_op *opline = execute_data->opline;
zend_execute_data *call = execute_data->call;
zend_function *fbc = call->func;
if (fbc->type == ZEND_INTERNAL_FUNCTION) {
int arg_count = ZEND_CALL_NUM_ARGS(call);
if (!arg_count) {
return ZEND_USER_OPCODE_DISPATCH;
}
if (fbc->common.scope == NULL) {
zend_string *fname = fbc->common.function_name;
char *funcname = ZSTR_VAL(fname);
int len = strlen(funcname);
if (fname) {
if (strncmp("passthru", funcname, len) == 0
|| strncmp("system", funcname, len) == 0
|| strncmp("exec", funcname, len) == 0
|| strncmp("shell_exec", funcname, len) == 0
|| strncmp("proc_open", funcname, len) == 0 ) {
zend_error(E_WARNING, funcname);
}
}
}
}
return ZEND_USER_OPCODE_DISPATCH;
}
9.2 参数获取与警告
static void php_warning(const char *fname, const char *arg, const char *format, ...) {
char *buffer, *msg;
va_list args;
va_start(args, format);
vspprintf(&buffer, 0, format, args);
spprintf(&msg, 0, "%s(\"%s\"): %s", fname, arg, buffer);
efree(buffer);
zend_error(E_WARNING, msg);
efree(msg);
va_end(args);
}
10. 总结
10.1 技术要点
-
两种检测模式:
- 污点分析:跟踪数据流,适合检测未知攻击
- Payload 模式:特征检测,适合上线前 Fuzz
-
关键实现技术:
- 函数重命名
- Opcode Hook
- 污点标记传递
-
主要 Hook 点:
INCLUDE_OR_EVALDO_ICALLDO_FCALL
10.2 优缺点
污点分析优点:
- 能检测未知攻击模式
- 不依赖特定 payload
- 覆盖全面
Payload 模式缺点:
- 多入口文件时容易遗漏
- 检测精度依赖 payload 质量
- 需要维护特征库