任意代码执行漏洞中,攻击者通过开启一个新的线程来执行命令时,rasp丢失请求的上下文、执行堆栈等重要参数,导致检测算法(堆栈算法、请求参数特征等无法关联)失效,极大的影响RASP的防御功能与溯源能力。
01 案例
如下案例:http 请求线程执行到下面的代码时,新建一个新的线程来执行任意,命令,http特征参数丢失。
`<%@ page language="java" contentType="text/html; charset=UTF-8"` `pageEncoding="UTF-8"%>` `<%@ page import="java.io.IOException" %>``DOCTYPE html>``<html>``<head>``<meta charset="UTF-8">``<title>Insert title heretitle>``head>``<body>``<%` `// 创建线程执行命令,而不是直接执行命令` `Thread t = new Thread(new Runnable() {` `@Override`` public void run() { `` try {` `Runtime.getRuntime().exec(new String[]{"touch","/tmp/test"});`` } catch (IOException e) {` `e.printStackTrace();` `}` `}` `});` `t.start();` `out.println(">==test==<");``%>``body>``html>`
RASP最终截获的参数会丢失http,无法溯源。(实现原理上的缺陷)
**02 参数丢失原因与优化措施**
rasp 中使用 ThreadLocal 在线程的不同hook点处传递http等参数,由于无法跨线程,参数必然丢失。
有更好的线程关联类,来解决上面新建线程参数丢失问题:java.lang.InheritableThreadLocal类。
Inheritable thread-local variables are used in preference to ordinary thread-local variables when the per-thread-attribute being maintained in the variable (e.g., User ID, Transaction ID) must be automatically transmitted to any child threads that are created.
实现原理:在父线程创建子线程时,向子线程传递变量。可以参考:\[InheritableThreadLocal\](https://www.jianshu.com/p/94ba4a918ff5)。
**03 JRASP实现**
* 使用InheritableThreadLocal创建线程变量
`// 线程上下文` `// 上下文增强:使用 InheritableThreadLocal 代替 ThreadLocal 防止线程注入` `public static InheritableThreadLocal<Context> requestContext = new InheritableThreadLocal<Context>() {` `@Override`` protected Context initialValue() { `` return new Context(); ``}` `};` ` // 模块中使用线程上下文` `@RaspResource` `private ThreadLocal<Context> context;`
* 线程变量的清除
由于jrasp的基本特征是热卸载(加载),如果不能清除线程变量,将会导致已经加载的 jrasp-agent/module 无法正常卸载,造成比较严重的内存泄漏。
因此,jrasp-agent 在卸载时, 执行**强制清除**线程变量操作,彻底解决内存泄漏问题;
// 除去 context 线程变量`` List<Thread> threadList = ThreadUtil.getThreadList(); `` for (Thread thread : threadList) { `` /** `` * 在 rasp 退出时清理线程变量,这里使用 inheritableThreadLocals 应该清除 inheritableThreadLocals `` * @see Thread.inheritableThreadLocals `` * @see Thread.threadLocals ``*/
Object threadLocalMap = RaspReflectUtils.unCaughtGetClassDeclaredJavaFieldValue(Thread.class, "inheritableThreadLocals", thread);
if (null != threadLocalMap) {`` // 反射获取 ThreadLocalMap类的 remove 方法 `` Method method = RaspReflectUtils.unCaughtGetClassDeclaredJavaMethod(threadLocalMap.getClass(), "remove", ThreadLocal.class); ``try {
RaspReflectUtils.unCaughtInvokeMethod(method, threadLocalMap, requestContext);
} catch (Exception e) {`` e.printStackTrace(); `` } `` } ``}
**04 优化后演示**
在springboot项目中创建一个controller:
`@GetMapping("/get/cmd.do")` `public void getProcessBuilder(String cmd) throws Exception {` `Thread thread = new Thread(new Runnable() {` `@Override` `public void run() {` `try {` `// 执行命令` `execCMD(cmd);` `} catch (Exception e) {` `e.printStackTrace();` `}` `}` `});` `thread.start();` `return;` `}`
发起请求,检测结果,http 参数未丢失。
卸载jrasp后,触发full gc,可以看到jvm的类卸载事件,完全卸载。
// 卸载jrasp``./attach -p <pid> -s
》》》1.1.0 新版本功能预览,欢迎申请试用
`### 1.1.0【2022-10】(当前开发版本)``#### Enhancement``+ [attach] 新增jrasp-attach工程(Golang),支持手动注入、查看hook类、更新模块参数和卸载RASP``+ [agent] agent依赖的bridge打包时指定,防止加载错误依赖``+ [agent] 去掉logback/sl4j,使用原生jul ,减少不安全的依赖``+ [agent] 去掉内置jetty,使用原生socket``+ [agent] 使用InheritableThreadLocal代替ThreadLocal防止线程注入``+ [agent] 去掉java-agent的json日志格式,并修改filebeat的日志分割grok表达式``+ [module] 上下文对象优化为context对象``+ [module] module统一参数更新接口``+ [project] 将jrasp-agent、jrasp-module、jrasp-attach和jrasp-daemon等工程合并,统一编译打包``+ [project] 全面兼容 windows、linux、mac``#### BugFix``+ [agent] jar包文件名称增加版本号,解决jar包文件句柄清除问题``+ [module] 替换 @Resource 注解,解决与javax包类的冲突``+ [agent] 解决jvm-sandbox抛出异常时的内存泄漏 bug (jvm-sandbox 已经合入补丁)``+ [jetty module] 解决 http input.read方法重复hook问题 (在openrasp上已经复现该问题)``+ [xxe module] 解决dom4j方法重复hook问题 (在openrasp官方已经确认该问题)`` ``#### TODO``+ [agent] 优化类匹配机制,全局唯一transform实例,减少stw时间`` ``### 1.0.8 【2022-08】(内部测试版本)``#### Enhancement``+ [module] 增加多个安全模块``+ [daemon] 进程扫描优化``+ [daemon] 防止启动多个守护进程`` ``### 1.0.7 【2022-07】(用户使用的稳定版本)``#### Enhancement``+ [daemon] 上报配置更新时间``+ [daemon] daemon启动上报nacos初始化的状态和注册的服务ip``+ [daemon] 发现无法连接nacos时,自动重启,24小时检测一次`` ``#### BugFix``+ [daemon] 修复软刷新panic``+ [daemon] 删除获取依赖的功能,由安全插件自行上报`` ``### 1.0.6 【2022-06】``#### BugFix``+ [daemon] 使用 os.RemoveAll 删除Java进程文件夹`` ``### 1.0.5 【2022-05】``+ [daemon]插件以配置文件为准,配置文件中没有的,删除磁盘上的``+ [daemon]注入后增加软刷新功能和参数更新功能`` ``### 1.0.4 【2022-04】 (开源版本)``+ [agent] 增加native方法hook``+ [daemon] 支持对多个Java进程注入,每个Java进程独立的数据目录`