初識Rasp——Openrasp代碼分析

初識Rasp——Openrasp代碼分析

@author:Drag0nf1y

本文首發於奇安信安全社區,現轉載到個人博客。
原文鏈接:
//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 //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 //www.anquanke.com/post/id/187415#h3-11

淺談RASP技術攻防之實戰[代碼實現篇] //www.03sec.com/Ideas/qian-tanrasp-ji-shu-gong-fang-zhi-shi-zhan-dai-ma.html

OpenRASP系統架構-Java版本 //rasp.baidu.com/doc/hacking/architect/java.html

Tags: