Java RASP简单实现
字数 1530 2025-08-20 18:17:53

Java RASP 实现技术详解

一、RASP 技术概述

RASP (Runtime Application Self-Protection) 是一种运行时应用自我保护技术:

  • 内置在应用程序或应用程序运行时环境中
  • 能够控制应用程序的执行并检测漏洞以防止实时攻击
  • 通过分析应用程序的行为和上下文来保护其不受恶意输入或行为影响
  • 使应用程序持续检测自身行为,可立即识别和缓解攻击,无需人工干预

在 Java 应用程序中,RASP 通常使用 Java Agent 技术实现。

二、Java Agent 基础

1. Agent 类型

Java Agent 分为两种类型:

  1. premain:在应用程序启动前加载
  2. agentmain:在应用程序运行中也可以加载(使用更广泛)

2. 创建基本 Agent

实现步骤:

  1. 新建一个类,实现 agentmain 方法
  2. 项目中添加一个工件
  3. 修改 MANIFEST.MF 文件,添加 Agent-Class 指定为我们的类
  4. 构建工件得到 jar 包

示例代码框架:

public class AgentDemo {
    public static void agentmain(String agentArgs, Instrumentation instrumentation) {
        System.out.println("Agent loaded successfully!");
    }
}

3. Agent 注入器

要将 Agent 注入到运行中的 Java 程序,需要一个注入器(需要 tools.jar 依赖):

import com.sun.tools.attach.*;
import java.io.IOException;
import java.util.List;

public class Main {
    public static void main(String[] args) throws IOException, AttachNotSupportedException, 
            AgentLoadException, AgentInitializationException {
        List<VirtualMachineDescriptor> list = VirtualMachine.list();
        for (VirtualMachineDescriptor virtualMachineDescriptor : list) {
            System.out.println(virtualMachineDescriptor.displayName());
            if (virtualMachineDescriptor.displayName().contains("apache.catalina.startup")) {
                VirtualMachine attach = VirtualMachine.attach(virtualMachineDescriptor);
                attach.loadAgent("路径\\agent01.jar");
            }
        }
    }
}

三、RASP 核心实现

1. 命令执行 Hook

基本原理

Runtime.getRuntime().exec() 本质调用的是 java.lang.ProcessImplstart 方法和 java.lang.Process

实现版本1 - 单次加载

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

public class AgentDemo implements ClassFileTransformer {
    private static final String TARGET_CLASS_NAME = "java.lang.ProcessImpl";
    
    public static void agentmain(String agentArgs, Instrumentation instrumentation) {
        instrumentation.addTransformer(new AgentDemo(), true);
    }
    
    @Override
    public byte[] transform(ClassLoader loader, String className, 
            Class<?> classBeingRedefined, ProtectionDomain protectionDomain, 
            byte[] classfileBuffer) {
        System.out.println(className.replace("/", "."));
        if (!TARGET_CLASS_NAME.equals(className.replace("/", "."))) {
            return classfileBuffer; // 不是目标类,直接返回
        }
        
        try {
            ClassPool pool = ClassPool.getDefault();
            CtClass ctClass = pool.getCtClass(TARGET_CLASS_NAME);
            CtMethod execMethod = ctClass.getDeclaredMethod("start");
            String insertBefore = "System.out.println(\"ProcessImpl start method is called\");";
            execMethod.insertBefore(insertBefore);
            return ctClass.toBytecode();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return classfileBuffer; // 发生异常,返回原始字节码
    }
}

特点:

  • 只能加载一次,之后不会再触发
  • 通过 instrumentation.addTransformer 向 JVM 注册 ClassFileTransformer 实例

实现版本2 - 重加载

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import java.lang.instrument.*;
import java.security.ProtectionDomain;

public class AgentDemo implements ClassFileTransformer {
    private static final String TARGET_CLASS_NAME = "java.lang.ProcessImpl";
    
