初识Rasp——Openrasp代码分析

初识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中重新生成了。

image-20220915144546904

以上就是通过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方法,获取的信息包括类名、方法名和行号。

    image-20220916111430638

    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的规则插件:

image

  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 从而载入虚拟机。

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

image

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方法。

image

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

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

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

image

OpenRasp绕过

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

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

image

随机生成的类名

冰蝎混淆使用的代码:

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

posted @ 2022-10-31 15:15  Highness_DragonFly  阅读(244)  评论(0编辑  收藏  举报