//文 | 美团信息安全团队许乐 、孙绥 、石东华、陈驰、郁丛祥、卢世宇等。
背景
RASP 是 Runtime Application Self-Protection(运行时应用自我保护)的缩写,是一种应用程序安全技术。RASP 技术能够在应用程序运行时检测并阻止应用级别的攻击。随着云计算和大数据的发展,应用程序安全越来越受到重视。RASP 技术作为一种新型的安全防护手段,正在逐渐被业界接受并广泛应用。其中Java RASP 是一种针对 Java 应用程序的 RASP 技术。通过在 Java 虚拟机(JVM)级别进行监控和防护,能够有效防止对 Java 应用程序的攻击。
RASP建设挑战
在业界,RASP的部署形式一般有agentmain、premain两种方式,二者各有优劣。适合不同的业务场景,以及安全需求:
agentmain:业务无需改动,无需重启,热插拔,动态升级。有性能抖动,业务有感知。
premain:需要改动,需要重启,前置注入,升级需要重启。无性能抖动,业务无感知。
美团的RASP建设时,大部分业务都已经在线上运营,而且有多个发布平台,没有提供一个统一的方式来更改启动参数,也就是说无法通过premain方式实现快速部署。为了抓住主要矛盾,快速解决大部分风险问题,我们选择了agentmain方式。
1.1.1 业务场景复杂
技术方案的设计,依赖于业务形态。美团内部的业务服务中,Java语言占比80%以上,是主要的风险所在。2010年至今,有特别复杂的业务部署形态、业务依赖环境、繁多的JDK等等,这些都是RASP技术方案的挑战:
业务部署方式:物理机、宿主机、富容器、轻容器等;
发布环境:由于历史原因公司已知的发布系统至少有3个;
Web中间件:Spring Boot、Jetty、Tomcat、WebLogic、自研框架等;
JDK版本:OpenJDK、OracleIDK、 MJDK、Kona、Dragonwell、毕昇等;
进程数量:单个主机上进程数量和生命周期差异大,有的几千个进程,生命周期有分钟级、年级等。
问题的拆解思路依旧是抓住主要矛盾,以JDK版本为例,各个版本JDK的主机占比如下图1:
图1 公司RASP JDK版本分布占比
业务目标确定后,解决方案同样具体到某一类的JDK上。同样,在发布环境、Web中间件的差异上,对RASP也有了更多的兼容要求。
1.1.2 对业务性能影响大
agentmain的动态注入机制,对JVM的影响是不可规避的。影响大小可以从与其他安全防护产品的部署位置看出,下图2是常见的基础安全防护产品:WAF、HIDS和RASP,他们与业务的隔离方式有以下几类:
主机隔离
进程(容器)隔离
无隔离(或者类加载器隔离)
图2 主机安全防护产品与业务的隔离等级
与其他的安全产品相比,如网络应用防火墙(WAF)和主机入侵检测系统(HIDS),RASP与业务部署在同一Java虚拟机(JVM),其隔离级别是最低的。这就意味着,当RASP自身出现BUG或者与业务不兼容时,对业务造成直接影响。RASP 一旦出现故障那至少是S4级别(核心功能受影如资损、客诉,且预判5分钟无法恢复) 。从业务指标上分为cpu和执行耗时,执行耗时方面主要是对服务的TP9999影响较大,而CPU方面出现cpu.busy指标抖动情况。对于业务的指标影响,有以下几种:
运行时注入cpu.busy指标突增
下图3为特殊情况下运行时注入cpu.busy指标抖动情况,在RASP注入时间内(CPU分钟级别采样),Java 进程的CPU从0%飙升到50%,然后又恢复。如果RASP注入之前Java进程的CPU已经很高了,注入时CPU会直接打满(注入前后10分钟)。
图3 运行时注入cpu.busy指标抖动情况
运行时注入TP9999指标突增
下图4为运行时注入TP9999指标抖动情况。单机维度,注入时TP9999从5ms飙升到1000ms,大幅度增加,TP9999出现明显的尖刺,对响应时间敏感的服务影响特别大。
图4 运行时注入TP9999指标抖动情况
启动时性能差与检测逻辑执行耗时长
在RASP启动时,大量请求进入到检测流程中,此时RASP检测代码没有完成预热,检测方法处于字节码解释运行模式,执行效率低,从而导致启动时TP线高。如果正常的请求检测耗时过长,将严重影响业务的TP线,甚至导致请求超时。在RASP运行过程中,因为检测引擎执行耗时长也会导致业务超时。
1.1.3 升级变更难
由于原生Java Agent的限制问题,JVM一旦加载了Agent,就无法进行更新,只能等待JVM重启。
图5 运行时Java Agent的实现原理与升级过程
图5左边的图展示了一个典型的运行时Java Agent的实现原理。在这个过程中,守护进程(这里指主动发起Attach的进程RASP Daemon)会attach到目标JVM上,然后RASP Agent的jar包会被JVM的AppClassLoader加载,接着Agent就会初始化并开始运行。然而,由于JVM类加载机制的限制,同一个类(Agent入口类)无法被AppClassLoader加载器加载两次。使用新的Agent jar包重新attach,即使attach成功,也不会加载新的类。因此想要增加新的功能或者进行bug修复,就必须等待业务进程重启后才能实现。
这也就是说,RASP功能的升级完全依赖于业务进程的重启时机。然而,我们发现线上有些业务,如大数据服务的核心节点,其重启时间可能长达半年甚至更长时间,这就使得RASP的功能升级过程变得异常漫长。由于服务长期未重启,RASP版本无法进行更新。影响主要有2个方面,一方面长期未重启服务的RASP版本低于最新版本,RASP Daemon需要兼容多种RASP Agent版本,这无疑提升了代码工程向下兼容的工作量和稳定性;另一方面,未重启的服务最新的hook点无法生效,也带来一定的安全风险。
在美团,安全部门在不对业务有过多的打扰或者阻碍前提下保障业务安全运行,大规模重启服务风险高,不具备可实施性。如果遇到紧急漏洞或者重大bug时,这种升级难的问题尤为突出。升级难的问题是RASP在部署中遇到的第一个重大问题。
1.1.4 监控难
当JVM加载Java Agent后,由于其运行在业务的同一层面,必然会对业务产生一定的影响。这些影响可能包括CPU使用率飙高、TP9999线的波动,甚至可能出现故障如内存泄漏、磁盘打满、核心转储(core dump)、触发JDK Bug、线程死锁、GC时间变长等等各种问题。业务反馈的线上各类问题的占比如下图6所示。
图6 RASP各类故障占比
美团 RASP 利用 Java agent 和 instrumentation 技术,通过 ASM 修改类字节码,实时分析检测命令执行、文件访问、反序列化、JNDI、SQL注入等入侵行为。它最初是从开源项目 btrace 演化而来,后使用 Golang 重写了 btrace 的进程注入的功能,即架构中的 RASP Daemon 部分、在 Java Agent 端也参考了一些开源项目和公司内部的性能诊断工具。经过多年的迭代,RASP 逐渐形成目前的架构。
通过RASP管理端进行主机维度的配置下发,将最新配置更新应用到 RASP Daemon。日志收集和jar包下载使用公司基础组件,通过这些组件的协同工作,实现对 RASP 部署过程的管理,包括支持灰度发布、配置回滚、降级和一键关闭操作。下图7为 RASP 的配置分发流程。
图7 RASP 的配置分发流程
解决方案
2.1
灰度部署方式和复杂场景的兼容
2.1.1 RASP 启动方式
传统的RASP直接修改JVM启动参数增加RASP的Java Agent参数,即premain 方式。而美团的RASP在最初只支持运行注入agentmain方式,不支持premain。原因主要是下面的2个方面:
在RASP项目建立时,公司的机器节点数量已经有几十万规模了,安全风险较大,急需立即推动覆盖,agentmain运行时注入方式能够满足快速上线的需求;
项目初期公司没有统一的服务发布平台,并且每个业务线的发布脚本不统一,如果通过修改JVM启动参数来启动RASP,需要联系所有业务方来修改参数,运营成本高。
最近两年,逐步支持premain方式。RASP联合服务发布与镜像团队在拉起服务之前将RASP的Java Agent以环境变量的方式设置到服务启动脚本的上下文中。下面为部署脚本中关于RASP环境变量的设置片段。
// 前置检查...
// 增加环境变量
if [[ $RASP_SWITCH=="ON" ]];then
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -javaagent:rasp-premain.jar" && export JAVA_TOOL_OPTIONS
fi
// 启动Java进程...
2.1.2 配置分发方案
在 RASP 升级新版本时,为尽可能地提高稳定性,需要按照一定策略进行灰度升级。
公司内部分为测试和生产等多种环境,并且测试环境服务器数量为万级别,RASP 需先在线下环境稳定运行足够时候后再开始线上环境灰度;
服务按照重要性(或者环境复杂性)从低到高划分为普通服务、重要服务、高优服务3个类别,依次进行。
每一个服务需要再按照主机数量的百分比进行灰度,一个服务下的主机不能同时进行 RASP 的升级,需按照 10%、30%、50%、100% 的比例灰度。
2.1.3 Web/JDK版本识别与准入
RASP Daemon(golang语言) 通过识别进程的cmdLine、JDK、Tomcat、Jetty、Spring Boot等的关键jar包,解析出JDK版本、Web类型和版本。对于已经兼容的服务可以开启注入,对于无法识别或者与RASP不兼容的服务关闭注入(es、jetty等个别版本),最大程度的减少对业务的影响。
2.1.4 组件的兼容性
JDK 兼容性:美团RASP除了使用ASM包之外基本上不使用第三方组件,降低供应链攻击,同时减少对不同版本JDK的专有特性依赖,对于JDK的代码也尽可能的本地化到RASP工程中,屏蔽JDK的版本差异性。
Java Agent 兼容性:公司有多种Java Agent 包括性能诊断,安全扫描、动态调试、流量录制、热部署、链路追踪等约十多种,这些工具实现原理都是基于Instrument。冲突主要在还是在字节码修改上,例如RASP与 jdwp的兼容上,最初版本的RASP在业务类中增加方法数量,当用户开启远程debug时,本地代码的方法数量与远程不一样,导致JVM崩溃。
Java Agent应该遵循的规范:
字节码的修改应该遵循下面的基本原则:不允许新增、修改和删除成员变量 ;不允许新增和删除方法 ;不允许修改方法签名(来源于:Java 字节码规范);
Java Agent的jar包应该采用自定义类加载加载,依赖包名称前缀替换等方式,避免与其他Java Agent和业务依赖的冲突;
与其他Java Agent约定,在类查找遍历修改时排除其他的Java Agent的包名称,避免相互引用;
对于热部署等Java Agent,由于它不遵循字节码修改的基本规范,很遗憾,目前无法兼容,只能排除关闭注入。
RASP的运行时注入与更新
运行时注入方式解决了RASP的首次注入不依赖业务重启服务的问题,但是随着部署场景的增加,不可避免的要对RASP进行更新迭代,如何升级成为一个让人头疼的问题。于是更新也不依赖业务重启,成为一个需要解决的最大问题。
插件热更新是一项具有挑战性的技术,也是RASP建设初期要求具备的核心特征之一。由于美团拥有上百万个Java服务节点,一般的Java Agent安装和升级都需要重启Java进程,对于如此庞大规模的服务来说,这并非易事。在超大规模下,如果依赖业务重新发布的方式来使RASP生效,需要等待所有的服务重启一遍。RASP项目没有权限重启业务。因此,对于RASP来说,插件热更新是至关重要的。
在最初的版本中,当RASP注入到业务中后,如果需要更新功能(如修改策略或hook点),仍然需要重新启动Java进程。如果业务不重启,之前版本的RASP会残留在进程中无法卸载,而新版本需要兼容这些无法卸载的部分。这导致线上存在多个不同版本的RASP,不同版本之间的兼容性几乎无法实现,这种方式是行不通的。
因此, RASP借鉴了Tomcat的类加载器架构,将功能分为两类:第一类是需要频繁迭代的功能,如hook点、资源监控、检测引擎、通信等;第二类是几乎不需要改动的部分,如插件加载和初始化部分。将第一类功能抽取出来,形成一个单独的插件包(RASP Plugin),插件包由自定义类加载器加载,使得这部分具备运行时更新的能力。而RASP Agent引导包仅保留几个类,负责初始化插件jar包。下图8展示了拆分前后的对比:
图8 mt-rasp jar包拆分前后对比
对于拆分后的架构,首次注入 RASP Agent 加载V1.0的插件,在需要对插件进行更新时,清除RASP PluginV1.0对象的引用和PluginClassLoader对象,然后创建新的PluginClassLoader实例重新加载并初始化V1.1版本插件,从而实现插件的卸载与热更新。上面拆分方案实现依靠自定义RASP类加载器,RASP的类加载器层次结构(agentmain)如下图9所示:
图9 RASP的类加载器层次结构
从顶层类加载器开始依次说明RASP包的功能和所属的类加载器。
rasp-boot.jar:定义全局变量,能够被所有类访问到,使用 BootstrapClasLoader 加载;
rasp-agent.jar :标准的Java Agent 入口类,定义了agentmain/premain 等Agent初始方法、加载plugin并初始化,使用AppClassLoader加载;
rasp-plugin.jar :RASP核心实现,包括hook点、检测逻辑、资源监控等功能,使用自定义类加载器RaspClassLoader加载;
Script.class :定义检测逻辑,父加载器为RaspClassLoader,使得脚本类能够访问rasp-plugin.jar中的类,使用自定义类加载器ScriptClassLoader加载,并且脚本在磁盘加密在运行时解密。
premain & agentmain 两种方式兼顾
agentmain和premain方是Java Agent的两种启动方式,agentmain在Java进程启动后加载,而premain在Java进程启动前加载。由于启动时机不一样,带来的差异主要有agentmain 更新加载更加灵活,但是字节码修改时存在性能问题,特别是对性能比较敏感的服务;而premain需要将javaagent参数加入到JVM启动命令行中,完全依赖业务启动,不太灵活,但是性能上比较稳定。美团RASP采用agentmain与premain 结合方式,平衡灵活性与性能。原则上premain逻辑尽可能的简单,避免频繁的迭代与升级。
2.3.1 premain 一期方案
RASP在加载时,Java进程的CPU会短暂的升高甚至打满,并且CPU核数越少,升高越明显持续时间越长。根因是Java Agent首次加载时会触发JVM中的code cache区域清零机制(可以认为是JDK的bug),大量热点代码的编译导致JIT编译线程将CPU打满,并且这种现象在CPU核数低于4核时表现尤为明显。
Manifest-Version: 1.0
Premain-Class: com.meituan.rasp.agent.RaspAgent
Agent-Class: com.meituan.rasp.agent.RaspAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Can-Set-Native-Method-Prefix: true
为了解决运行时CPU飙高问题,我们引入空的premain包(premain v1.0)(仅开启上面的字节码转换的开关Can-Redefine-Classes,无任何逻辑,也不修改字节码),在应用启动前加载,该方案取得较大优化效果。因为无任何代码,代码兼容性风险极小(并不是没有),因此能快速上线解决CPU飙高问题。以某个业务的主机为例子,在优化前后的cpu.busy 指标如下图10所示(注入前后10分钟)。
图10 cpu.busy指标优化前后对比
图10中注入时间为 2022-11-23 05:20:00,红色为优化前的cpu.busy指标,优化前即使注入前系统负载很低(4核8G,cpu.busy <2%),注入瞬间CPU依然飙升很高(28%);蓝色为优化后的cpu.busy指标,优化后cpu.busy曲线较平滑,无明显尖刺。
2.3.2 premain 二期方案
采用premain一期方案的原因是代码足够简单,几乎没有兼容性问题,因此能够快速大规模部署解决棘手的cpu抖动问题,上线效果较好。但是大部分服务虽然CPU不飙高了,但是还有少部分的服务TP9999指标依然影响较大。
2.3.2.1 修改字节码TP9999线升高分析
premain一期方案主要用来快速解决cpu.busy 抖动问题,但是对于性能比较敏感(如tp9999 < 50ms)的业务,运行时字节码修改不可避免的造成STW,从而导致TP9999 线升高。因为字节码修改时需要进入JVM的safepoint,执行字节码转换的方法VM_RedefineClasses::doit 会导致应用短暂地暂停响应,这部分代码执行慢就会影响应用TP9999 。大批量修改字节码测得火焰图如下图11所示。
图11 批量字节码转换时的Java进程火焰图
从火焰图11看出,RedefineClasses 耗时主要在VM_RedefineClasses::AdjustCpoolCacheAndVtable::do_klass,hotspot JDK官方也有类似的issue。转换一个类的主要耗时在redefine_single_class方法,初步测试耗时占比在40%~80% 之间,并且一次转换类越多,占STW总耗时越大。
2.3.2.2 类转换STW时间与服务负载(QPS)关系
在同一服务(硬件配置8核8G)测试了修改类的个数、服务负载和STW时间的关系如下图12所示:
图12 类转换STW时间与服务QPS、转换类数量的关系
从上面数据可以对比看出:
业务请求QPS为0时,STW时间并不为0;
QPS为20时,一次转换的类越多,单个类的STW耗时越长;
转换相同数量的类,随着QPS的增加,STW时间有增加,但是不明显。
从上面的分析可以看出,修改字节码无法避免的产生STW(当然,优化这部分JDK代码理论上是可以实现的,但是技术难度较高,短期内无法解决),因此只能从规避的角度出发来解决。原则上只要保证字节码修改时没有请求即可。
一种可行的方案是将字节码转换的逻辑前移到JVM启动前(即业务没有流量或者主动摘除流量),并且尽量避免有请求时大批量的回滚/修改字节码,能够在一定程度上避开或者缓解STW影响业务请求响应时间。
2.3.2.3 premain修改字节码
对于高频率调用的方法如http body参数读取、sql.execute等,使用premain修改字节码插入RASP检测逻辑,premain agent做到轻量级。对RASP的架构做出相应修改,新增 rasp-premain.jar,让服务启动前进行加载并初始化,将字节码的转换逻辑前置到启动时,如下图13所示,蓝色jar包为新增的rasp-premain.jar。
图13 RASP类加载器增加premain agent
为了最大程度复用之前的系统架构,premain 加载后虽然字节码已经被转换,但 RASP的功能在逻辑上是关闭的,需要等到 agentmain 注入之后打开检测开关。premain 只做字节码转换,没有日志和通信等功能,不能单独工作。如图14所示,优化前后TP9999指标有较明显改善。
图14 优化前后注入时TP9999指标
2.4
运行时性能优化与整体指标
rasp加载之后的流量预热
在RASP的流量控制层增加对流量的计数,RASP初次接入流量时控制接入流量的比例(如1%),使得业务业务流量能够预热RASP检测逻辑,预热时间或者次数达到设定的阈值后,再开启100%的流量检测。
软降级与逻辑开关
业务负载较高的场景(CPU飙高、hook逻辑执行严重超时等),为了避免RASP检测逻辑加剧性能恶化,RASP采样软降级措施,关闭对应hook类的逻辑开关,使得部分流量不执行检测逻辑。如果性能进一步恶化,RASP运行模式降级为观察上报模式,待系统资源检恢复正常过后,资源监测通过后自动恢复到检测阻断模式。
卸载时不回滚字节码
RASP更新插件代码时,需要将plugin的全部对象置空,否则会有内存泄漏问题,特别是元空间的内存泄漏,将导致业务将运行越来越慢,直到停止运行。从前面的STW时间结论来看,运行时的字节码回滚(和修改机制相同)也会产生STW,因此RASP将hook代码的逻辑开关关闭后,字节码依然留在业务类中,在清理完各种对象引用关系后,依然能够卸载plugin插件。
监控体系建设
全局维度的监控指标:
主机注入覆盖率大盘;
coredump总数;
高峰期字节码的修改数量;
熔断超时数量和比例。
单机维度指标:从业务层面到系统层面如下(列举部分)
业务层面:检测引擎执行耗时、TP9999、请求出错率等;
JVM层面:堆内存、元空间/永久代、线程死锁、插件加载次数限制、GC、STW耗时、字节码转换等;
进程层面:Java进程CPU、内存、coredump、守护进程状态等;
系统维度:系统CPU、系统内存、系统磁盘空间、网络等。
图15 RASP监控的指标分布
系统指标和进程指标对于Golang来说很容易获取,相关api较多。这里仅以JVM指标元空间使用率(MetaSpace)的检测为例子说明。RASP Daemon 执行attach获取目前JVM的最大元空间(MaxMetaSpaceSize)指标,然后读取 /tmp/hsperfdata_${user} /pid 文件解析元空间的占用(usedMetaspaceSize 参数在jvm里面是sun.gc.metaspace.used ),计算出元空间的占用比例和剩余空间,当剩余空间不足时,禁止RASP Agent注入,防止RASP成为压垮业务的最后一根稻草。
性能影响
测试配置: 8核/8G/150G。
压力:QPS梯度100,持续120s,稳定施压 120s。
cpu.busy
表1 注入前后的cpu.busy指标
基准数据 (不加载RASP) | 注入数据 (加载RASP) | CPU指标增量值 | |
---|---|---|---|
QPS=20 | 3.47% | 4.18% | 0.71% |
QPS=100 | 11.70% | 11.76% | 0.06% |
QPS=200 | 20.95% | 21.05% | 0.15% |
QPS=300 | 32.12% | 32.78% | 0.66% |
QPS=400 | 41.23% | 44.2% | 2.97% |
QPS=500 | 52.78% | 56.5% | 3.73% |
最大QPS | 620 | 587.8 | - |
拟合方程 | 拟合方程:y = 0.103x + 1.203 拟合度:0.999 | 拟合方程:y = 0.109x + 0.827 拟合度:0.998 |
cpu.busy 绝对值增加: 0.06%~3.73%,整体性能与开源的RASP相当。
堆内存增加
表2 注入前后的内存增加值
最小值 | 平均值 | 最大值 | |
---|---|---|---|
基准(QPS=350) | 422.38MB | 638.34MB | 3.10GB |
注入(QPS=350) | 457.69MB | 821.59MB | 3.30GB |
差值 | 32MB | 183MB | 0.2GB |
注入前后对比,压测到系统弹性扩容的最大QPS,最大堆内存增加约200M,整体性能与开源的RASP相当。
元空间增加
元空间/永久代增加2MB,优于开源RASP产品。
请求耗时
当前请求耗时控制在5ms内,优于开源RASP产品。
漏洞检测
支持的漏洞类型
经过近多年的研发迭代,目前具备的漏洞检测类型如下,基本覆盖常见漏洞(部分):命令执行 (支持native方法)、SQL注入、文件访问、反序列化攻击、JNDI、表达式等。
3.2
实时检测与阻断
3.2.1 不同语言实现的脚本性能比较
开源方案中采用了JavaScript引擎作为实现方式,JS脚本可以被Java、PHP和C++等各种语言兼容,具备较强的通用性。但是经过测试,与原生Java相比,这些方案在性能上存在较大的差距。尽管JavaScript引擎具有不同语言通用性的大优势,但在执行性能方面并不满足高性能场景下RASP的需求。在美团,相比于性能,检测引擎的语言通用性并不是最重要的考虑因素。下面简单对比一下JavaScript和Java实现的检测引擎的性能。因为检测脚本主要涉及字符串的各种操作,我们选择了字符串累加的for循环作为测试场景。
// java
c+='c'
// javascript
c=c+'c'
经过测试,我们发现Java在执行这种字符串操作的性能方面表现更好。Java作为一种编译型语言,具有较高的执行效率和优化能力。它可以通过使用StringBuilder等高效的字符串操作类来提高性能。相比之下,JavaScript作为一种解释型语言,执行效率相对较低。因此,在高性能场景下,使用Java实现的检测引擎往往能够更好地满足需求。尽管JavaScript引擎具有通用性,但在性能要求较高的场景下,选择使用Java实现的原生检测引擎更为合适。
表3 10万量级的for循环中跑出结果如下(单位ms)
javascript | java | |
平均执行耗时 | 585 | 6.5 |
可以看出Java语言实现的检测引擎,性能上具备优越性。美团RASP使用Java语言构建检测引擎,能够满足性能上的需求。
3.2.2 检测脚本的实现
在RASP Plugin中定义了检测脚本需要实现的接口,脚本的实现类由RASP Server下载到磁盘上;RASP Agent定时检测脚本文件是否更新,如果脚本更新,使用新的类加载器加载磁盘上的class文件,并创建实例。
3.2.3 阻断与热修复
在RASP中,通常会在hook方法的执行之前(before)、返回(return)和抛出异常处(throw)增加检测逻辑。RASP通过使用ASM字节码框架,在方法的before、return和throw处织入检测逻辑的字节码(下图16黄色框)。
图16 RASP阻断热修复控制流程
这里以在方法返回之前增加hook逻辑为例子说明阻断/热修复的流程:
1. 字节码插桩:使用ASM工具识别方法中的返回指令如(return、areturn等),在返回指令之前插入RASP的检测方法的字节码,使用instrument 的restransform api将修改后的字节码替换原来的字节码。
2. 运行时检测:当检测引擎返回阻断异常对象时,方法的异常处理抛出阻断异常,终止方法的执行(上图16红色箭头的流程);当检测引擎返回对象时,提前返回指定的对象,修改返回的返回值(上图16中蓝色箭头的流程);返回Null,表示既不阻断也不返回对象(上图16绿色箭头的流程),不改变当前方法的执行流程和返回对象。
热修复与阻断的区别在于热修复返回的是一个对象,这个对象是修复后的正确的对象。
04
总结
美团RASP经过几年的建设,在覆盖对象、部署方式、性能优化、兼容性和安全策略等多个方面逐步迭代,现在已覆盖绝大多数Java服务,支持众多web容器部署,基本覆盖常见的安全漏洞,整体覆盖率上达到了较高水位,并且多次检测出海量的漏洞攻击,成为美团IDC基础安全纵深防御体系中最重要的安全能力。
本文主要介绍了美团RASP在研发过程中遇到的问题和解决方案。首先介绍了RASP的痛点问题,包括业务场景复杂、升级变更难、对业务性能影响大和缺少监控等。对于RASP的升级问题,引入了插件热更新的技术,可以在不重启Java进程的情况下,即时地更新RASP的功能。为了降低对业务性能的影响,介绍了采取的优化措施,包括低峰期注入、启动时流量预热、软降级与逻辑开关以及插件卸载时不回滚字节码等关键技术。然后介绍了RASP的监控体系建设,包括监控指标的定义和收集。最后介绍了RASP的性能与灰度策略,通过对性能损耗的测试和分析,可以看出RASP对CPU和QPS的影响较小。而灰度策略方面,RASP采用了逐步验证和测试的方式。
后续规划:
新型容器形态支持:美团IDC形态中,逐步从富容器过度到轻容器,未来轻容器的增量会超过每年10万台,RASP的管控机制、容器隔离机制,都是未来RASP的挑战;
低打扰无感接入:宿主业务的低打扰,注入性能影响,小众场景的覆盖,依旧是RASP的核心重点,让业务无感、自动、默认接入RASP,提升整体IDC防御水位;
管控、监控自动化:管控端的配置下发依赖链路较多、流程较长,配置变更成本风险高,优化为更高效、更实时、更准确的机制。
相关招聘
范围包括不限于HIDS、RASP、EDR、零信任产品等基础安全方向研发:
岗位职责
负责美团应用程序自我防护系统的开发,通过Java字节码技术实现Java应用程序的防护;
工程能力做到百万级别的Java应用覆盖,保障稳定性和性能;
RASP/IAST等安全产品技术预研。
岗位要求
具备计算机科学、信息安全或相关领域的本科及以上学历;
熟练掌握 Java、Python、Go 等编程语言中的一项或多项;
至少能够精通一个Java web框架的原理,有一定的字节码技术的研发使用经验;
有过中间件开发经验,大规模覆盖应用者优先;
具备出色的分析问题和解决问题的能力,良好的团队协作和沟通能力;
具备良好的学习总结能力,并乐于分享。
优先条件
有过JVM技术研究经验;
有过RASP/IAST/APM开发经验。
岗位亮点
技术本身的难度;
挑战工业界的工程能力。
btrace:https://github.com/btraceio/btrace
hotspot源码位置:hotspot/src/share/vm/prims/jvmtiRedefineClasses.cpp
issue:https://bugs.openjdk.org/browse/JDK-8046246
美团信息安全部,肩负统筹保障公司整体业务和数据的信息安全重要职责,涵盖合规与隐私保护、基础安全、移动安全、数据安全、内容安全、业务风控等风险领域。随着业务升级与拓展,我们拥有诸多全球化安全领域人才,依托前瞻的安全技术视野、创新的机器学习技术、领先的产品运营体系和针对性的安全解决方案,构建全方位、多维度的智能防御体系,全面赋能公司各业务合规、安全和高质量的发展,为美团业务生态链上亿万C端、B端用户的安全提供有力保障。
我们致力于建设业界卓越的安全团队,落地更多业界认可的实践,同时助力业务奔跑。期待你的加入,让我们奔赴热爱,无畏山海,共筑安全长城。