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 获取到函数的参数

启动流程

  1. 启动时首先会进入 javaagent 的 premain 函数,该函数会在 main 函数之前预先执行
  2. 当去 hook 像 java.io.File 这样由 BootstrapClassLoader 加载的类的时候,无法从该类调用非 BootstrapClassLoader 加载的类中的接口,所以 agent.jar 会先将自己添加到 BootstrapClassLoader 的 ClassPath 下,这样 hook 由 BootstrapClassLoader 加载的类的时候就能够成功调用到 agent.jar 中的检测入口
  3. 释放 log4j 日志配置文件,如果存在则跳过
  4. 根据 openrasp.yml 文件初始化相应配置项
  5. 初始化 JS 插件模块
    • JS 上下文类初始化
    • 插件文件初始化
  6. 初始化字节码转换模块
    • 给 load class 操作进行插桩操作,当类加载的时候会先进入 agent 进行处理
    • 对于在初始化前已加载的类执行 retransform 处理,e.g FileInputStream
  7. 输出启动成功日志,开启全局 Hook 开关(启动阶段为关闭状态)
    • 若启动过程中发生错误,记录错误日志
  8. 给 openrasp.yml 配置文件和 js 插件目录以及 assets 目录增加文件监控,以便文件内容更改的时候不需要重启就能够实时生效

hook class 流程

  1. 因为启动时候进行了插桩操作(premain),当有类被 ClassLoader 加载时候,所以会把该类的字节码先交给自定义的 Transformer 处理
  2. 自定义 Transformer 会判断该类是否为需要 hook 的类,如果是会将该类交给 javassist 字节码处理框架进行处理
  3. javassist 框架会将类的字节码依照事件驱动模型逐步解析每个方法,当触发需要 hook 的方法时,会在方法的开头或者结尾插入进入检测函数的字节码
  4. 把 hook 好的字节码返回给 transformer 从而载入虚拟机

请求处理流程

tomcat + JDBC + MySQL 为例

  1. 服务器收到一个请求,从而进入了服务器的请求 hook 点,该 hook 点标注当前线程为请求线程,开启当前线程的检测开关并把请求对象和响应对象进行缓存,以便后面使用
  2. 服务器发起 SQL 查询
  3. 进入 SQLStatementHook 点,我们挂钩了 execute、executeUpdate、executeQuery 等方法,从该方法进入检测流程如下:
    • 判断当前线程是否为请求线程(第一步标记的),如果是继续下面检测
    • 采集 connection_id(这个字段仅JDBC支持)SQL 语句 以及 数据库类型 等信息
    • 构建参数信息,调用本地插件(Java)和 JS 插件进行安全检测
      • 还有基线检测
    • 根据插件的执行结果决定是拦截请求、放行还是仅打印日志
  4. 进入 SQLResultSetHook 点,我们挂钩了 resultSet.next 方法
    • 调用本地插件检查是否发生拖库行为,默认策略为一次查询结果超过 500 条就报警
  5. 若决定拦截攻击
    • 输出报警日志到 logs/alarm.log
    • 如果 header 还没有发出,默认使用 302 跳转到拦截页面
    • 如果 body 还没有发出,则重置未发送的 body
    • 输出自定义拦截页面跳转 js 脚本
      • </script><script>location.href='.../?request_id=xxx'</script>

总结

  • -javaagent 启动服务时加载 Agent,在 premain 函数中定义插桩逻辑,实现插桩
  • 收到请求后,触发插桩点收集函数调用参数信息,调用插件检测
  • 插件检测并返回结果,根据策略执行相应操作

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

执行流程

  1. 等待用户发送请求
  2. Agent 端将 hook 信息发送给扫描器端
  3. 扫描器端构建 payload 重放
  4. Agent 端将 hook 信息发送给扫描器端,扫描器端收到响应

RASP + IAST

  • RASP 安装 IAST 插件:openrasp/plugins/iast/plugin.js(热更新)
    • 注册 hook 点,触发检测逻辑
  • IAST 扫描器结合插件使用
    • hook 信息、响应信息

管理后台

管理 Agent、查询日志

总结

  1. hook 点预先定义,触发后调用 JS 插件检测
  2. 扫描器接收请求及其 hook 信息,构造新的请求,根据执行结果和 hook 信息实现检测

参阅