RASP (Runtime Application Self-Protection) 实现指南

RASP (Runtime Application Self-Protection) 实现指南
项目概述
🎯 什么是RASP
RASP (Runtime Application Self-Protection) 是一种新兴的应用安全技术,通过在应用程序运行时进行实时监控和防护,能够检测并阻止各种安全攻击。与传统的WAF等外围防护不同,RASP直接嵌入到应用程序内部,具有更高的检测精度和更低的误报率。
🏗️ 项目特点
- 零侵入性: 通过Java Agent技术,无需修改应用代码
- 实时防护: 运行时动态检测和拦截恶意行为
- 高性能: 基于ASM字节码操作,性能开销极低
- 可扩展: 模块化设计,支持自定义Hook点和检测规则
- 跨平台: 支持所有Java应用环境
🎨 技术选型说明
本项目选择ASM作为字节码操作框架,相比ByteBuddy具有以下优势:
特性 | ASM | ByteBuddy |
---|---|---|
性能开销 | 极低 | 较低 |
学习成本 | 较高 | 较低 |
自由度 | 极高 | 受限 |
字节码控制 | 精确 | 抽象 |
适用场景 | 高性能要求 | 快速开发 |
技术架构
🏛️ 整体架构图
┌─────────────────────────────────────────────────────────────┐
│ Java Application │
├─────────────────────────────────────────────────────────────┤
│ RASP Agent Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Hook Manager│ │ASM Transform│ │ Interceptors│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Bootstrap ClassLoader │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Commons │ │ Interceptor │ │ Config │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ JVM Runtime │
└─────────────────────────────────────────────────────────────┘
📦 模块结构
1. rasp-agent (核心代理模块)
- RaspAgent: Java Agent入口点
- HookManager: Hook点管理器
- ASMTransformer: 字节码转换器
- RASPLog:RASP日志实现
- 拦截器集合: 各种安全检测拦截器
2. rasp-commons (公共组件模块)
- 配置管理: AgentConfig配置类
- 事件模型: HookEvent、HookResult等
- 性能监控: PerformanceMonitor
- 异常处理: RASPException
3. rasp-interceptor (拦截器模块)
- BootstrapProcessor: 引导处理器
- InterceptorAdvice: 拦截器父接口
- InvokeRegister: 调用注册器
4. rasp-test (测试应用模块)
- CommandController: 命令执行测试
- SerializeController: 序列化测试
- 漏洞模拟: 各种安全漏洞场景
核心实现原理
🔧 Java Agent机制
RASP基于Java Agent技术实现,在JVM启动时通过-javaagent
参数加载:
java -javaagent:rasp-agent.jar -jar your-application.jar
Agent生命周期
- premain阶段: JVM启动前执行,注册ClassFileTransformer
- transform阶段: 类加载时进行字节码转换
- runtime阶段: 运行时执行Hook逻辑
- shutdown阶段: 应用关闭时清理资源
🎯 Hook点设计原理
RASP需要在关键的安全敏感方法上设置Hook点,主要考虑以下几个时机:
1. 方法进入时 (OnMethodEnter)
- 目的: 分析方法入参,检测潜在威胁
- 处理: 参数校验、来源追踪、威胁检测
- 决策: 允许执行 or 阻断执行
2. 方法退出时 (OnMethodExit)
- 目的: 分析方法返回值和执行结果
- 处理: 结果验证、行为分析、日志记录
- 决策: 允许返回 or 修改结果
3. 异常处理时 (OnMethodException)
- 目的: 捕获和分析异常情况
- 处理: 异常分析、安全日志、告警通知
🔍 威胁检测流程

