JDK1.5 开始引入了 Agent 机制 (即启动 java 程序时添加 -javaagent 参数”, 如 java -javaagent:/data/test.jar LingXeTest),Java Agent 机制允许用户在 JVM 加载 class 文件的时候先加载自己编写的 Agent 文件,通过修改 JVM 传入的字节码来实现注入 RASP 防御逻辑。这种方式因为必须是在容器启动时添加 jvm 参数,所以需要重启 Web 容器。JDK1.6 新增了 attach 方式 (agentmain),可以对运行中的 java 进程附加 agent。使用附加的方式可以在容器运行时动态的注入 RASP 防御逻辑。

Java

IAST

概念比较

概念 描述 备注
SAST 静态应用程序安全测试,在编码阶段分析应用程序的源代码或二进制文件的语法、结构、过程、接口等来发现程序代码存在的安全漏洞。 白盒测试
DAST 动态应用程序安全测试,模拟黑客行为对应用程序进行动态攻击,分析应用程序的反应,从而确定该 Web 应用是否易受攻击。 黑盒测试
IAST 交互式应用程序安全测试,通过 Web 应用服务端部署运行时插桩、终端流量代理/VPN、旁路流量镜像及部署主机 Agent流量嗅探软件等,收集、监控 Web 应用程序运行时函数执行、数据传输,并与分析引擎端进行实时交互,高效、准确的识别安全缺陷及漏洞。 灰盒测试

运行时插桩模式

主动插桩技术需要在被测试应用程序中部署插桩探针,使用时需要外部扫描器去触发这个 Agent。一个组件产生恶意攻击流量,另一个组件在被测应用程序中监测应用程序的反应,由此来进行漏洞定位和降低误报。

主动插桩

  1. 被测试服务器中安装 IAST 插桩探针;
  2. DAST Scanner 主动发起扫描测试;
  3. IAST 插桩探针追踪被测试应用程序在扫描期间的反应,并动态分析测试覆盖率和上下文。当当定位到具体漏洞信息时,将有关信息发送给管理控制台,控制台展示安全测试结果。

被动插桩

  1. 被测试服务器中安装 IAST 插桩探针;
  2. 插桩探针在应用程序运行时获取请求和代码数据流、代码控制流,进行动态污点追踪
  3. 当定位到具体漏洞信息,插桩探针将获取的信息发送给管理控制台,控制台展示应用安全测试结果。

不会主动对 Web 应用程序执行攻击,而是纯粹被动地分析检测代码。

终端流量代理模式

不需要装agent,安全测试会产生一定的脏数据。

image-20211229170934435

RASP

运行时应用程序自我保护,它的实现方式是通过Instrumentation编写一个agent,在 agent 中加入 hook 点,当程序运行流程到了 hook 点时,将检测流程插入到字节码文件中,统一进入JVM中执行。RASP需要能修改.class文件并且在JVM解释执行的时候注入保护程序。

image-20211229172553629

Java部署的两种方式

一个代理实现ClassFileTransformer接口用于改变运行时的字节码class File,这个改变发生在jvm加载这个类之前。对所有的类加载器有效。

class File这个术语定义于虚拟机规范3.1,指的是字节码的byte数组,而不是文件系统中的class文件。

premain方式

允许在main开始前修改字节码,也就是在大部分类加载前对字节码进行修改。

实现方式:

通过修改 Tomcat、Jetty 和 Springboot 等的启动脚本,给JVM增加 javaagent 参数来让JVM加载一个Java Agent

1
java -javaagent:/path/to/your/agent.jar -jar XXX.jar

知道 web 容器的安装路径

具体代码:

创建PreTest.jar

1
2
3
4
5
public class test {
public static void main(String[] args) {
System.out.println("run test main");
}
}

创建PreAgent.jar

1
2
3
4
5
6
7
import java.lang.instrument.Instrumentation;

public class PreAgent {
public static void premain(String args, Instrumentation is){
System.out.println("run premain agent before test");
}
}

运行java -javaagent:PreAgent.jar -jar PreTest.jar

image-20220112162516225

agentmain方式

允许在main执行后通过com.sun.tools.attach的Attach API attach到程序运行时中,通过retransform的方式修改字节码,也就是在类加载后通过类重新转换(定义)的方式在方法体中对字节码进行修改,其本质还是在类加载前对字节码进行修改。

实现方式:

RASP 守护进程发现了 Java 进程之后,对Java进程依赖的jar包进行分析,从对象存储服务下载对应 jar 包的插件到目标主机上,然后 attach 到目标 JVM 上,目标 JVM 在初始化一个 **轻量级的 Agent (仅一个类)**,最后动态加载核心插件 jar 包。

MANIFEST.MF参数

1
2
3
4
5
6
Premain-Class: 包含 premain 方法的类(类的全路径名)
Agent-Class: 包含 agentmain 方法的类(类的全路径名)
Boot-Class-Path: 设置引导类加载器搜索的路径列表。查找类的特定于平台的机制失败后,引导类加载器会搜索这些路径。按列出的顺序搜索路径。列表中的路径由一个或多个空格分开。路径使用分层 URI 的路径组件语法。如果该路径以斜杠字符(“/”)开头,则为绝对路径,否则为相对路径。相对路径根据代理 JAR 文件的绝对路径解析。忽略格式不正确的路径和不存在的路径。如果代理是在 VM 启动之后某一时刻启动的,则忽略不表示 JAR 文件的路径。(可选)
Can-Redefine-Classes: true表示能重定义此代理所需的类,默认值为 false(可选)
Can-Retransform-Classes: true 表示能重转换此代理所需的类,默认值为 false (可选)
Can-Set-Native-Method-Prefix: true表示能设置此代理所需的本机方法前缀,默认值为 false(可选)

需要修改已经被JVM加载过的类的字节码,那么还需要设置在MANIFEST.MF中添加Can-Retransform-Classes: trueCan-Redefine-Classes: true

两者区别

agent运作模式不同:premain相当于在main前类加载时进行字节码修改,agentmain是main后在类调用前通过重新转换类完成字节码修改。可以发现他们的本质都是在类加载前完成的字节码修改,但是premain可以直接修改或者通过redefined进行类重定义,而agentmian必须通过retransform进行类重新转换才能完成字节码修改操作。

部署方式不同:由于agent运作模式的不同,所以才导致premain需要在程序启动前指定agent,而agentmain需要通过Attach API进行attach。而且由于都是在类加载前进行字节码的修改,所以如果premain模式的hook进行了更新,就只能重启服务器,而agentmain模式的hook如果进行了更新的话,需要重新attach。

两者缺陷

  • premain:每次修改需要重启服务。
  • agentmain:由于attach的运行时中的进程,因JVM的进程保护机制,禁止在程序运行时对运行时的类进行自由的修改,具体的限制如下:
    • 父类应为同一个类
    • 实现的接口数要相同
    • 类访问符要一致
    • 字段数和字段名必须一致
    • 新增的方法必须是private static/final
    • 可是删除修改方法

通过动态污点跟踪,基于词法分析的漏洞检测解决如何跟踪信息流,如何检测安全性两个问题。

参考链接

http://www.jrasp.com/

https://rasp.baidu.com/doc/install/compat.html

RASP对抗技术浅析

https://www.anquanke.com/post/id/187415#h3-3