初识Rasp——Openrasp代码分析

@author:Drag0nf1y

本文首发于奇安信安全社区,现转载到个人博客。

原文链接:

https://forum.butian.net/share/1959

什么是RASP?

Rasp的概念

​ RASP(Runtime application self-protection)的概念,最早由Gartner在2014年提出,即“运行时自我保护”,指对应用程序的保护不应该单纯依赖于外部的系统,而应该使程序自身带有自我保护的能力。RASP在实现或构建的过程中会被链接到应用程序的运行环境中,并能够控制应用程序代码的执行,实时地检测和阻止攻击行为。

​ 区别于传统的基于网络流量的安全检测设备,诸如WAF或者IDS产品,RASP将防御能力内嵌到应用本身里,在关键的方法调用之前,对执行的方法和参数进行安全校验。因此,相较于传统的基于流量的安全产品,会有更好的检出率和更低对误报率。同时由于在应用内部,接触到的数据对象都是解密完成的,能够直接对于各类加密、混淆绕过的攻击,和有着更好的防护能力。

Rasp的实现

​ 以本文介绍的Java rasp为例,Java是通过Java Agent方式进行实现,具体是使用ASM(或者其他字节码修改框架)技术实现RASP技术。在jdk1.5之后,java提供一个名为Instrumentation的API接口,Instrumentation的最大作用,就是类定义动态改变和操作。开发者可以在一个普通 Java 程序(带有 main 函数的 Java 类)启动时,通过 – javaagent参数指定一个包含 Instrumentation 代理的 jar 文件,来启动 Instrumentation 的代理程序。

​ java.lang.instrument包是Java中来增强JVM上的应用的一种方式,机制是在JVM启动前或启动后attach上去修改方法的字节码。

例子:

以下为一个介绍rasp hook目标类原理的demo,属于本文的前置知识:

首先我们来创建一个agent,然后编写一个premain方法,该方法会在main函数之前预先执行。这里的参数instjava.lang.instrument.Instrumentation的一个实例,这个接口所对应的包集中了agent所需的所有方法。

package org.javaweb.test;
public class Agent{
public static void premain(String agentOps, Instrumentation inst) {
System.out.println("=======this is agent premain function=======");
}
}

然后我们需要再premain中添加一个自定义的Transformer。

关于Transformer的解释

这个方法可以转换目标类文件,并返回一个新的替换类文件。

添加一个新的Transformer类作为转换器:

package org.javaweb.test;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain; public class TestTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
System.out.println(className.replace("/", "."));
return classfileBuffer;
}
}

(PS:多个转换器同时存在时,转换会由Transformer链来执行,一个tranform类返回的byte[]会作为下一个的classfilebuffer参数的输入)

之后在agent.java中修改premain方法:

System.out.println("=======this is agent premain function=======");
inst.addTransformer(new TestTransformer());

用maven将该方法打成一个jar包之后,我们再创建一个可以被hook的带有main方法的程序:

package org.javaweb.test;

public class TestAgent {
public static void main(String[] args) {
System.out.print("=======This is TestAgent Program!=======");
}
}

maven打包后,我们测试一下这个程序:

java -jar testagent.jar

可以看到输出了很多包的名字,因为我们在新增的TestTransformer中重新生成了。

以上就是通过Agent来修改目标类输出结果的示例。

OpenRasp介绍

简介

​ OpenRasp是百度开源的一款Rasp产品,社区热度很高,插件开发也十分简单,降低了使用门槛,也吸引了很多企业与研究者开始接触Rasp并将其运用到生产实践中。

​ OpenRASP利用js来编写规则,通过V8来执行js。这样可以更加方便热部署,以及规则的通用性。同时减少了为不同语言重复制定相同规则的问题。

