了解Rasp - RASP VS WAF & 简单分析
0x01 引言
RASP(Runtime application self-protection)运行时应用自我保护,是一种植入到应用程序内部或其运行时环境的安全技术。RASP可将自身注入到应用程序中,与应用程序融为一体,实时监测、阻断攻击,使程序自身拥有自保护的能力。
从实时监测、阻断攻击
这两个词看好像与waf(Web Application Firewall,web应用防护系统)很像,那它们有什么不同之处呢?RASP到底是做什么的?实现原理是什么?一场”修行”又开始了。
0x02 RASP vs WAF
1、简介
-
WAF
Web应用防火墙是通过执行一系列针对HTTP/HTTPS的安全策略来专门为Web应用提供保护的一款产品。
聊点阳间的东西,waf就好像“WEB应用小区”的门卫,门卫手里有个小册子(俗称特征库:存放以往来小区做坏事的坏蛋特征如坏蛋总是戴头套)。当小区外边人要进小区串门的时候,它就会拦下来问:来者何人?并根据这个人的特征和手里的小册子核对,如果没有“在案”的坏蛋特征,就一律放行。
从坏人的角度看,似乎这样的门卫并非无懈可击,坏人可以换装、整容等等手段去骗过门卫。
-
RASP
RASP(Runtime application self-protection)运行时应用自我保护,是一种植入到应用程序内部或其运行时环境的安全技术。RASP可将自身注入到应用程序中,与应用程序融为一体,实时监测、阻断攻击,使程序自身拥有自保护的能力。
聊点阳间的东西,相当于在小区里每家每户都安排了一位管家,无事不登三宝殿,坏人进入小区肯定是要干坏事的,所以RASP思路就是:你戴不戴头套我不管,就看你做不做出格(攻击动作)的事。
就好像,越狱的方法千万种,总要离开牢房;撩妹的手段永远在变花样,但目标都是...
2、部署&产品特性
-
WAF
-
外部边界入口统一部署
-
支持串联、旁路、反向代理三种方式部署
-
容易形成单点故障,影响面大
简单说就是,如果小区门口门卫今天喝多了,可能在门口睡着了导致小区的人不管好的坏的都进不去。
-
-
RASP
-
服务器上单独部署,嵌入在应用程序内部,应用代码无感知
-
与开发语言强相关,但防护插件可共用
-
产品特性对比如下:
a57a6de47b9031c1d923bf55d700505e.png3、性能&检测能力
-
WAF
-
规则越多,匹配时对性能消耗就越大
-
和硬件配置相关
-
对服务器CPU无影响
-
业务报文多一次转发,延迟变大
-
-
RASP
-
只在关键点检测,不是所有请求都匹配所有规则
-
对服务器CPU性能有消耗
-
非防护状态延迟增大3-5%,防护状态延迟增大4.6 – 8.9%
-
检测能力对比如下:
e2e2cd15-d1bc-4b24-bd97-bbdf2b83a7a9.png百度Openrasp 官方检测能力说明:https://rasp.baidu.com/doc/usage/web.html
4、优劣分析
接着聊小区门卫与管家的事儿,列举几个关键点:
1)门卫手里有个小册子(俗称特征库:存放以往来小区做坏事的坏蛋特征如坏蛋总是戴头套)
Waf误报多,坏蛋总是戴头套,但不一定戴头套的全是坏人。
Waf维护成本高,小册子需要不断更新添加新的特征。
Waf漏报多容易被绕过,小册子要在发生偷盗事件后再去更新特征比较被动,而且坏人骗过门卫的手段太多了,换衣服、化妆、整容等等。
2)RASP 相当于在小区里每家每户都安排了一位管家&你戴不戴头套我不管,就看你做不做出格(攻击动作)的事
Rasp检测更全面精准,每家每户都安排一位管家肯定比小区门口的门卫更了解每家每户的情况。
Rasp漏洞响应更快可预防未知漏洞,不过多新的偷盗方法肯定要有把钱装到口袋的动作,不管是0day还是Nday都要获取权限执行命令。
Rasp消耗服务器资源多,Waf是一个门卫放小区门口就行,Rasp可能需要N多个管家安排到每家每户。
Rasp技术栈太多时使用不方便,需要根据业务不同开发语言、开发不同语言的探针。
0x03 以OpenRasp为例了解实现原理
经过简单的对比我们已经对RASP有了一定了解,那它是怎么实现的呢?下面以百度的OpenRasp 为例看看它的实现原理。
阅读官方文档,发现启动方式为:
java -javaagent:/opt/spring-boot/rasp/rasp.jar -jar XXX.jar
java -h
-javaagent:<jar 路径>[=<选项>]
加载 Java 编程语言代理, 请参阅 java.lang.instrument
发现是用到了探针技术,原理见:
了解完Java 探针的原理后我们来简单看下OpenRasp的实现原理,可以发现其入口同样是premain
或agentmain
方法,并且都会在pox.xml中标注:
<!--pom.xml-->
<manifestEntries>
<Premain-Class>com.baidu.openrasp.Agent</Premain-Class>
<Agent-Class>com.baidu.openrasp.Agent</Agent-Class>
<Main-Class>com.baidu.openrasp.Agent</Main-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
// 所以入口就是 com.baidu.openrasp.Agent
/**
* 启动时加载的agent入口方法
*
* @param agentArg 启动参数
* @param inst {@link Instrumentation}
*/
public static void premain(String agentArg, Instrumentation inst) {
init(START_MODE_NORMAL, START_ACTION_INSTALL, inst);
}
/**
* attach 机制加载 agent
*
* @param agentArg 启动参数
* @param inst {@link Instrumentation}
*/
public static void agentmain(String agentArg, Instrumentation inst) {
init(Module.START_MODE_ATTACH, agentArg, inst);
}
继续往下看,在com.baidu.openrasp.transformer.CustomClassTransformer.java
为Instrumentation注册了transformer
,从此之后的类加载都会被Transformer拦截。
public CustomClassTransformer(Instrumentation inst) {
this.inst = inst;
inst.addTransformer(this, true); // 注册Transformer
addAnnotationHook(); // 加载所有hook点
}
接着我们来看拦截后的操作:
/**
* 过滤需要hook的类,进行字节码更改
*
* @see ClassFileTransformer#transform(ClassLoader, String, Class, ProtectionDomain, byte[])
*/
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain domain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (loader != null) {
DependencyFinder.addJarPath(domain);
}
if (loader != null && jspClassLoaderNames.contains(loader.getClass().getName())) {
jspClassLoaderCache.put(className.replace("/", "."), new SoftReference<ClassLoader>(loader));
}
for (final AbstractClassHook hook : hooks) {
if (hook.isClassMatched(className)) {
CtClass ctClass = null;
try {
ClassPool classPool = new ClassPool();
addLoader(classPool, loader);
ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
if (loader == null) {
hook.setLoadedByBootstrapLoader(true);
}
classfileBuffer = hook.transformClass(ctClass);
if (classfileBuffer != null) {
checkNecessaryHookType(hook.getType());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ctClass != null) {
ctClass.detach();
}
}
}
}
serverDetector.detectServer(className, loader, domain);
return classfileBuffer;
}
可以看到在这里hook.isClassMatched(className)
检测当前拦截的类是否为已经注册的hook的类,如果是的话则利用javassist的方法创建ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
, javassist 与之前文章中提到的ASM相似都是修改字节码的工具,想了解的可以的官方搜一下使用方法。
在这里classfileBuffer = hook.transformClass(ctClass);
获取处理后的字节码,展开看下代码:
/**
* 转化目标类
*
* @param ctClass 待转化的类
* @return 转化之后类的字节码数组
*/
public byte[] transformClass(CtClass ctClass) {
try {
hookMethod(ctClass);
return ctClass.toBytecode();
} catch (Throwable e) {
if (Config.getConfig().isDebugEnabled()) {
LOGGER.info("transform class " + ctClass.getName() + " failed", e);
}
}
return null;
}
这里直接调用具体hook类的hookMethod(ctClass);
方法来执行具体的逻辑,完成字节码的生成及写入,如OgnlHook#hookMethod
代码示例如下:
/**
* (none-javadoc)
*
* @see com.baidu.openrasp.hook.AbstractClassHook#hookMethod(CtClass)
*/
@Override
protected void hookMethod(CtClass ctClass) throws IOException, CannotCompileException, NotFoundException {
String src = getInvokeStaticSrc(OgnlHook.class, "checkOgnlExpression",
"$_", Object.class);
insertAfter(ctClass, "topLevelExpression", null, src);
}
/**
* struct框架ognl语句解析hook点
*
* @param object ognl语句
*/
public static void checkOgnlExpression(Object object) {
if (object != null) {
String expression = String.valueOf(object);
if (expression.length() >= Config.getConfig().getOgnlMinLength()) {
HashMap<String, Object> params = new HashMap<String, Object>();
params.put("expression", expression);
HookHandler.doCheck(CheckParameter.Type.OGNL, params);
}
}
}
getInvokeStaticSrc
用于获取调用静态方法的代码字符串,然后通过insertAfter
在目标类的目标方法的出口插入相应的源代码,从而完成一次对字节码的修改操作。
今天就先到这儿吧,要去团建...感兴趣的朋友也可以看下这篇文章实际操作一遍。
https://mp.weixin.qq.com/s/LtoDe353uXPA8oT2D9FE8A
参考链接:
http://blog.nsfocus.net/rasp-tech/
http://blog.nsfocus.net/openrasp-tech/
https://www.freebuf.com/articles/network/167166.html // 文章比喻的写法是参考这篇文章去写的,很有意思建议大家看下原文