问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
初识Rasp——Openrasp代码分析
安全管理
本文以Openrasp为例,介绍了rasp产品的部署与检测原理,详细分析了OpenRasp的部署和检测、拦截攻击的过程。分析了Rasp产品的绕过方式和优缺点。
0x00 什么是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所需的所有方法。 ```java 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类作为转换器: ```java 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方法: ```java System.out.println("=======this is agent premain function======="); inst.addTransformer(new TestTransformer()); ``` 用maven将该方法打成一个jar包之后,我们再创建一个可以被hook的带有main方法的程序: ```java package org.javaweb.test; public class TestAgent { public static void main(String\[\] args) { System.out.print("=======This is TestAgent Program!======="); } } ``` maven打包后,我们测试一下这个程序: ```java java \-jar testagent.jar ``` 可以看到输出了很多包的名字,因为我们在新增的TestTransformer中重新生成了。 ![image-20220915144546904.png](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-2cce6c0953b5dcbf6634c9e9780c2e6a7dd21bfb.png) 以上就是通过Agent来修改目标类输出结果的示例。 0x01 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)。 ```java 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: ```java 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引擎、初始化插件系统、配置核查、初始化字节码转换模块、初始化云管理模块等操作。 ```java 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.png](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-7e3589163fde721360660a34e4e95a81b7221d01.png) `InitFileWatcher`启动对js插件的文件监控,从而实现热部署,动态的增删js中的检测规则 ```java 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(): ```java public synchronized static void init() throws Exception { for (Type type : Type.values()) { checkers.put(type, type.checker);//加载所有类型的检测放入checkers,type.checker就是某种检测对应的类 } } ``` 这里的type参数就是各种JS的规则插件: ![image-20220916165708543.png](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-f02fb73232f8df9f8a4808aca449611859370bdc.png) 6. 最后调用`initTransformer(inst)`初始化字节码转换模块,实现插桩,这里分为两种情况: - 对于第一次加载的class进行插桩操作,类加载的时候直接CustomClassTransformer进入agent处理。 - 对于之前已经加载了的类,使用retransform方法遍历所有已经加载的类。 ```java \* @param inst 用于管理字节码转换器 \*/ private void initTransformer(Instrumentation inst) throws UnmodifiableClassException { transformer \= new CustomClassTransformer(inst); transformer.retransform(); } ``` 7. 跟进`CustomClassTransformer`,该类实现`ClassFileTransformer`接口(JVM TI接口) ```java public class CustomClassTransformer implements ClassFileTransformer { public CustomClassTransformer(Instrumentation inst) { this.inst \= inst; inst.addTransformer(this, true); addAnnotationHook(); } ``` 8. 跟进`addAnnotationHook`,获取com.baidu.openrasp.hook包下的AbstractClassHook子类,继续调用addHook添加hook点。 ```java 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注解的类。 ```java 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信息。 9. 过滤并hook,调用transformer.retransform() 对于已经被加载的类,会经由`retransform`方法到`transform`,而对于第一次加载的类,会直接被`transform`捕获(这里是重写了ClassFileTransformer) 遍历hooks获取所有Hook类,并通过Hook类的isClassMatched方法判断当前类是否Hook类的关注类,如果是,之后的具体操作则交由Hook类的tranformClass方法 。 ```java 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 从而载入虚拟机。 具体流程可以看官网址这张图: ![startup.png](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-e0fa1bf783f9873c697a21ebe8048d57fa94638d.png) ### Hook 分析案例: 以ProcessBuilderHook 为例: #### 开始hook插桩 根据`com.baidu.openrasp.transformer.CustomClassTransformer#isClassMatched`方法判断是否对目标class进行hook。 ```java 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)`方法进行了字节码的修改。 ```java 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的类在实例化之前会先调用插入的方法。 ```java 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: ```java 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内容: ```json { "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) ```java 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) ```java 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: ```java { "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): ```java public static boolean check(Type type, CheckParameter parameter) { return checkers.get(type).check(parameter);//调用检测类进行参数检测 } ``` 此处会根据传入的type来选择调用相对应的checkers,这里的checkers就是前面CheckerManager.init()的时候放入的内容。 由于我们这个demo放入的内容是command命令,因此会调用V8AttackChecker的check方法。 ![image-20220916165708543.png](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-aca59de99477046a1eede3add1f2bc2347493806.png) 之后再对插件层层追踪,跟进V8AttackChecker的checkParam方法 ```java /\*\* \* 执行js插件进行安全检测 \* @param checkParameter 检测参数 {@link CheckParameter} \* @return 检测结果 \*/ @Override public List<EventInfo\> checkParam(CheckParameter checkParameter) { return JS.Check(checkParameter); } ``` 跟进JS.Check(checkParameter),就能看到调用JS插件进行检测的代码了: ![image-20221012183222441.png](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-9ab0ea706280885b75097a83d800a51fc7bbb432.png) OpenRasp绕过 ---------- 互联网上有很多大佬写过关于Openrasp绕过的文章,但是普遍泛用型较差,或者需要获取shell,利用前提较为困难,这里举一个Kcon黑客大会上分享过的冰蝎4.0的例子: 冰蝎作为一个常用的后门连接工具,在更新4.0之后加入了随机生成类名混淆绕过rasp的功能,测试之后,原本3.0时代类名为rebeyond的类,都被混淆成了随机类名,使得rasp虽然可以检测到命令执行,但是无法判断是冰蝎的后门连接,也较难针对性的进行阻断: ![image-20221013194112265.png](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-82ca2bad2934004991fcc8ceab4f1463d565d65b.png) ***随机生成的类名*** 冰蝎混淆使用的代码: ```java 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; } ``` 0x02 总结 ======= 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方式,几乎等同于两套产品,缺乏泛用型。 0x03 参考 ======= 浅谈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>
发表于 2022-10-20 09:30:02
阅读 ( 8622 )
分类:
安全工具
1 推荐
收藏
0 条评论
请先
登录
后评论
drag0nf1y
1 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!