    public static void agentmain(String agentArgs, Instrumentation instrumentation) 
            throws UnmodifiableClassException {
        // 获取所有已加载的类
        Class[] allLoadedClasses = instrumentation.getAllLoadedClasses();
        for (Class<?> clazz : allLoadedClasses) {
            if (clazz.getName().equals(TARGET_CLASS_NAME)) {
                // 对已加载的类应用变换
                instrumentation.retransformClasses(clazz);
            }
        }
        // 添加变换器以应用于之后加载的类
        instrumentation.addTransformer(new AgentDemo(), true);
    }
    
    @Override
    public byte[] transform(ClassLoader loader, String className, 
            Class<?> classBeingRedefined, ProtectionDomain protectionDomain, 
            byte[] classfileBuffer) {
        System.out.println(className.replace("/", "."));
        if (!TARGET_CLASS_NAME.equals(className.replace("/", "."))) {
            return classfileBuffer; // 不是目标类,直接返回
        }
        
        try {
            ClassPool pool = ClassPool.getDefault();
            CtClass ctClass = pool.getCtClass(TARGET_CLASS_NAME);
            CtMethod execMethod = ctClass.getDeclaredMethod("start");
            String insertBefore = "System.out.println(\"ProcessImpl start method is called\");";
            execMethod.insertBefore(insertBefore);
            return ctClass.toBytecode();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return classfileBuffer; // 发生异常,返回原始字节码
    }
}

特点:

  • 每次遇到目标 hook 类都会重加载
  • 通过 instrumentation.retransformClasses 实现

2. 文件读写 Hook

java.io.File 为例:

import java.lang.instrument.*;
import java.security.ProtectionDomain;
import javassist.*;

public class AgentDemo implements ClassFileTransformer {
    private static final String TARGET_CLASS_NAME = "java.io.File";
    
    public static void agentmain(String agentArgs, Instrumentation instrumentation) 
            throws UnmodifiableClassException {
        Class[] allLoadedClasses = instrumentation.getAllLoadedClasses();
        for (Class<?> clazz : allLoadedClasses) {
            if (clazz.getName().equals(TARGET_CLASS_NAME)) {
                instrumentation.retransformClasses(clazz);
            }
        }
        instrumentation.addTransformer(new AgentDemo(), true);
    }
    