OpenRasp启动流程

  1. 首先进入Agent.java的premain方法,这个方法会将agent.jar添加到BootstrapClassLoader的ClassPath下,这样 hook 由BootstrapClassLoader加载的类的时候就能够成功调用到 agent.jar 中的检测入口。否则像java.io.File这样的类将无法加载(Java双亲委派机制,用户自定义的类将会使用SystemClassLoader)。

    public static void premain(String agentArg, Instrumentation inst) {
    init(START_MODE_NORMAL, START_ACTION_INSTALL, inst);
    }
    public static synchronized void init(String mode, String action, Instrumentation inst) {
    try {
    JarFileHelper.addJarToBootstrap(inst);
    readVersion();
    ModuleLoader.load(mode, action, inst);
    } catch (Throwable e) {
    System.err.println("[OpenRASP] Failed to initialize, will continue without security protection.");
    e.printStackTrace();
    }
    }
  2. 然后调用ModuleLoader.load函数,ModuleContainer传入的ENGINE_JAR就是rasp-engine.jar:

    public static synchronized void load(String mode, String action, Instrumentation inst) throws Throwable {
    if (Module.START_ACTION_INSTALL.equals(action)) {
    if (instance == null) {
    try {
    instance = new ModuleLoader(mode, inst);
    } catch (Throwable t) {
    instance = null;
    throw t;
    }
    } else {
    System.out.println("[OpenRASP] The OpenRASP has bean initialized and cannot be initialized again");
    }
    } else if (Module.START_ACTION_UNINSTALL.equals(action)) {
    release(mode);
    } else {
    throw new IllegalStateException("[OpenRASP] Can not support the action: " + action);
    }
    }
    private ModuleLoader(String mode, Instrumentation inst) throws Throwable {
    
            if (Module.START_MODE_NORMAL == mode) {
    setStartupOptionForJboss();
    }
    engineContainer = new ModuleContainer(ENGINE_JAR);
    //传入的ENGINE_JAR就是rasp-engine.jar
    engineContainer.start(mode, inst);
    }

    这里会从相关的配置文件中取出moduleEnterClassName值,即为com.baidu.openrasp.EngineBoot,然后去实例化这个类。也就是说这里的module是一个EngineBoot

  3. 启动Engine,ModuleContainer加载了rasp-engine.jar之后,实例化并调用其口入口类EngineBoot的start方法,start方法中进行了:加载V8引擎、初始化插件系统、配置核查、初始化字节码转换模块、初始化云管理模块等操作。

    public void start(String mode, Instrumentation inst) throws Throwable {
    module.start(mode, inst);//实际上调用的就是EngineBoot的start方法
    }

    EngineBoot的start方法:

    public void start(String mode, Instrumentation inst) throws Exception {
    System.out.println("\n\n" +
    " ____ ____ ___ _____ ____ \n" +
    " / __ \\____ ___ ____ / __ \\/ | / ___// __ \\\n" +
    " / / / / __ \\/ _ \\/ __ \\/ /_/ / /| | \\__ \\/ /_/ /\n" +
    "/ /_/ / /_/ / __/ / / / _, _/ ___ |___/ / ____/ \n" +
    "\\____/ .___/\\___/_/ /_/_/ |_/_/ |_/____/_/ \n" +
    " /_/ \n\n");
    try {
    Loader.load();//openrasp_v8_java
    } catch (Exception e) {
    System.out.println("[OpenRASP] Failed to load native library, please refer to https://rasp.baidu.com/doc/install/software.html#faq-v8-load for possible solutions.");
    e.printStackTrace();
    return;
    }
    if (!loadConfig()) {
    return;
    }
    //缓存rasp的build信息
    Agent.readVersion();
    BuildRASPModel.initRaspInfo(Agent.projectVersion, Agent.buildTime, Agent.gitCommit);
    // 初始化插件系统,包括js上下文类初始化和插件文件初始化
    if (!JS.Initialize()) {
    return;
    }
    CheckerManager.init();
    initTransformer(inst);//初始化字节码转换模块
    if (CloudUtils.checkCloudControlEnter()) {
    CrashReporter.install(Config.getConfig().getCloudAddress() + "/v1/agent/crash/report",
    Config.getConfig().getCloudAppId(), Config.getConfig().getCloudAppSecret(),
    CloudCacheModel.getInstance().getRaspId());
    }
    deleteTmpDir();
    String message = "[OpenRASP] Engine Initialized [" + Agent.projectVersion + " (build: GitCommit="
    + Agent.gitCommit + " date=" + Agent.buildTime + ")]";
    System.out.println(message);
    Logger.getLogger(EngineBoot.class.getName()).info(message);
    }
  4. 初始化插件系统

    ​ 首先是加载V8 JS引擎,OpenRasp的一大特色就是将部分规则通过JS插件的形式来实现编写,这样做有两个优势,一是可以实现跨平台使用,减少了为不同语言重复制定相同规则的问题。另一个就是可以实现规则的热部署,添加或修改规则不需要重新启动服务。

    ​ 这里设置了v8的logger信息、以及其获取栈内信息的getter方法,获取的信息包括类名、方法名和行号。

    InitFileWatcher启动对js插件的文件监控,从而实现热部署,动态的增删js中的检测规则

    public synchronized static boolean Initialize() {
    try {
    if (!V8.Initialize()) {
    throw new Exception("[OpenRASP] Failed to initialize V8 worker threads");
    }
    V8.SetLogger(new com.baidu.openrasp.v8.Logger() {
    @Override
    public void log(String msg) {
    pluginLog(msg);
    }
    });//设置v8的logger
    //设置v8获取栈信息的getter方法,这里获得的栈信息,每一条信息包括类名、方法名和行号classname@methodname(linenumber)
    V8.SetStackGetter(new com.baidu.openrasp.v8.StackGetter() {
    @Override
    public byte[] get() {
    try {
    ByteArrayOutputStream stack = new ByteArrayOutputStream();
    JsonStream.serialize(StackTrace.getParamStackTraceArray(), stack);
    stack.write(0);
    return stack.getByteArray();
    } catch (Exception e) {
    return null;
    }
    }
    });
    Context.setKeys();
    if (!CloudUtils.checkCloudControlEnter()) {
    UpdatePlugin();//加载js插件到v8引擎中
    InitFileWatcher();//启动对js插件的文件监控,从而实现热部署,动态的增删js中的检测规则
    }
    return true;
    } catch (Exception e) {
    e.printStackTrace();
    LOGGER.error(e);
    return false;
    }
    }
  5. 然后调用CheckerManager.init():

    public synchronized static void init() throws Exception {
    for (Type type : Type.values()) {
    checkers.put(type, type.checker);//加载所有类型的检测放入checkers,type.checker就是某种检测对应的类
    }
    }

    这里的type参数就是各种JS的规则插件:

  1. 最后调用initTransformer(inst)初始化字节码转换模块,实现插桩,这里分为两种情况:

    • 对于第一次加载的class进行插桩操作,类加载的时候直接CustomClassTransformer进入agent处理。
    • 对于之前已经加载了的类,使用retransform方法遍历所有已经加载的类。
     * @param inst 用于管理字节码转换器
    */
    private void initTransformer(Instrumentation inst) throws UnmodifiableClassException {
    transformer = new CustomClassTransformer(inst);
    transformer.retransform();
    }
  2. 跟进CustomClassTransformer,该类实现了ClassFileTransformer接口(JVM TI接口)

    public class CustomClassTransformer implements ClassFileTransformer {
    public CustomClassTransformer(Instrumentation inst) {
    this.inst = inst;
    inst.addTransformer(this, true);
    addAnnotationHook();
    }
  3. 跟进addAnnotationHook,获取com.baidu.openrasp.hook包下的AbstractClassHook子类,继续调用addHook添加hook点。

    private void addAnnotationHook() {
    Set<Class> classesSet = AnnotationScanner.getClassWithAnnotation(SCAN_ANNOTATION_PACKAGE, HookAnnotation.class);
    for (Class clazz : classesSet) {
    try {
    Object object = clazz.newInstance();
    if (object instanceof AbstractClassHook) {
    addHook((AbstractClassHook) object, clazz.getName());
    }
    } catch (Exception e) {
    LogTool.error(ErrorType.HOOK_ERROR, "add hook failed: " + e.getMessage(), e);
    }
    }
    }

    classSet收集所有有HookAnnotation注解的类。

    private void addHook(AbstractClassHook hook, String className) {
    if (hook.isNecessary()) {
    necessaryHookType.add(hook.getType());
    }
    String[] ignore = Config.getConfig().getIgnoreHooks();
    for (String s : ignore) {
    if (hook.couldIgnore() && (s.equals("all") || s.equals(hook.getType()))) {
    LOGGER.info("ignore hook type " + hook.getType() + ", class " + className);
    return;
    }
    }
    hooks.add(hook);
    }

    hooks收集所有不是配置文件中忽略的hook信息。

  4. 过滤并hook,调用transformer.retransform()

    对于已经被加载的类,会经由retransform方法到transform,而对于第一次加载的类,会直接被transform捕获(这里是重写了ClassFileTransformer)

    遍历hooks获取所有Hook类,并通过Hook类的isClassMatched方法判断当前类是否Hook类的关注类,如果是,之后的具体操作则交由Hook类的tranformClass方法 。

    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流程

  1. 为启动时候进行了插桩操作,当有类被 ClassLoader 加载时候,会把该类的字节码先交给自定义的 Transformer 处理。

  2. 自定义 Transformer 会判断该类是否为需要 hook 的类,如果是会将该类交给 javassist 字节码处理框架进行处理。

  3. javassist 框架会将类的字节码依照事件驱动模型逐步解析每个方法,当触发了我们需要 hook 的方法,就会在方法的开头或者结尾插入进入检测函数的asm字节码。

  4. 把 hook 好的字节码返回给 transformer 从而载入虚拟机。

    具体流程可以看官网址这张图:

Hook 分析案例:

以ProcessBuilderHook 为例:

开始hook插桩

根据com.baidu.openrasp.transformer.CustomClassTransformer#isClassMatched方法判断是否对目标class进行hook。

public boolean isClassMatched(String className) {
if (ModuleLoader.isModularityJdk()) {
return "java/lang/ProcessImpl".equals(className);
} else {
if (OSUtil.isLinux() || OSUtil.isMacOS()) {
// LOGGER.info("come into linux hook class");
return "java/lang/UNIXProcess".equals(className);
} else if (OSUtil.isWindows()) {
return "java/lang/ProcessImpl".equals(className);
}
return false;
}
}

接着调用的是hook类的transformClass(CtClass ctClass)->hookMethod(CtClass ctClass)方法进行了字节码的修改。

protected void hookMethod(CtClass ctClass) throws IOException, CannotCompileException, NotFoundException {
if (ctClass.getName().contains("ProcessImpl")) {
if (OSUtil.isWindows()) {
String src = getInvokeStaticSrc(ProcessBuilderHook.class, "checkCommand",
"$1,$2", String[].class, String.class);
insertBefore(ctClass, "<init>", null, src);
} else if (ModuleLoader.isModularityJdk()) {
String src = getInvokeStaticSrc(ProcessBuilderHook.class, "checkCommand",
"$1,$2,$4", byte[].class, byte[].class, byte[].class);
insertBefore(ctClass, "<init>", null, src);
}
} else if (ctClass.getName().contains("UNIXProcess")) {
String src = getInvokeStaticSrc(ProcessBuilderHook.class, "checkCommand",
"$1,$2,$4", byte[].class, byte[].class, byte[].class);
insertBefore(ctClass, "<init>", null, src);
}
}

在这里想要将checkCommand函数插入到init函数之前,需要先通过getInvokeStaticSrc方法获取插桩位置的Java代码,再调用insertBefore方法,使用 Javaassist 进行“插入”的操作。在插入在构造方法之前后,被hook的类在实例化之前会先调用插入的方法。

public static void checkCommand(byte[] command, byte[] args, final byte[] envBlock) {
if (HookHandler.enableCmdHook.get()) {
LinkedList<String> commands = new LinkedList<String>();
if (command != null && command.length > 0) {
commands.add(new String(command, 0, command.length - 1));
}
if (args != null && args.length > 0) {
int position = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] == 0) {
commands.add(new String(Arrays.copyOfRange(args, position, i)));
position = i + 1;
}
}
}
LinkedList<String> envList = new LinkedList<String>();
if (envBlock != null) {
int index = -1;
for (int i = 0; i < envBlock.length; i++) {
if (envBlock[i] == '\0') {
String envItem = new String(envBlock, index + 1, i - index - 1);
if (envItem.length() > 0) {
envList.add(envItem);
}
index = i;
}
}
}
checkCommand(commands, envList);
}
}