ASM字节码Hook实现
🛠️ ASM核心组件
1. RASPASMTransformer
public class RASPASMTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
// 1. 检查是否需要转换
if (!shouldTransform(className)) {
return classfileBuffer;
}
// 2. 创建ClassReader和ClassWriter
ClassReader classReader = new ClassReader(classfileBuffer);
ClassWriter classWriter = getClassWriter(classReader);
// 3. 应用字节码转换
classReader.accept(new RASPClassVisitor(classWriter), EXPAND_FRAMES);
return classWriter.toByteArray();
}
}
2. RASPAdviceAdapter
public class RASPAdviceAdapter extends AdviceAdapter {
private final HookPoint hookPoint;
private final String className;
private final String methodName;
private final String methodDescriptor;
private final String hookClassName;
private final int adviceHash;
private final Label start = new Label();
private final Label end = new Label();
private final Label handler = new Label();
private final boolean isRequest;
private int argIndex = -1;
private int returnValueIndex = -1;
protected RASPAdviceAdapter(int api, MethodVisitor methodVisitor, int access, String name, String descriptor,
HookPoint hookPoint) {
super(api, methodVisitor, access, name, descriptor);
this.hookPoint = hookPoint;
this.className = hookPoint.getClassName();
this.methodName = hookPoint.getMethodName();
this.methodDescriptor = hookPoint.getMethodDescriptor();
this.hookClassName = hookPoint.getHookClassName();
Object advice = hookPoint.getAdvice();
this.adviceHash = advice.hashCode();
this.isRequest = hookPoint.isRequest();
}
// 判断是不是return opcode
protected boolean isReturnOpcode(int opcode) {
return opcode == RETURN || opcode == ARETURN || opcode == DRETURN ||
opcode == FRETURN || opcode == IRETURN || opcode == LRETURN;
}
protected void storeArgs() {
// 将参数加载到数组中
loadArgArray();
Type type = Type.getType(Object[].class);
argIndex = newLocal(type);
storeLocal(argIndex, type);
}
protected int getEventStatus(int opcode) {
if (!isReturnOpcode(opcode)) {
return EventStatus.ENTER;
} else {
return EventStatus.EXIT;
}
}
protected void methodBody(int opcode) throws NoSuchMethodException {
// 加载This对象
loadThis();
// 加载arg参数
loadLocal(argIndex);
// 加载ReturnValue
if (isReturnOpcode(opcode)) {
loadLocal(returnValueIndex);
} else {
// thisReturnValue 空
push((String) null);
}
// className
push(className);
// methodName
push(methodName);
// methodDesc
push(methodDescriptor);
// adviceHash
push(adviceHash);
// eventHash
push(getEventStatus(opcode));
// hookClassName
push(hookClassName);
push(isRequest);
// 调用 BootstrapProcessor.bootstrapProcess
// public static void bootstrapProcess(Object thisObject, Object[] thisArgs, Object thisReturnValue,
// String className,String methodName, int adviceHash,
// String hookClassName) {
java.lang.reflect.Method bootstrapProcess = BootstrapProcessor.class.getMethod(
"bootstrapProcess", Object.class, Object[].class, Object.class, String.class,
String.class, String.class, int.class, int.class, String.class, boolean.class
);
Method method = Method.getMethod(bootstrapProcess);
Type bootstrapProcessorType = Type.getType(BootstrapProcessor.class);
invokeStatic(bootstrapProcessorType, method);
Type hookResultType = Type.getType(HookResult.class);
int hookResult = newLocal(hookResultType);
storeLocal(hookResult, hookResultType);
loadLocal(hookResult);
// 获取isAllowed方法
invokeVirtual(hookResultType, new Method("isAllowed", "()Z"));
int booleanIndex = newLocal(Type.BOOLEAN_TYPE);
storeLocal(booleanIndex, Type.BOOLEAN_TYPE);
loadLocal(booleanIndex);
Label ifAllowed = new Label();
// 检查结果
ifZCmp(NE, ifAllowed);
// 如果不允许,抛出异常
loadLocal(hookResult);
invokeVirtual(hookResultType, new Method("getException", "()Ljava/lang/Exception;"));
visitInsn(ATHROW);
// 允许执行
mark(ifAllowed);
}
// 方法进入时
@Override
protected void onMethodEnter() {
visitTryCatchBlock(start, end, handler, "java/lang/RuntimeException");
mark(start);
// 调用方法体
try {
// 从本地变量表中加载thisArgs
storeArgs();
methodBody(-1);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
// 方法退出时
@Override
protected void onMethodExit(int opcode) {
if (isReturnOpcode(opcode)) {
Type type = Type.getReturnType(methodDescriptor);
try {
switch (opcode) {
case RETURN:
// thisReturnValue
returnValueIndex = newLocal(Type.VOID_TYPE);
break;
case ARETURN:
// thisReturnValue
returnValueIndex = newLocal(Type.getType(Object.class));
break;
case IRETURN:
// thisReturnValue
returnValueIndex = newLocal(Type.INT_TYPE);
break;
case FRETURN:
// thisReturnValue
returnValueIndex = newLocal(Type.FLOAT_TYPE);
break;
case LRETURN:
// thisReturnValue
returnValueIndex = newLocal(Type.LONG_TYPE);
break;
case DRETURN:
// thisReturnValue
returnValueIndex = newLocal(Type.DOUBLE_TYPE);
break;
}
storeLocal(returnValueIndex, type);
methodBody(opcode);
// 返回
loadLocal(returnValueIndex);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
if (opcode != ATHROW) {
// 正常退出,标记try块结束
mark(end);
}
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
// 添加异常处理代码
mark(handler);
// 复制异常对象
dup();
invokeVirtual(Type.getType(RuntimeException.class),
new Method("printStackTrace", "()V"));
// 重新抛出异常
throwException();
super.visitMaxs(maxStack, maxLocals);
}
}
3、Hook点注解配置
通过为每个拦截类配置对应的需要处理的Hook配置,在Transform的时候会读取所有的HookOption配置来完成对应Hook点的字节码设计。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface HookOption {
// hook类名
String className();
String hookClassName();
// hook方法名
String methodName() default "<init>";
// hook方法参数类型
Class<?>[] methodParameterTypes() default {};
String methodParameterTypesDesc() default "";
boolean isConstructor() default false;
Class<?> returnType() default void.class;
boolean isRequest() default false;
boolean isSuperClass() default false;
HookType hookType();
}
📝 Hook代码生成示例
以Runtime.exec
方法为例,展示ASM生成的Hook代码(这里简单的展示了Runtime.getRuntime().exec()方法的Hook结果,但真实环境点一般会下到最底层,防止漏处理):
原始方法
public Process exec(String command) throws IOException {
return exec(command, null, null);
}
Hook后的方法
public Process exec(String var1) throws IOException {
try {
// === 方法进入Hook ===
Object[] var2 = new Object[]{var1};
HookResult var3 = BootstrapProcessor.bootstrapProcess(
this, var2, null,
"java.lang.Runtime", "exec", "(Ljava/lang/String;)Ljava/lang/Process;",
2101440631, 1,
"org.n1es.rasp.agent.hook.interceptor.RuntimeExecInterceptor"
);
// 检查是否允许执行
if (!var3.isAllowed()) {
throw var3.getException();
}
// === 原始方法执行 ===
Process var5 = this.exec(var1, null, null);
// === 方法退出Hook ===
HookResult var6 = BootstrapProcessor.bootstrapProcess(
this, var2, var5,
"java.lang.Runtime", "exec", "(Ljava/lang/String;)Ljava/lang/Process;",
2101440631, 2,
"org.n1es.rasp.agent.hook.interceptor.RuntimeExecInterceptor"
);
if (!var6.isAllowed()) {
throw var6.getException();
}
return var5;
} catch (RuntimeException var8) {
var8.printStackTrace();
throw var8;
}
}
安全检测机制
🛡️ 命令执行检测
RuntimeExecInterceptor实现
在实现对应防护逻辑时,需要结合实际情况考虑,避免因为误报、漏报,及防护逻辑的问题影响到正常业务,也不能因为防护逻辑的问题被攻击者绕过,这就需要对漏洞点进行深层次的思考。这里简单的对命令执行漏洞的来源进行了处理:
1、验证参数来源
2、source来源且存在威胁直接阻断
3、来源不源于请求但存在威胁,只进行最严格的命令黑名单检查,命中即阻断,否则放行,不影响正常业务

@HookOptions(
className = "java.lang.Runtime",
methodName = "exec",
methodParameterTypes = {String.class},
returnType = Process.class
)
public class RuntimeExecInterceptor implements InterceptorAdvice {
@Override
public HookResult onEnter(HookEvent event) {
try {
// 1. 获取当前请求上下文
HookRequest currentRequest = HookRequest.getCurrentRequest();
if (currentRequest.hasRequest()) {
Object[] args = event.getThisArgs();
if (args != null && args.length > 0) {
String command = args[0].toString();
// 2. 检查参数来源
if (isFromRequest(command, currentRequest)) {
// 3. 威胁检测
if (isDangerousCommand(command)) {
return HookResult.error("命令执行拦截",
new RASPException("检测到危险命令: " + command));
}
}
}
}
} catch (Exception e) {
PerformanceMonitor.getInstance().recordHookError("Runtime.exec", e);
}
return HookResult.allow();
}
}
危险命令检测规则
public static boolean isDangerousCommand(String command) {
if (command == null) return false;
String lowerCommand = command.toLowerCase();
String[] dangerousPatterns = {
"whoami","rm -rf", "del /f", "format", "fdisk",
"wget", "curl", "nc ", "netcat",
"powershell", "cmd.exe", "/bin/sh",
"chmod 777", "sudo su", "passwd"
};
return Arrays.stream(dangerousPatterns)
.anyMatch(lowerCommand::contains);
}
拦截效果


放行效果


🔒 SQL注入检测
检测策略
- 参数来源追踪: 确认SQL参数是否来自用户输入
- 语法分析: 检测SQL语句中的危险模式
- 上下文分析: 结合业务逻辑进行智能判断
检测规则
private boolean containsSqlInjectionPatterns(String sql) {
if (sql == null) return false;
String lowerSql = sql.toLowerCase();
return lowerSql.contains("union") ||
lowerSql.contains("drop") ||
lowerSql.contains("delete") ||
lowerSql.contains("'") ||
lowerSql.contains("--") ||
lowerSql.contains("/*");
}
🌐 网络连接检测
可疑连接检测
private boolean isSuspiciousConnection(String host, int port) {
// 1. 恶意域名检测
if (host.contains("evil.com") || host.contains("malware.org")) {
return true;
}
// 2. 后门端口检测
int[] suspiciousPorts = {4444, 5555, 6666, 7777, 8888, 9999, 31337};
return Arrays.stream(suspiciousPorts).anyMatch(p -> p == port);
}
性能优化策略
🎯 优化技巧
1. 减少Hook点数量
- 只Hook关键的安全敏感方法
- 避免Hook高频调用的方法
- 使用配置开关控制Hook范围
2. 优化检测逻辑
- 使用快速失败策略
- 缓存检测结果
- 异步处理非关键逻辑
3. 内存管理
- 及时清理临时对象
- 使用对象池复用对象
- 避免内存泄漏
开发指南
🔨 自定义拦截器
1. 创建拦截器类
@HookOptions(
className = "com.example.YourClass",
methodName = "yourMethod",
methodParameterTypes = {String.class},
returnType = String.class
)
public class YourInterceptor implements InterceptorAdvice {
@Override
public HookResult onEnter(HookEvent event) {
// 方法进入时的处理逻辑
Object[] args = event.getThisArgs();
// 执行安全检测
if (isSecurityThreat(args)) {
return HookResult.error("检测到安全威胁",
new RASPException("威胁描述"));
}
return HookResult.allow();
}
@Override
public HookResult onExit(HookEvent event) {
// 方法退出时的处理逻辑
return HookResult.allow();
}
private boolean isSecurityThreat(Object[] args) {
// 实现具体的威胁检测逻辑
return false;
}
}
2. 注册拦截器
// 在RaspAgent中注册
HookOptions hookOption = YourInterceptor.class.getAnnotation(HookOptions.class);
hookManager.registerHookPoint(
hookOption.className(),
hookOption.methodName(),
new HookPoint(hookOption)
);
🧪 调试开发
📊 调试技巧
1. 启用详细日志
rasp.log.level属性开启为DEBUG模式,会调用TraceClassVisitor打印修改后的类方法的字节码指令。
rasp.log.name=rasp.log
rasp.log.dir=dist/logs/
rasp.log.fileNamePattern=rasp.log.%d{yyyy-MM-dd}.%i.log
rasp.log.level=DEBUG
rasp.log.pattern=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
rasp.log.charset=UTF-8
rasp.log.maxFileSize=100MB
rasp.log.totalSizeCap=1GB
rasp.log.maxHistory=30

2. 查看转换后的字节码
转换后的字节码会保存在rasp-transformed-classes/
目录中,可以使用反编译工具查看。
🚀 性能优化
1. Hook点选择原则
- 优先级: 安全敏感 > 业务关键 > 通用方法
- 频率: 避免Hook高频调用方法
- 影响: 评估对业务逻辑的影响
2. 检测逻辑优化
// 使用快速失败
if (args == null || args.length == 0) {
return HookResult.allow();
}
// 缓存检测结果
private static final Map<String, Boolean> threatCache = new ConcurrentHashMap<>();
// 异步处理
CompletableFuture.runAsync(() -> {
// 非关键的后台处理
});
📈 监控与告警
1. 关键指标监控
- Hook执行次数和耗时
- 安全事件数量和类型
- 系统资源使用情况
- 错误率和异常情况
2. 告警配置
// 性能告警
if (averageExecutionTime > 100) {
alertManager.sendAlert("RASP性能告警", "平均执行时间超过100ms");
}
// 安全告警
if (securityEventCount > 10) {
alertManager.sendAlert("RASP安全告警", "检测到大量安全事件");
}
附录
📚 参考资料
🔗 相关链接
📞 技术支持
如有问题,请通过以下方式联系:
- 📧 邮箱: [email protected]