    @Override
    public byte[] transform(ClassLoader loader, String className, 
            Class<?> classBeingRedefined, ProtectionDomain protectionDomain, 
            byte[] classfileBuffer) {
        if (className.equals(TARGET_CLASS_NAME.replace(".", "/"))) {
            try {
                ClassPool pool = ClassPool.getDefault();
                CtClass ctClass = pool.getCtClass(TARGET_CLASS_NAME);
                // 获取 createNewFile 方法
                CtMethod createNewFileMethod = ctClass.getDeclaredMethod("createNewFile");
                // 在createNewFile 方法前后插入代码
                String beforeCode = "System.out.println(\"Before createNewFile: \" + $1);";
                createNewFileMethod.insertBefore(beforeCode);
                String afterCode = "System.out.println(\"After createNewFile: \" + $1);";
                createNewFileMethod.insertAfter(afterCode);
                return ctClass.toBytecode();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return classfileBuffer; // 发生异常或不是目标类,返回原始字节码
    }
}

四、RASP 防御能力扩展

通过 hook 不同的类,可以实现多种防御能力:

  1. 文件系统防御:目录遍历、文件读、写、重命名、移动等
  2. SQL查询防御
  3. XML实体注入防御
  4. 恶意表达式执行防御:Ognl、SpEL、MVEL2等
  5. 恶意WebShell请求拦截
  6. 恶意文件上传防御
  7. 本地命令执行防御
  8. 反序列化攻击防御:Java、XML、Json
  9. SSRF攻击防御

五、RASP 绕过思路

常见的 RASP 绕过方法:

  1. 寻找未被限制的类或函数

    • 类似绕过黑名单的方式
    • 需要尽量覆盖所有可能的类或 hook 更底层的实现
  2. 利用底层技术绕过

    • 直接 hook C 代码(难度较大)
    • 使用 JNI 绕过 RASP(通过编译 so 和 dll 文件的方式)
    • 创建新线程的方式(如果有黑名单无法绕过,但可以绕过堆栈检测)

六、关键技术点

  1. Instrumentation API:提供了修改已加载类和加载新类的功能
  2. Javassist:用于动态修改字节码
  3. VirtualMachine API:用于动态附加 Agent 到运行中的 JVM
  4. ClassFileTransformer:实现类转换的核心接口

七、实际应用建议

  1. 全面覆盖:尽可能 hook 所有可能的危险操作入口点
  2. 性能考虑:字节码转换会影响性能,需优化实现
  3. 安全更新:持续更新防御规则以应对新的绕过技术
  4. 多层防御:RASP 应作为整体安全策略的一部分,与其他安全措施配合使用

通过以上技术实现,可以构建一个基本的 Java RASP 系统,实时监控和防御应用程序中的各种攻击行为。

Java RASP 实现技术详解 一、RASP 技术概述 RASP (Runtime Application Self-Protection) 是一种运行时应用自我保护技术: 内置在应用程序或应用程序运行时环境中 能够控制应用程序的执行并检测漏洞以防止实时攻击 通过分析应用程序的行为和上下文来保护其不受恶意输入或行为影响 使应用程序持续检测自身行为,可立即识别和缓解攻击,无需人工干预 在 Java 应用程序中,RASP 通常使用 Java Agent 技术实现。 二、Java Agent 基础 1. Agent 类型 Java Agent 分为两种类型: premain :在应用程序启动前加载 agentmain :在应用程序运行中也可以加载(使用更广泛) 2. 创建基本 Agent 实现步骤: 新建一个类,实现 agentmain 方法 项目中添加一个工件 修改 MANIFEST.MF 文件,添加 Agent-Class 指定为我们的类 构建工件得到 jar 包 示例代码框架: 3. Agent 注入器 要将 Agent 注入到运行中的 Java 程序,需要一个注入器(需要 tools.jar 依赖): 三、RASP 核心实现 1. 命令执行 Hook 基本原理 Runtime.getRuntime().exec() 本质调用的是 java.lang.ProcessImpl 的 start 方法和 java.lang.Process 。 实现版本1 - 单次加载 特点: 只能加载一次,之后不会再触发 通过 instrumentation.addTransformer 向 JVM 注册 ClassFileTransformer 实例 实现版本2 - 重加载 特点: 每次遇到目标 hook 类都会重加载 通过 instrumentation.retransformClasses 实现 2. 文件读写 Hook 以 java.io.File 为例: 四、RASP 防御能力扩展 通过 hook 不同的类,可以实现多种防御能力: 文件系统防御 :目录遍历、文件读、写、重命名、移动等 SQL查询防御 XML实体注入防御 恶意表达式执行防御 :Ognl、SpEL、MVEL2等 恶意WebShell请求拦截 恶意文件上传防御 本地命令执行防御 反序列化攻击防御 :Java、XML、Json SSRF攻击防御 五、RASP 绕过思路 常见的 RASP 绕过方法: 寻找未被限制的类或函数 : 类似绕过黑名单的方式 需要尽量覆盖所有可能的类或 hook 更底层的实现 利用底层技术绕过 : 直接 hook C 代码(难度较大) 使用 JNI 绕过 RASP(通过编译 so 和 dll 文件的方式) 创建新线程的方式(如果有黑名单无法绕过,但可以绕过堆栈检测) 六、关键技术点 Instrumentation API :提供了修改已加载类和加载新类的功能 Javassist :用于动态修改字节码 VirtualMachine API :用于动态附加 Agent 到运行中的 JVM ClassFileTransformer :实现类转换的核心接口 七、实际应用建议 全面覆盖 :尽可能 hook 所有可能的危险操作入口点 性能考虑 :字节码转换会影响性能,需优化实现 安全更新 :持续更新防御规则以应对新的绕过技术 多层防御 :RASP 应作为整体安全策略的一部分,与其他安全措施配合使用 通过以上技术实现,可以构建一个基本的 Java RASP 系统,实时监控和防御应用程序中的各种攻击行为。