rasp
该项目是 Java RASP 的 SDK,引入rasp-agent-commons
即可开发 RASP 防御模块,添加如下依赖:
<dependency>
<groupId>org.javaweb.rasp</groupId>
<artifactId>rasp-agent-commons</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
RASP 技术原理
在 Java 语言中,从JDK1.5
开始新增了Instrumentation(Java Agent API)
和JVMTI(JVM Tool Interface)
API(Java Agent 机制),允许 JVM 在加载某个 class 文件之前对其字节码进行修改,同时也支持对已加载的 class(类字节码)进行重新加载(Retransform
)。
利用Java Agent
技术衍生出了APM(Application Performance Management,应用性能管理)
、RASP(Runtime application self-protection,运行时应用自我保护)
、IAST(Interactive Application Security Testing,交互式应用程序安全测试)
等相关产品,它们都无一例外的使用了Instrumentation/JVMTI
的API
来实现动态修改 Java 类字节码并插入监控或检测代码。
RASP Agent
RASP 借助 Agent 机制,在 Java 底层与安全相关的类方法中设置了防御点,当 JVM 加载被 RASP 监控的类时会调用 RASP 的防御代码,从而触发 RASP 的主动防御。
RASP 运行原理 :
启动 Servlet 容器时需要添加 JVM 启动参数,如:-javaagent:/data/rasp/rasp-loader.jar
,Agent 注册好后会开始监控待加载的类,当有 HTTP 请求时 RASP 会劫持该请求(Filter/Servlet)并存入 RASP 上下文用于防御模块(Modules)检测。
Agent 整体架构
RASP 由两大核心机制(Agent 机制
、Hook 机制
)、四大核心模块(RASP Loader(加载 RASP Agent)
、RASP Transformer(ASM 字节码编辑)
、RASP Context(RASP 上下文,存储请求相关对象)
、RASP 防御模块
)组成。
RASP 防御原理
RASP 防御的核心就是在 Web 应用程序执行关键的 Java API 之前插入防御逻辑,从而控制原类方法执行的业务逻辑,RASP 是基于事件驱动
机制来防御攻击行为的,即以防御类方法调用为核心,监控类方法的onMethodEnter(方法进入)
、onMethodEnter(方法退出)
、onMethodEnter(方法异常)
三类事件。
示例 - com.mysql.jdbc.ConnectionImpl 类防御示例:
public class ConnectionImpl extends ConnectionPropertiesImpl implements MySQLConnection {
// 省略无关代码
public PreparedStatement prepareStatement(String sql) throws SQLException {
Object[] args = new Object[]{sql};
// 生成 try/catch
try {
// 调用 RASP 方法进入时检测逻辑
HookResult<?> hookResult = HookProxy.processHookEvent(this, args, ...);
// RASP 调用 Hook 处理后的结果类型
HookResultType resultType = hookResult.getRASPHookResultType();
if (REPLACE_OR_BLOCK == resultType) {
// 如果 RASP 检测结果需要阻断或替换程序执行逻辑,return RASP 返回结果中设置的返回值
return (PreparedStatement) hookResult.getReturnValue();
} else if (THROW == resultType) {
// 如果 RASP 检测结果需要往外抛出异常,throw RASP 返回结果中设置的异常对象
throw (Throwable) hookResult.getException();
}
// ------------------------------------------------------------------------------------------
// 执行程序原始逻辑
PreparedStatement returnValue = prepareStatement(sql, RESULT_SET_TYPE, RESULT_SET_CONCURRENCY);
// ------------------------------------------------------------------------------------------
// 调用方法退出时候 RASP 检测逻辑,同 onMethodEnter,此处省略对应代码
return returnValue;
} catch (Throwable t) {
// 调用方法异常时 RASP 检测逻辑,同 onMethodEnter,此处省略对应代码
}
}
}
当 Web 应用接入 RASP 防御后,RASP 会在 Java 语言底层重要的 API(如:文件读写、命令执行、SQL 注入等 API)中设置防御点(API Hook 方式),攻击者一旦发送 Web 攻击请求就会被 RASP 监控并拦截,从而有效的防御 Web 攻击。
示例 - RASP 防御原理:
RASP Hook
Hook 机制类似于 AOP 机制(Aspect Oriented Programming
,面向切面编程),使用基于 Java Agent 实现的 Hook 技术,RASP 可以实现对 Java 类方法执行执行前后插入自定义逻辑,从而实现控制原本的程序执行的业务逻辑。
通过总结常见的 Web 攻击,RASP 归纳出了所有常见的攻击行为会触发的 Java 接口和类,并针对这些 Java 接口/类进行 Hook,让 RASP 控制这些容易受到攻击的 Java API。
rasp-hooks/rasp-hook-cmd
提供了一个可参考的本地系统命令执行
防御示例,任何一个防御模块都必须实现org.javaweb.rasp.commons.hooks.RASPClassHook
接口,然后通过注解的方式配置 Hook 信息,示例代码如下:
package org.javaweb.rasp.agent.hooks.cmd;
import org.javaweb.rasp.commons.MethodHookEvent;
import org.javaweb.rasp.commons.hooks.RASPClassHook;
import org.javaweb.rasp.commons.hooks.RASPMethodAdvice;
import org.javaweb.rasp.commons.hooks.RASPMethodHook;
import java.rasp.proxy.loader.HookResult;
import java.rasp.proxy.loader.RASPModuleType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static org.javaweb.rasp.agent.hooks.cmd.handler.LocalCommandHookHandler.processCommand;
import static org.javaweb.rasp.commons.constants.RASPConstants.DEFAULT_HOOK_RESULT;
import static org.javaweb.rasp.commons.log.RASPLogger.errorLog;
import static org.javaweb.rasp.commons.utils.ReflectionUtils.invokeFieldProxy;
import static org.javaweb.rasp.commons.utils.ReflectionUtils.invokeFieldProxy;
/**
* RASP 防御本地系统命令执行示例
*/
public class LocalCommandHook implements RASPClassHook {
public static final RASPModuleType CMD_TYPE = new RASPModuleType("cmd", "本地命令执行");
/**
* Hook ProcessBuilder 类的 start 方法
*/
@RASPMethodHook(className = "java.lang.ProcessBuilder", methodName = "start", requireRequest = false)
public static class ProcessBuilderHook extends RASPMethodAdvice {
@Override
public HookResult<?> onMethodEnter(MethodHookEvent event) {
Object obj = event.getThisObject();
try {
// 获取 ProcessBuilder 类的 command 变量值
List<String> commandList = new ArrayList<String>();
List<String> command = invokeFieldProxy(obj, "command");
if (command != null) {
commandList.addAll(command);
}
// 获取 ProcessBuilder 类的 environment 变量
Map<String, String> environment = invokeFieldProxy(obj, "environment");
if (environment != null) {
for (String key : environment.keySet()) {
commandList.add(key);
commandList.add(environment.get(key));
}
}
// 调用 processCommand 方法,检测执行的本地命令合法性
return processCommand(commandList, event);
} catch (Exception e) {
errorLog(CMD_TYPE, e);
}
return DEFAULT_HOOK_RESULT;
}
}
}
RASP 防御检测
编写 RASP 防御逻辑之前必须要先检测当前线程中是否有 HTTP 请求,因为如果没有请求存在就无法创建RASPHttpRequestContext
(RASP 上下文),同时还必须检测当前的防御模块是否开启。
当检测到存在恶意攻击时应当立即添加攻击日志并阻断请求:
// 添加攻击日志记录
context.addAttackInfo(new RASPAttackInfo.Builder()
.bindParameters(key, values, cmd, true)
.bindType(position, CMD_TYPE, RULES)
.build()
);
return BLOCK_RESULT;
示例 - RASP 防御本地系统命令执行检测代码:
package org.javaweb.rasp.agent.hooks.cmd.handler;
import org.javaweb.rasp.commons.MethodHookEvent;
import org.javaweb.rasp.commons.attack.RASPAttackInfo;
import org.javaweb.rasp.commons.attack.RASPPosition;
import org.javaweb.rasp.commons.cache.RASPCachedParameter;
import org.javaweb.rasp.commons.cache.RASPRequestCached;
import org.javaweb.rasp.commons.context.RASPContext;
import org.javaweb.rasp.commons.utils.StringUtils;
import java.rasp.proxy.loader.HookResult;
import java.rasp.proxy.loader.RASPHookException;
import java.util.List;
import java.util.Set;
import static java.rasp.proxy.loader.HookResultType.THROW;
import static org.javaweb.rasp.agent.hooks.cmd.LocalCommandHook.CMD_TYPE;
import static org.javaweb.rasp.commons.attack.RASPAlertType.RULES;
import static org.javaweb.rasp.commons.constants.RASPConstants.DEFAULT_HOOK_RESULT;
/**
* RASP 防御本地系统命令执行示例
* Creator: yz
* Date: 2019-07-23
*/
public class LocalCommandHookHandler {
private static final HookResult<?> BLOCK_RESULT = new HookResult<Object>(THROW, new RASPHookException(CMD_TYPE));
/**
* 本地命令执行拦截模块,如果系统执行的 CMD 命令和请求参数完全一致则直接拦截
*
* @param command 执行的系统命令
* @param event Hook 事件
* @return Hook 处理结果
*/
public static HookResult<?> processCommand(List<String> command, MethodHookEvent event) {
String[] commands = command.toArray(new String[0]);
// 如果当前线程中不包含 HTTP 请求则不需要检测
if (event.hasRequest()) {
RASPContext context = event.getRASPContext();
RASPRequestCached cachedRequest = context.getCachedRequest();
// 检测当前请求是否需要经过安全模块检测和过滤且该模块是否是开启状态
if (!context.mustFilter(CMD_TYPE)) {
return DEFAULT_HOOK_RESULT;
}
Set<RASPCachedParameter> cachedParameters = cachedRequest.getCachedParameter();
// 只过滤请求参数值,忽略请求参数名称,因为参数名出现命令执行的概率太低
for (RASPCachedParameter parameterValue : cachedParameters) {
// 请求参数名称
String key = parameterValue.getKey();
// 请求参数值
String[] values = parameterValue.getValue();
// 请求参数出现的位置
RASPPosition position = parameterValue.getRaspAttackPosition();
// 遍历所有的参数值
for (String value : values) {
if (StringUtils.isEmpty(value)) {
continue;
}
// 遍历被执行的系统命令
for (String cmd : commands) {
if (value.equals(cmd)) {
// 添加攻击日志记录
context.addAttackInfo(new RASPAttackInfo.Builder()
.bindParameters(key, values, cmd, true)
.bindType(position, CMD_TYPE, RULES)
.build()
);
return BLOCK_RESULT;
}
}
}
}
}
return DEFAULT_HOOK_RESULT;
}
}
编写好的防御模块 build 成功后需要放到rasp/hooks
目录即可自动加载。
RASPMethodHook/RASPMethodHooks
定义一个 Hook 点必须使用@RASPMethodHook
注解,@RASPMethodHook
注解是用于标注 Hook 点配置信息的,如superClasses
(Hook 的父类名称)、className
(Hook 类名)、methodName
(Hook 方法名)、methodNameRegexp
(Hook 方法名是否使用正则表达式匹配)等,如果多个注解标注的 Hook 点可以使用同一个 Hook 类处理,那么可以使用,如下:
@RASPMethodHooks({
@RASPMethodHook(
superClasses = "jakarta.servlet.Servlet", methodName = "service",
methodArgsDesc = JAKARTA_ARGS_TYPE, requireRequest = false, requestEntry = true
),
@RASPMethodHook(
superClasses = "jakarta.servlet.FilterChain", methodName = "doFilter",
methodArgsDesc = JAKARTA_ARGS_TYPE, requireRequest = false, requestEntry = true
)
})
public static class JakartaHttpRequestHook extends RASPMethodAdvice {
@Override
public HookResult<?> onMethodEnter(MethodHookEvent event) {
return onRequestEnter(event, false);
}
}
RASPMethodAdvice
RASPMethodAdvice
用于处理 Hook 方法事件,该事件分类三大类型,分别对应类方法调用的执行前、后、异常,Hook 事件分类:onMethodEnter
(方法进入前)、onMethodExit
(方法退出后 )、onMethodThrow
(方法异常时),任何一个 Hook 点都必须重写RASPMethodAdvice
类的其中一个方法,否则 Hook 没有意义。
RASPHttpRequestContext
RASPHttpRequestContext
(RASP 上下文)是在 HTTP 请求时自动创建的,RASP 上下文中包含了使用 Hook 方式劫持到的HttpServletRequestProxy/HttpServletResponseProxy
、openModules
(RASP 开发的防御模块列表)、applicationConfig
(当前访问的 W 而不应用配置信息)等。
MethodHookEvent
MethodHookEvent
中包含了RASPHttpRequestContext
(RASP 上下文)和被 Hook 的类方法调用信息,如被 Hook 的类thisClass
、类方法thisMethodName
、类方法参数值thisArgs
等。
HookResult
HookResult
是 RASP 防御模块处理结果对象,返回的结果可以为:RETURN
(直接返回什么都不做)、THROW
(抛出异常)、REPLACE_OR_BLOCK
(阻断或替换值),如果返回类型为REPLACE_OR_BLOCK
则必须提供一个与当前 Hook 类方法类型一致的返回值否则可能会导致栈异常,如:
return new HookResult<String[]>(REPLACE_OR_BLOCK, new String[]{"test"});
注意事项
- 为了避免产生兼容性问题请勿在防御模块中使用 jni、多线程;
- 您所编写的防御模块和所依赖库第三方库必须支持 JDK1.6;
- 如果防御模块依赖了第三方库,必须将防御模块和依赖库打包到一起;