PHP 版本
Last updated
Last updated
关于PHP扩展开发,可参考 。
以 cli SAPI 为例,其单个请求生命周期如下图所示:
OpenRASP 核心原理为:在 MINIT 阶段,替换全局compiler_globals
的function_table
与class_table
中特定 PHP_FUNCTION 对应的函数指针(封装原有handler,增加前置、后置处理),由此实现对敏感函数的挂钩。通过敏感函数参数结合请求信息判断是否存在攻击行为,进而采取拦截或者放行操作。
OpenRASP 采用模块化的结构,按照初始化顺序,启动流程如下:
初始化OpenRASP所需全局变量
注册INI配置条目,通过ini配置文件初始化全局配置
日志模块,记录报警、插件、基线等日志,支持FILE/TCP/UDP
V8模块,JS运行环境,负责插件加载与结合运行时上下文的检测能力
HOOK模块,敏感PHP_FUNCTION挂钩执行检测及检测结果处理
INJECT模块,针对特定URL,修改响应内容,注入HTML
安全基线检查模块,检查敏感ini配置项
文件监控模块,监控插件目录,实现运行时检测逻辑修改
下面针对几个主要的模块进行针对性说明:
日志模块启动流程如下:
初始化日志模块所需全局变量
申请共享内存(针对特定SAPI),用于部分日志的进/线程间同步
获取本机网卡以及主机信息,用于基线日志记录
我们将 V8 嵌入到 OpenRASP 中作为 JavaScript 插件的执行引擎
MINIT 阶段载入所有插件,生成一份 V8 Startup Snapshot
请求处理线程第一次触发检测时,使用 V8 Startup Snapshot 还原此线程独享的 V8 Isolate
每个请求线程在对应的 V8 Isolate 环境上执行检测逻辑
执行检测逻辑前向 V8 Platform 添加超时监控后台任务,超时后中断检测
GSHUTDOWN 阶段销毁线程对应 V8 Isolate
HOOK流程包含两类:compiler_globals的handler替换和用户自定义opcode_handler,启动流程如下
在全局compiler_globals对应的hashtable(function_table和class_table)中查找非禁用函数对应zend_function
封装原有handler,根据需求增加前置、后置处理
针对指定opcode(如ZEND_INCLUDE_OR_EVAL)通过zend_set_user_opcode_handler自定义处理逻辑
我们使用经过我们优化增强的 libfswatch 实现了跨平台的文件监控
MINIT 阶段初始化 fswatch 实例,并开启后台线程进行目标文件目录监控
目标文件目录发生变化时,根据类型,向支持重载的 SAPI 主线程发送重载信号
MSHUTDOWN 阶段停止目标文件目录监控,销毁 fswatch 实例和后台线程
经过对比,我们最终选择了 Double Array Trie
算法来匹配白名单,具体实现如下:
白名单存储。白名单放在共享内存里,当云端下发新的配置,通过读写锁更新
白名单匹配。使用 Double Array Trie 算法在白名单里寻找匹配的项目,并生成检测类型的 bitmask。当进入检测点,根据 bitmask 来决定是否直接放行。
内存消耗。每个检测类型最多允许10条白名单,URL长度最大200。最坏情况下,PHP 版本内存 400 KB。
为了适配百度内部的ORP平台,我们特意实现了扩展进程管理模型,以避免再安装一个独立的agent。
在扩展初始化阶段,我们会 fork 出三个进程,分别用于异步日志发送、远程管理、进程守护等功能;在 PHP-FPM 或者 apache 退出或者重启时,我们会杀死这些进程。具体请参考我们的代码实现。
以PHP(mysqli) + MySQL
为例,简要说明请求处理的流程,即 RINIT - RSHUTDOWN
阶段:
为新请求计算唯一的request-id
,设置response header
初始化不同logger,收集日志相关请求信息
连接数据库,触发 mysqli_connect
HOOK点:enforce_policy为1时,若用高权限用户连接数据库,记录基线日志,中断当前请求;enforce_policy为0时,仅当成功连接数据库后检查是否为高权限用户,若是记录日基线志,并将连接信息存入共享内存防止其他进/线程重复报警
数据库语句检测,即mysqli_query
HOOK点pre检测,收集查询参数调用V8模块执行检测,具体流程如下:
(待添加)
慢查询检测。即mysqli_query
HOOK点post检测,通过 call_user_function
检测查询结果数目,超过 openrasp.slowquery_min_rows
配置项则报警
RSHUTDOWN阶段:释放请求相关资源,根据 openrasp.inject_urlprefix
配置判断是否注入用户自定义HTML