跟进checkCommand:

public static void checkCommand(List<String> command, List<String> env) {
if (command != null && !command.isEmpty()) {
HashMap<String, Object> params = null;
try {
params = new HashMap<String, Object>();
params.put("command", StringUtils.join(command, " "));
params.put("env", env);
List<String> stackInfo = StackTrace.getParamStackTraceArray();
params.put("stack", stackInfo);
} catch (Throwable t) {
LogTool.traceHookWarn(t.getMessage(), t);
}
if (params != null) {
HookHandler.doCheckWithoutRequest(CheckParameter.Type.COMMAND, params);
}
}
}

在日志中查看收集到的params内容:

{
"params": {
"stack": [
"java.lang.UNIXProcess.\u003cinit\u003e",
"java.lang.ProcessImpl.start",
"java.lang.ProcessBuilder.start",
"java.lang.Runtime.exec",
"java.lang.Runtime.exec",
"superman.shells.T3OrIIOPShell.getServerLocation",
"superman.shells.T3OrIIOPShell_WLSkel.invoke",
"weblogic.rmi.internal.BasicServerRef.invoke",
"weblogic.rmi.internal.BasicServerRef$1.run",
"weblogic.security.acl.internal.AuthenticatedSubject.doAs",
"weblogic.security.service.SecurityManager.runAs",
"weblogic.rmi.internal.BasicServerRef.handleRequest",
"weblogic.rmi.internal.wls.WLSExecuteRequest.run",
"weblogic.work.ExecuteThread.execute",
"weblogic.work.ExecuteThread.run"
],
"env": [],
"command": "sh -c ls"
}
}
从日志中构建上下文参数信息:

