PHP 中的 RASP 实现流程分析
作者:yolo_yyh
一、什么是 RASP
RASP 全称是 Runtime Application self-protection,即运行时应用自我保护,这是一种嵌入到应用程序内部,实时检测来自外部的请求、输入的技术。PHP 的 RASP 是通过 PHP 拓展的形式嵌入到PHP 的解释器中。
RASP(Runtime Application self-protection)是一种在运行时检测攻击并且进行自我保护的一种技术。早在2012年,Gartner就开始关注RASP,惠普、WhiteHat Security等多家国外安全公司陆续推出RASP产品,时至今日,惠普企业的软件部门出售给了Micro Focus,RASP产品Application Defender随之易主。而在国内,去年知道创宇KCon大会兵器谱展示了JavaRASP,前一段时间,百度开源了OpenRASP,去年年底,360的0kee团队开始测试Skywolf,虽然没有看到源码和文档,但它的设计思路或许跟RASP类似。而商业化的RASP产品有OneAPM的OneRASP和青藤云的自适应安全产品。在国内,这两家做商业化RASP产品做得比较早。
二、PHP 拓展简介
PHP 在不同的环境下有不同的工作模式,常见的有:命令行下的单进程模式和 Apache 环境下的多进程或者多线程模式。但不管是哪种模式下,都需要执行以下几个流程:
图1 单进程拓展执行流程
单进程模式下整个 PHP 的生命周期为:
图2 单进程生命周期
多进程模式下的生命周期:
图3 多进程生命周期
多进程下每个进程只执行一次模块初始化和模块关闭,会不断执行请求初始化-处理请求-请求关闭的过程。多线程模式下类似,只是处理请求的是线程。
因此我们可以在模块初始化(MINIT)或者请求初始化(RINIT)阶段 hook,这样每次处理请求的就是我们的业务逻辑函数,可以在我们的业务逻辑函数中对输入、或者请求进行监测,判断出异常后即可上报风险。
三、PHP 的 HOOK 实现
想要了解 hook 的方式,需要先看一下PHP对脚本的处理流程。
PHP 对脚本进行词法分析和语分析后会生成 OPArray,也就是 OPCode 的数组,每个 OPCode 都代表一种不同的操作,名称类似下面这种:
ZEND_ADD:执行两个操作数的算术加法操作;
ZEND_EXIT:退出PHP执行;
Zend VM中则存在一个主分支循环(while(1)死循环),只有当执行的 opcode 的 handler 的返回值是1(ZEND_VM_RETURN())时,这个循环才会结束,所以编译器会为每个 PHP 脚本在最后添加一个 RETURN 的 OPCode。
以 ZEND_ADD 这个 opcode 为例,这个结构体里包含有两个操作数(op1和op2)、handler(函数指针)、result(运算后的结果)。Zend VM 会根据两个操作数的类型,找到对应的handler,在源码中对 ZEND_ADD 这个 opcode 的 handler 定义如下:
ZEND_VM_HANDLER(1, ZEND_ADD, CONST|TMP|VAR|CV, CONST|TMP|VAR|CV) { USE_OPLINE zend_free_op free_op1, free_op2; SAVE_OPLINE(); fast_add_function(&EX_T(opline->result.var).tmp_var, GET_OP1_ZVAL_PTR(BP_VAR_R), GET_OP2_ZVAL_PTR(BP_VAR_R) TSRMLS_CC); FREE_OP1(); FREE_OP2(); CHECK_EXCEPTION(); ZEND_VM_NEXT_OPCODE(); }
函数后两个参数分别代表 op1 和 op2 可接受的操作数类型。
处理工具会根据这个函数的定义,对 op1 和 op2 进行类型组合,生成16个处理特定类型的 handler函数。这些 handler 函数命名如下:
static int ZEND_FASTCALL ZEND_ADD_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { /* handler code */ } static int ZEND_FASTCALL ZEND_ADD_SPEC_CONST_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { /* handler code */ } static int ZEND_FASTCALL ZEND_ADD_SPEC_CONST_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { /* handler code */ } static int ZEND_FASTCALL ZEND_ADD_SPEC_CONST_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { /* handler code */ } static int ZEND_FASTCALL ZEND_ADD_SPEC_TMP_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { /* handler code */ } static int ZEND_FASTCALL ZEND_ADD_SPEC_TMP_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { /* handler code */ } ......
规律为:static int ZEND_FASTCALL OPCode_SPEC_{OP1-TYPE}_{OP2-TYPE}_HANDLER
所以最终执行哪个 handler 是根据需要两个操作数的类型决定的。
所以我们可以替换OPCode的handler,刚好源码中有对应的接口zend_set_user_opcode_handler(zend_uchar opcode, user_opcode_handler_t handler)可供使用。
除了 OPCode 外,PHP 还有很多内置函数,比如 sprintf、 system、usort 等等,这些函数是没有OPcode 的,但是这些函数都被保存在了全局函数表里,可以通过 CG(function_table) 获取,这些函数也有对应的handler函数指针,所以我们可以直接备份原先的 handler 后使用 function->internal_function.handler = new_handler 替换即可。
到此这篇关于PHP 中的 RASP 实现流程分析的文章就介绍到这了,更多相关PHP 的 RASP 实现内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!