从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 程序执行的四个阶段:

  1. 模块初始化 (MINIT) - PHP_MINIT_FUNCTION
  2. 请求初始化 (RINIT) - PHP_RINIT_FUNCTION
  3. 请求处理
  4. 请求结束 (RSHUTDOWN) - PHP_RSHUTDOWN_FUNCTION
  5. 模块结束 (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 开发环境准备

  1. 下载 PHP 源码:

    wget https://github.com/php/php-src/archive/php-7.0.33.zip
    
  2. 创建扩展骨架:

    ./ext_skel --extname=passer6y
    
  3. 修改 config.m4 文件,去掉相关行的 dnl 注释

  4. 声明和实现扩展函数:

    // 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;
    }
    
  5. 编译安装扩展:

    apt-get install php7.0-dev
    phpize
    ./configure --with-php-config=/usr/bin/php-config7.0
    make && make install
    

5.2 GDB 调试环境

  1. 编译带调试信息的 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
    
  2. 创建调试用 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. 威胁判断流程

  1. 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);
        }
    }
    
  2. 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;
    }
    
  3. 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 技术要点

  1. 两种检测模式:

    • 污点分析:跟踪数据流,适合检测未知攻击
    • Payload 模式:特征检测,适合上线前 Fuzz
  2. 关键实现技术:

    • 函数重命名
    • Opcode Hook
    • 污点标记传递
  3. 主要 Hook 点:

    • INCLUDE_OR_EVAL
    • DO_ICALL
    • DO_FCALL

10.2 优缺点

污点分析优点

  • 能检测未知攻击模式
  • 不依赖特定 payload
  • 覆盖全面

Payload 模式缺点

  • 多入口文件时容易遗漏
  • 检测精度依赖 payload 质量
  • 需要维护特征库

11. 参考资源

  1. 替换PHP底层函数实现
  2. 从PHP源码与扩展开发谈PHP任意代码执行与防御
  3. php7内核剖析
  4. xmark: A PHP7 extension that can hook most functions/classes and parts of opcodes
  5. 一类PHP RASP实现
  6. PHP 运行时漏洞检测
  7. 毕业设计之php RASP
  8. taint: Taint is a PHP extension, used for detecting XSS codes
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 代码执行流程: Opcode 是 PHP 代码在 Zend 虚拟机中执行的指令形式。 4. 函数实现机制 PHP 中函数的存储结构 ( zend_function ): 5. 环境搭建 5.1 开发环境准备 下载 PHP 源码: 创建扩展骨架: 修改 config.m4 文件,去掉相关行的 dnl 注释 声明和实现扩展函数: 编译安装扩展: 5.2 GDB 调试环境 编译带调试信息的 PHP: 创建调试用 PHP: 5.3 VLD 扩展安装 查看 PHP 代码的 Opcode: 在 php.ini 中添加: 使用 VLD: 6. 函数 Hook 技术 6.1 重命名函数 在 MINIT 阶段重命名内部函数: 6.2 Hook Opcode 对于语言特性(如 echo , eval )需要通过 Hook Opcode 实现: 关键 Opcode 需要 Hook: INCLUDE_OR_EVAL - 包含 eval , include , require 等 DO_ICALL - 如 system 等内部函数调用 DO_FCALL - 变量函数调用,如 $a="system"; $a("whoami"); 7. 污点标记实现 7.1 标记函数实现 7.2 标记位定义 8. 威胁判断流程 Source 点标记 - 标记所有输入源: Filter 点传递 - 在数据处理过程中传递标记: Sink 点检测 - 在敏感函数处检测标记: 9. 实现示例 9.1 敏感函数 Hook 示例 9.2 参数获取与警告 10. 总结 10.1 技术要点 两种检测模式: 污点分析:跟踪数据流,适合检测未知攻击 Payload 模式:特征检测,适合上线前 Fuzz 关键实现技术: 函数重命名 Opcode Hook 污点标记传递 主要 Hook 点: INCLUDE_OR_EVAL DO_ICALL DO_FCALL 10.2 优缺点 污点分析优点 : 能检测未知攻击模式 不依赖特定 payload 覆盖全面 Payload 模式缺点 : 多入口文件时容易遗漏 检测精度依赖 payload 质量 需要维护特征库 11. 参考资源 替换PHP底层函数实现 从PHP源码与扩展开发谈PHP任意代码执行与防御 php7内核剖析 xmark: A PHP7 extension that can hook most functions/classes and parts of opcodes 一类PHP RASP实现 PHP 运行时漏洞检测 毕业设计之php RASP taint: Taint is a PHP extension, used for detecting XSS codes