初識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函數之前預先執行。這裡的參數inst
是java.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啟動流程
-
首先進入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(); } }
-
然後調用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。
-
啟動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); }
-
初始化插件系統
首先是加載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; } }
-
然後調用CheckerManager.init():
public synchronized static void init() throws Exception { for (Type type : Type.values()) { checkers.put(type, type.checker);//加載所有類型的檢測放入checkers,type.checker就是某種檢測對應的類 } }
這裡的type參數就是各種JS的規則插件:
-
最後調用
initTransformer(inst)
初始化位元組碼轉換模塊,實現插樁,這裡分為兩種情況:- 對於第一次加載的class進行插樁操作,類加載的時候直接CustomClassTransformer進入agent處理。
- 對於之前已經加載了的類,使用retransform方法遍歷所有已經加載的類。
* @param inst 用於管理位元組碼轉換器 */ private void initTransformer(Instrumentation inst) throws UnmodifiableClassException { transformer = new CustomClassTransformer(inst); transformer.retransform(); }
-
跟進
CustomClassTransformer
,該類實現了ClassFileTransformer
接口(JVM TI接口)public class CustomClassTransformer implements ClassFileTransformer { public CustomClassTransformer(Instrumentation inst) { this.inst = inst; inst.addTransformer(this, true); addAnnotationHook(); }
-
跟進
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信息。
-
過濾並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流程
-
為啟動時候進行了插樁操作,當有類被 ClassLoader 加載時候,會把該類的位元組碼先交給自定義的 Transformer 處理。
-
自定義 Transformer 會判斷該類是否為需要 hook 的類,如果是會將該類交給 javassist 位元組碼處理框架進行處理。
-
javassist 框架會將類的位元組碼依照事件驅動模型逐步解析每個方法,當觸發了我們需要 hook 的方法,就會在方法的開頭或者結尾插入進入檢測函數的asm位元組碼。
-
把 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產品的優勢與缺陷如下:
優勢:
-
準確性高、防繞過能力強
waf往往誤報率高,繞過率高,市面上也有很多針對不同waf的繞過方式,而RASP技術防禦是根據請求上下文進行攔截的。
-
節省開發成本
Rasp的原理,決定了其節省了大量協議解析、解碼解密、防混淆防繞過的工序。節約了這部分工作的開發和,產品使用時分析解密的工作。
-
0day防禦與發現
RASP能夠洞察應用程序內部的情況,檢測到由新攻擊引起的行為變化,使它能夠對0day攻擊、應用自身未知漏洞對目標應用程序的影響作出反應,同時記錄完整的利用流程,方便安全人員分析發現0day漏洞。
-
加密流量檢測
因為Rasp是部署於業務內部,不關心流量傳輸和加解密過程,所以對於加密流量中攻擊行為的檢測要遠勝於傳統的流量檢測產品。
缺陷:
-
部署難度較大、成本較高
Rasp和業務產品的代碼結合得十分深入,所以部署面相對狹窄,對於不同用戶的hook需求,定製難度較大。而且隨着應用服務的語言不同,需要付出額外的開發和維護成本。
理想中的Java RASP實踐方式是使用agent進行無侵入部署,但是受限於JVM進程保護機制沒有辦法對目標類添加新的方法,所以無法反覆進行位元組碼插入。
Open RASP推薦的部署方式都是利用premain模式進行部署,這就造成了必須停止相關業務,加入相應的啟動參數,再開啟服務。而對甲方來說,重啟一次業務完成部署RASP的代價是比較高的。
-
與業務代碼結合較深、業務風險較大
因為rasp部署內容深入到業務代碼的執行中,出現bug或者其他漏洞風險時,很可能對業務造成極大的影響。如果在RASP所指定的邏輯中出現了嚴重錯誤,將直接將錯誤拋出在業務邏輯中。輕則當前業務中斷,重則整個服務中斷。例如在RASP的檢測邏輯中存在exit()這樣的利用,將直接導致程序退出。
-
規則編寫要求較高
相對於常規的waf和ids產品,有一個web poc就能快速編寫檢測規則,迅速上線,而不一定需要深刻理解漏洞的產生原理。
RASP的規則需要經過專業的安全研究人員反覆打磨,要求撰寫者對漏洞的理解十分深刻,才能找到合適的hook點,之後還要根據業務來定製化,將所有的可能影響業務的可能性都考慮進去,同時盡量減少誤報。但是由於攻擊者和規則編寫者水平的參差不齊,很容易導致規則遺漏,無法攔截相關攻擊,或產生大量的攻擊誤報。也因為規則編寫的複雜,產品對於最新的漏洞,可能無法及時覆蓋。
-
通用性欠缺
針對不同語言的服務,要使用完全不同的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