获取到堆栈信息后,会调用HookHandler.doCheckWithoutRequest(CheckParameter.Type.COMMAND, params)

public static void doCheckWithoutRequest(CheckParameter.Type type, Map params) {
boolean enableHookCache = enableCurrThreadHook.get();
try {
enableCurrThreadHook.set(false);
//当服务器的cpu使用率超过90%,禁用全部hook点
if (Config.getConfig().getDisableHooks()) {
return;
}
//当云控注册成功之前,不进入任何hook点
if (Config.getConfig().getCloudSwitch() && Config.getConfig().getHookWhiteAll()) {
return;
}
if (requestCache.get() != null) {
try {
StringBuffer sb = requestCache.get().getRequestURL();
if (sb != null) {
String url = sb.substring(sb.indexOf("://") + 3);
if (HookWhiteModel.isContainURL(type.getCode(), url)) {
return;
}
}
} catch (Exception e) {
LogTool.traceWarn(ErrorType.HOOK_ERROR, "white list check has failed: " + e.getMessage(), e);
}
}
doRealCheckWithoutRequest(type, params);
} catch (Throwable t) {
if (t instanceof SecurityException) {
throw (SecurityException) t;
}
} finally {
enableCurrThreadHook.set(enableHookCache);
}
}

跟进doRealCheckWithoutRequest(type, params)

