PHP RASP 实现
yolo_yyh 人气:0一、什么是 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 替换即可。
加载全部内容