百度 OpenRASP 组成分析
RASP 架构
RASP 将 agent 嵌入应用,通过 hook 技术植入探针,在关键点触发检测,拦截/放行
Java 应用的探针植入方法
- Servlet Filter:在请求响应路径上监测,只能对 http 报文过滤处理
- JVM 重构:植入 JVM 内部, 基于 JVM 的安全控制层实现 RASP 容器
- 需要对 JVM 非常熟悉,难度很大,国外 waratek 采用这种方法
- Java Instrument:普遍,OpenRASP 使用该方法
OpenRASP 组成部分
- Java Agent
- JavaScript 插件
- Agent 管理后台(Vue + Golang 实现)
- ElasticSearch + MongoDB
- IAST 扫描器(Python 实现)
Java Agent
启动服务时使用 -javaagent
参数指定 Java Agent,动态修改 Java 字节码(即 hook 插桩)
- 攻击触发插桩点,Java Agent 获取到函数的参数
启动流程
- 启动时首先会进入
javaagent
的 premain 函数,该函数会在 main 函数之前预先执行 - 当去 hook 像 java.io.File 这样由
BootstrapClassLoader
加载的类的时候,无法从该类调用非BootstrapClassLoader
加载的类中的接口,所以 agent.jar 会先将自己添加到BootstrapClassLoader
的 ClassPath 下,这样 hook 由BootstrapClassLoader
加载的类的时候就能够成功调用到 agent.jar 中的检测入口 - 释放
log4j
日志配置文件,如果存在则跳过 - 根据
openrasp.yml
文件初始化相应配置项 - 初始化 JS 插件模块
- JS 上下文类初始化
- 插件文件初始化
- 初始化字节码转换模块
- 给 load class 操作进行插桩操作,当类加载的时候会先进入 agent 进行处理
- 对于在初始化前已加载的类执行
retransform
处理,e.gFileInputStream
- 输出启动成功日志,开启全局 Hook 开关(启动阶段为关闭状态)
- 若启动过程中发生错误,记录错误日志
- 给 openrasp.yml 配置文件和 js 插件目录以及 assets 目录增加文件监控,以便文件内容更改的时候不需要重启就能够实时生效
hook class 流程
- 因为启动时候进行了插桩操作(
premain
),当有类被 ClassLoader 加载时候,所以会把该类的字节码先交给自定义的 Transformer 处理 - 自定义 Transformer 会判断该类是否为需要 hook 的类,如果是会将该类交给 javassist 字节码处理框架进行处理
- javassist 框架会将类的字节码依照事件驱动模型逐步解析每个方法,当触发需要 hook 的方法时,会在方法的开头或者结尾插入进入检测函数的字节码
- 把 hook 好的字节码返回给 transformer 从而载入虚拟机
请求处理流程
以 tomcat + JDBC + MySQL
为例
- 服务器收到一个请求,从而进入了服务器的请求 hook 点,该 hook 点标注当前线程为请求线程,开启当前线程的检测开关并把请求对象和响应对象进行缓存,以便后面使用
- 服务器发起 SQL 查询
- 进入 SQLStatementHook 点,我们挂钩了 execute、executeUpdate、executeQuery 等方法,从该方法进入检测流程如下:
- 判断当前线程是否为请求线程(第一步标记的),如果是继续下面检测
- 采集
connection_id(这个字段仅JDBC支持)
、SQL 语句
以及数据库类型
等信息 - 构建参数信息,调用本地插件(Java)和 JS 插件进行安全检测
- 还有基线检测
- 根据插件的执行结果决定是拦截请求、放行还是仅打印日志
- 进入 SQLResultSetHook 点,我们挂钩了 resultSet.next 方法
- 调用本地插件检查是否发生拖库行为,默认策略为一次查询结果超过 500 条就报警
- 若决定拦截攻击
- 输出报警日志到 logs/alarm.log
- 如果 header 还没有发出,默认使用 302 跳转到拦截页面
- 如果 body 还没有发出,则重置未发送的 body
- 输出自定义拦截页面跳转 js 脚本
</script><script>location.href='.../?request_id=xxx'</script>
总结
-javaagent
启动服务时加载 Agent,在premain
函数中定义插桩逻辑,实现插桩- 预定义的 hook 点:Hook 函数列表
- 可新增 hook 点
- 收到请求后,触发插桩点收集函数调用参数信息,调用插件检测
- 插件检测并返回结果,根据策略执行相应操作
JavaScript 插件
Rhino 引擎支持将 Java 中的对象注册到 JavaScript 环境,并被 JavaScript 代码调用(数据共享)
- mozilla/rhino
- JS 插件提供热更新功能,即能够实时更新检测逻辑
- 根据 Java Agent 返回的插桩点信息,执行检测算法,最后进行告警/拦截
检测插件针对应用的行为进行检测,当应用执行操作时(触发检测点),OpenRASP 引擎就会调用检测插件,并将相关参数一并传递过来。
官方提供 14 个检测点:
- 数据库查询
- 读取目录
- 请求参数
- 读取文件
- 写入文件
- 删除文件
- 文件包含操作
- WebDAV 操作
- 文件上传
- 文件重命名
- 命令执行
- XML 外部实体引用
- Struts OGNL 表达式解析
- RMI 反序列化
- 服务器端 HTTP 请求
- 服务器端 HTTP 请求 - 重定向之后
- 代码执行
- 类库加载
- 响应检查
IAST 扫描器
被动扫描模式:启动扫描后保持运行,对新 url 进行实时扫描
- Python3 实现,MySQL 数据库,HTTP + JSON 通讯
- 属于主动 IAST,重放 payload
- 被动 IAST 使用污点追踪实现
- 缺点:脏数据
Agent 端
用于收集 Web 应用的运行信息,即 OpenRASP Java Agent + Java Application
扫描器端
用于处理 OpenRASP 插件产生的请求信息,并完成整个 IAST 扫描逻辑
- 预处理模块(HTTPServer):接收 agent 插件的 http 请求,处理、存储、分发 http 请求信息
- 扫描模块(Scanner):运行扫描插件,执行漏洞扫描逻辑
- 生成扫描请求,发送给 web server 处理并返回结果(以及 hook 信息),检验结果
- 监控模块(Monitor):定期获取其他模块的运行时信息,调整参数,提供控制台服务等
openrasp_iast 源码中包含三种类型的插件
- 扫描插件:plugin/scanner,生成测试向量,检测测试结果
- 去重插件:plugin/deduplicate,避免同一个请求被反复扫描
- 认证插件:plugin/authorizer
执行流程
- 等待用户发送请求
- Agent 端将 hook 信息发送给扫描器端
- 扫描器端构建 payload 重放
- Agent 端将 hook 信息发送给扫描器端,扫描器端收到响应
RASP + IAST
- RASP 安装 IAST 插件:openrasp/plugins/iast/plugin.js(热更新)
- 注册 hook 点,触发检测逻辑
- IAST 扫描器结合插件使用
- hook 信息、响应信息
管理后台
管理 Agent、查询日志
总结
- hook 点预先定义,触发后调用 JS 插件检测
- 扫描器接收请求及其 hook 信息,构造新的请求,根据执行结果和 hook 信息实现检测
参阅
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Jckling's Blog!
评论