public static void doRealCheckWithoutRequest(CheckParameter.Type type, Map params) {
/*...*/
try {
LOGGER.info("收集到的checkParameter: " + parameter);
isBlock = CheckerManager.check(type, parameter);
LOGGER.info("是否拦截isBlock: " + isBlock);
}
/*...*/

关注isBlock = CheckerManager.check(type, parameter),这里传进去的parameter是将Type和params进行封住哪个后的json:

{
"type": "COMMAND",
//多了一个type
"params": {
"stack": [
"java.lang.UNIXProcess.\u003cinit\u003e",
"java.lang.ProcessImpl.start",
"java.lang.ProcessBuilder.start",
"java.lang.Runtime.exec",
"java.lang.Runtime.exec",
"superman.shells.T3OrIIOPShell.getServerLocation",
"superman.shells.T3OrIIOPShell_WLSkel.invoke",
"weblogic.rmi.internal.BasicServerRef.invoke",
"weblogic.rmi.internal.BasicServerRef$1.run",
"weblogic.security.acl.internal.AuthenticatedSubject.doAs",
"weblogic.security.service.SecurityManager.runAs",
"weblogic.rmi.internal.BasicServerRef.handleRequest",
"weblogic.rmi.internal.wls.WLSExecuteRequest.run",
"weblogic.work.ExecuteThread.execute",
"weblogic.work.ExecuteThread.run"
],
"env": [],
"command": "sh -c ls"
}
}

跟进check(type, parameter):

public static boolean check(Type type, CheckParameter parameter) {
return checkers.get(type).check(parameter);//调用检测类进行参数检测
}

此处会根据传入的type来选择调用相对应的checkers,这里的checkers就是前面CheckerManager.init()的时候放入的内容。

由于我们这个demo放入的内容是command命令,因此会调用V8AttackChecker的check方法。

之后再对插件层层追踪,跟进V8AttackChecker的checkParam方法

   /**
* 执行js插件进行安全检测
* @param checkParameter 检测参数 {@link CheckParameter}
* @return 检测结果
*/
@Override
public List<EventInfo> checkParam(CheckParameter checkParameter) {
return JS.Check(checkParameter);
}

跟进JS.Check(checkParameter),就能看到调用JS插件进行检测的代码了:

OpenRasp绕过

​ 互联网上有很多大佬写过关于Openrasp绕过的文章,但是普遍泛用型较差,或者需要获取shell,利用前提较为困难,这里举一个Kcon黑客大会上分享过的冰蝎4.0的例子:

​ 冰蝎作为一个常用的后门连接工具,在更新4.0之后加入了随机生成类名混淆绕过rasp的功能,测试之后,原本3.0时代类名为rebeyond的类,都被混淆成了随机类名,使得rasp虽然可以检测到命令执行,但是无法判断是冰蝎的后门连接,也较难针对性的进行阻断:

随机生成的类名

冰蝎混淆使用的代码:

net.rebeyond.behinder.utils.Utils.class#getRandomClassName

public static String getRandomClassName(String sourceName) {
String[] domainAs = new String[]{"com", "net", "org", "sun"};
String domainB = getRandomAlpha((new Random()).nextInt(5) + 3).toLowerCase();
String domainC = getRandomAlpha((new Random()).nextInt(5) + 3).toLowerCase();
String domainD = getRandomAlpha((new Random()).nextInt(5) + 3).toLowerCase();
String className = getRandomAlpha((new Random()).nextInt(7) + 4);
className = className.substring(0, 1).toUpperCase() + className.substring(1).toLowerCase();
int domainAIndex = (new Random()).nextInt(4);
String domainA = domainAs[domainAIndex];
int randomSegments = (new Random()).nextInt(3) + 3;
String randomName;
switch(randomSegments) {
case 3:
randomName = domainA + "/" + domainB + "/" + className;
break;
case 4:
randomName = domainA + "/" + domainB + "/" + domainC + "/" + className;
break;
case 5:
randomName = domainA + "/" + domainB + "/" + domainC + "/" + domainD + "/" + className;
break;
default:
randomName = domainA + "/" + domainB + "/" + domainC + "/" + domainD + "/" + className;
} while(randomName.length() > sourceName.length()) {
} return randomName;
}

总结

OpenRasp的特点:

相对于传统的ids和waf等基于流量进行威胁检测的产品,Rasp产品的优势与缺陷如下:

优势:

  1. 准确性高、防绕过能力强

    waf往往误报率高,绕过率高,市面上也有很多针对不同waf的绕过方式,而RASP技术防御是根据请求上下文进行拦截的。

  2. 节省开发成本

    Rasp的原理,决定了其节省了大量协议解析、解码解密、防混淆防绕过的工序。节约了这部分工作的开发和,产品使用时分析解密的工作。

  3. 0day防御与发现

    RASP能够洞察应用程序内部的情况,检测到由新攻击引起的行为变化,使它能够对0day攻击、应用自身未知漏洞对目标应用程序的影响作出反应,同时记录完整的利用流程,方便安全人员分析发现0day漏洞。

  4. 加密流量检测

    因为Rasp是部署于业务内部,不关心流量传输和加解密过程,所以对于加密流量中攻击行为的检测要远胜于传统的流量检测产品。

缺陷:

  1. 部署难度较大、成本较高

    Rasp和业务产品的代码结合得十分深入,所以部署面相对狭窄,对于不同用户的hook需求,定制难度较大。而且随着应用服务的语言不同,需要付出额外的开发和维护成本。

    理想中的Java RASP实践方式是使用agent进行无侵入部署,但是受限于JVM进程保护机制没有办法对目标类添加新的方法,所以无法反复进行字节码插入。

    Open RASP推荐的部署方式都是利用premain模式进行部署,这就造成了必须停止相关业务,加入相应的启动参数,再开启服务。而对甲方来说,重启一次业务完成部署RASP的代价是比较高的。

  2. 与业务代码结合较深、业务风险较大

    因为rasp部署内容深入到业务代码的执行中,出现bug或者其他漏洞风险时,很可能对业务造成极大的影响。如果在RASP所指定的逻辑中出现了严重错误,将直接将错误抛出在业务逻辑中。轻则当前业务中断,重则整个服务中断。例如在RASP的检测逻辑中存在exit()这样的利用,将直接导致程序退出。

  3. 规则编写要求较高

    相对于常规的waf和ids产品,有一个web poc就能快速编写检测规则,迅速上线,而不一定需要深刻理解漏洞的产生原理。

    RASP的规则需要经过专业的安全研究人员反复打磨,要求撰写者对漏洞的理解十分深刻,才能找到合适的hook点,之后还要根据业务来定制化,将所有的可能影响业务的可能性都考虑进去,同时尽量减少误报。但是由于攻击者和规则编写者水平的参差不齐,很容易导致规则遗漏,无法拦截相关攻击,或产生大量的攻击误报。也因为规则编写的复杂,产品对于最新的漏洞,可能无法及时覆盖。

  4. 通用性欠缺

    针对不同语言的服务,要使用完全不同的hook方式,几乎等同于两套产品,缺乏泛用型。

参考

浅谈RASP https://www.anquanke.com/post/id/187415#h3-11

浅谈RASP技术攻防之实战[代码实现篇] https://www.03sec.com/Ideas/qian-tanrasp-ji-shu-gong-fang-zhi-shi-zhan-dai-ma.html

OpenRASP系统架构-Java版本 https://rasp.baidu.com/doc/hacking/architect/java.html

初识Rasp——Openrasp代码分析的更多相关文章

  1. Android代码分析工具lint学习

    1 lint简介 1.1 概述 lint是随Android SDK自带的一个静态代码分析工具.它用来对Android工程的源文件进行检查,找出在正确性.安全.性能.可使用性.可访问性及国际化等方面可能 ...

  2. pmd静态代码分析

    在正式进入测试之前,进行一定的静态代码分析及code review对代码质量及系统提高是有帮助的,以上为数据证明 Pmd 它是一个基于静态规则集的Java源码分析器,它可以识别出潜在的如下问题:– 可 ...

  3. [Asp.net 5] DependencyInjection项目代码分析-目录

    微软DI文章系列如下所示: [Asp.net 5] DependencyInjection项目代码分析 [Asp.net 5] DependencyInjection项目代码分析2-Autofac [ ...

  4. [Asp.net 5] DependencyInjection项目代码分析4-微软的实现(5)(IEnumerable<>补充)

    Asp.net 5的依赖注入注入系列可以参考链接: [Asp.net 5] DependencyInjection项目代码分析-目录 我们在之前讲微软的实现时,对于OpenIEnumerableSer ...

  5. 完整全面的Java资源库(包括构建、操作、代码分析、编译器、数据库、社区等等)

    构建 这里搜集了用来构建应用程序的工具. Apache Maven:Maven使用声明进行构建并进行依赖管理,偏向于使用约定而不是配置进行构建.Maven优于Apache Ant.后者采用了一种过程化 ...

  6. STM32启动代码分析 IAR 比较好

    stm32启动代码分析 (2012-06-12 09:43:31) 转载▼     最近开始使用ST的stm32w108芯片(也是一款zigbee芯片).开始看他的启动代码看的晕晕呼呼呼的. 还好在c ...

  7. 常用 Java 静态代码分析工具的分析与比较

    常用 Java 静态代码分析工具的分析与比较 简介: 本文首先介绍了静态代码分析的基 本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代码分析工具 (Checkstyle,FindBu ...

  8. SonarQube-5.6.3 代码分析平台搭建使用

    python代码分析 官网主页: http://docs.sonarqube.org/display/PLUG/Python+Plugin Windows下安装使用: 快速使用: 1.下载jdk ht ...

  9. angular代码分析之异常日志设计

    angular代码分析之异常日志设计 错误异常是面向对象开发中的记录提示程序执行问题的一种重要机制,在程序执行发生问题的条件下,异常会在中断程序执行,同时会沿着代码的执行路径一步一步的向上抛出异常,最 ...

随机推荐

  1. Taurus.MVC 微服务框架 入门开发教程:项目集成:5、统一的日志管理。

    系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 本系列第一篇:Taurus.MVC V3.0.3 微服务开源框架发布:让.NET 架构在大并发的演进过程更简单 ...

  2. C++ 运行单个实例,防止程序多次启动

    利用内核对象 封装的类,使用运行单个实例,防止多次启动Demo 例子下载地址:http://pan.baidu.com/share/link?shareid=3202369154&uk=303 ...

  3. 【Java】学习路径57-TCP协议客户端与服务器端的关闭

    在TCP协议中,如果发送端(客户端)关闭了,那么接收端(服务器端)端就会收到这个消息. 那么接收端(服务器端)怎么知道的呢? 我们进行实验: 首先在发送端中编写一段程序,当用户输入"end& ...

  4. APICloud 可视化编程 - 拖拉拽实现专业级源码

    低代码开发平台是无需编码 (0 代码或⽆代码) 或通过少量代码就可以快速生成应用程序的开发平台.它的强⼤之处在于,允许终端⽤户使⽤易于理解的可视化⼯具开发自己的应用程序,而不是传统的编写代码⽅式.当遇 ...

  5. k8s驱逐篇(3)-kubelet节点压力驱逐-源码分析篇

    kubelet节点压力驱逐-概述 kubelet监控集群节点的 CPU.内存.磁盘空间和文件系统的inode 等资源,根据kubelet启动参数中的驱逐策略配置,当这些资源中的一个或者多个达到特定的消 ...

  6. 【题解笔记】PTA基础6-10:阶乘计算升级版

    题目地址:https://pintia.cn/problem-sets/14/problems/742 前言 咱目前还只能说是个小白,写题解是为了后面自己能够回顾.如果有哪些写错的/能优化的地方,也请 ...

  7. 【设计模式】Java设计模式 - 桥接模式

    [设计模式]Java设计模式 - 桥接模式 不断学习才是王道 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 原创作品,更多关注我CSDN: 一个有梦有戏的人 准备将博客园.CSDN一起 ...

  8. electron 起步

    electron 起步 为什么要学 Electron,因为公司需要调试 electron 的应用. Electron 是 node 和 chromium 的结合体,可以使用 JavaScript,HT ...

  9. 面试突击83:什么情况会导致@Transactional事务失效?

    一个程序中不可能没有事务,而 Spring 中,事务的实现方式分为两种:编程式事务和声明式事务,又因为编程式事务实现相对麻烦,而声明式事务实现极其简单,所以在日常项目中,我们都会使用声明式事务 @Tr ...

  10. AVL Tree (1) - Definition, find and Rotation

    1. 定义 (15-1) [AVL tree]: 一棵空二叉树是 AVL tree; 若 T 是一棵非空二叉树, 则 T 满足以下两个条件时, T 是一棵 AVL tree: T_LeftSubtre ...