百度OpenRasp实现原理
1、基础知识
1.1、java插桩技术 :javaagent
-
允许在jvm启动时优先执行agent代码,通常agent代码用来添加Transformer,提供了对class在加载时进行字节码修改的机会。
-
java启动时增加参数java -javaagent:/Users/didi/Downloads/openrasp-javaagent/boot/target/rasp.jar -jar myapplication.jar
-
jar包构建时设置入口 <Premain-Class>com.baidu.openrasp.Agent</Premain-Class> ,启动时会进入此类的premain方法执行
public static void premain(String agentArg, Instrumentation inst) {inst.addTransformer(new CustomClassTransformer());}
1.2、java字节码修改:javaassist
- 其他类似的库:
- javaassist:openrasp用的这个,学习门槛低,源码级别,不需要掌握字节码相关技术。
- asm:字节码级别,学习门槛高,需要掌握字节码技术。性能好
- cglib:spring aop用的这个进行字节码增强
- bcel:apache
2、OpenRasp原理
2.1、openrasp介绍
- 百度开源rasp,提供了完整的rasp + iast的功能,包括以下模块:
- java + php 的探针,对应用插桩,收集应用运行时数据; (java)
- v8引擎 + rasp检测插件,根据hook信息判断是否为攻击;(javascript)
- 管理控制台,用于接收及查看rasp攻击事件、iast漏洞信息、配置下发、应用管理等功能;(golang)
- iast fuzz功能,用户对探针采集的流量增加poc并进行重放 (python);
- rasp支持的攻击类型:
- iast支持的漏洞类型:
- 命令注入
- 目录遍历
- PHP eval代码执行
- 文件上传
- 文件包含
- 任意文件读取
- 任意文件写入
- SQL注入
- SSRF
- Java XXE
2.2、启动过程
- a、通过插桩技术,进入openrasp入口com.baidu.openrasp.Agent.premain方法
- b、premain方法执行初始化,并加载引擎模块,引擎模块执行后续动作
- c、加载配置:本地yml文件 + 云端定期拉取,配置比如:每个攻击类型是否拦截还是只记录、agent身份认证、邮件&钉钉报警、控制台密码
loadConfigFromFile(new File(configFilePath), true);
- e、CheckerManager初始化,为不同的攻击类型,设置不同的检测checker,大部分攻击使用V8AttackChecker检测
public synchronized static void init() throws Exception {for (Type type : Type.values()) {checkers.put(type, type.checker);}}<!-- // js插件检测--><!--SQL("sql", new V8AttackChecker(), 1),--><!--COMMAND("command", new V8AttackChecker(), 1 << 1),--><!--DIRECTORY("directory", new V8AttackChecker(), 1 << 2),--><!--REQUEST("request", new V8AttackChecker(), 1 << 3),--><!--READFILE("readFile", new V8AttackChecker(), 1 << 5),--><!--WRITEFILE("writeFile", new V8AttackChecker(), 1 << 6),--><!--FILEUPLOAD("fileUpload", new V8AttackChecker(), 1 << 7),--><!--RENAME("rename", new V8AttackChecker(), 1 << 8),--><!--XXE("xxe", new V8AttackChecker(), 1 << 9),--><!--OGNL("ognl", new V8AttackChecker(), 1 << 10),--><!--DESERIALIZATION("deserialization", new V8AttackChecker(), 1 << 11),--><!--WEBDAV("webdav", new V8AttackChecker(), 1 << 12),--><!--INCLUDE("include", new V8AttackChecker(), 1 << 13),--><!--SSRF("ssrf", new V8AttackChecker(), 1 << 14),--><!--SQL_EXCEPTION("sql_exception", new V8AttackChecker(), 1 << 15),--><!--REQUESTEND("requestEnd", new V8AttackChecker(), 1 << 17),--><!--DELETEFILE("deleteFile", new V8AttackChecker(), 1 << 18),--><!--MONGO("mongodb", new V8AttackChecker(), 1 << 19),--><!--LOADLIBRARY("loadLibrary", new V8AttackChecker(), 1 << 20),--><!--SSRF_REDIRECT("ssrfRedirect", new V8AttackChecker(), 1 << 21),--><!--RESPONSE("response", new V8AttackChecker(false), 1 << 23),--><!--LINK("link", new V8AttackChecker(), 1 << 24),--><!--// java本地检测--><!--XSS_USERINPUT("xss_userinput", new XssChecker(), 1 << 16),--><!--SQL_SLOW_QUERY("sqlSlowQuery", new SqlResultChecker(false), 0),--><!--// 安全基线检测--><!--POLICY_LOG("log", new LogChecker(false), 1 << 22),--><!--POLICY_MONGO_CONNECTION("mongoConnection", new MongoConnectionChecker(false), 0),--><!--POLICY_SQL_CONNECTION("sqlConnection", new SqlConnectionChecker(false), 0),--><!--POLICY_SERVER_TOMCAT("tomcatServer", new TomcatSecurityChecker(false), 0),--><!--POLICY_SERVER_JBOSS("jbossServer", new JBossSecurityChecker(false), 0),--><!--POLICY_SERVER_JBOSSEAP("jbossEAPServer", new JBossEAPSecurityChecker(false), 0),--><!--POLICY_SERVER_JETTY("jettyServer", new JettySecurityChecker(false), 0),--><!--POLICY_SERVER_RESIN("resinServer", new ResinSecurityChecker(false), 0),--><!--POLICY_SERVER_WEBSPHERE("websphereServer", new WebsphereSecurityChecker(false), 0),--><!--POLICY_SERVER_WEBLOGIC("weblogicServer", new WeblogicSecurityChecker(false), 0),--><!--POLICY_SERVER_WILDFLY("wildflyServer", new WildflySecurityChecker(false), 0),--><!--POLICY_SERVER_TONGWEB("tongwebServer", new TongwebSecurityChecker(false), 0),--><!--POLICY_SERVER_BES("bes", new BESSecurityChecker(false), 0);-->
- f、构造CustomClassTransformer,设置到Instrumentation:关键动作如下:
transformer = new CustomClassTransformer(inst);public CustomClassTransformer(Instrumentation inst) {this.inst = inst;inst.addTransformer(this, true);addAnnotationHook();}
- ServerDetectorManager构造,并创建所有服务器检测类,比如tomcat、springboot、webloigc、jboss、jetty、resin、dubbo、bes、ubdertow等
private ServerDetectorManager serverDetector = ServerDetectorManager.getInstance();
- 对所有com.baidu.openrasp.hook包里的class进行扫描,判断每个class是否设置了注解HookAnnotation,如果设置了则加入CustomClassTransformer的hooks集合
Set<Class> classesSet = AnnotationScanner.getClassWithAnnotation(SCAN_ANNOTATION_PACKAGE, HookAnnotation.class);
- ServerDetectorManager构造,并创建所有服务器检测类,比如tomcat、springboot、webloigc、jboss、jetty、resin、dubbo、bes、ubdertow等
- g、对已经加载和后续加载的class进行transform,transform包括以下处理:
-
判断class所在的文件路径是否为jar包,如果是则加入 loadedJarPaths ,这样可以获取所有加载的jar包,可用于三方组件检测
-
调用hooks集合每个hook的isClassMatched与每个class进行匹配,判断是否需要hook,如果需要则进行hook处理
- 主要是利用字节码技术 javaassist 对相应的字节码进行修改,插入hook逻辑
// 这个类hook发送网络请求的类,用于检测ssrf;这个hook点类似于白盒的sink点@HookAnnotationpublic class URLConnectionHook extends AbstractSSRFHook {@Overridepublic boolean isClassMatched(String className) { //这个方法判断当前class是否需要hookreturn "sun/net/www/protocol/http/HttpURLConnection".equals(className) ||"weblogic/net/http/HttpURLConnection".equals(className);}@Override //这个方法执行真正的hook逻辑,完成对class的修改protected void hookMethod(CtClass ctClass) throws IOException, CannotCompileException, NotFoundException {String src = getInvokeStaticSrc(URLConnectionHook.class, "checkHttpConnection","$0", URLConnection.class);// 通过javaassit修改class,插入一段源码srcinsertBefore(ctClass, "getInputStream", "()Ljava/io/InputStream;", src);src = getInvokeStaticSrc(URLConnectionHook.class, "onExit", "$0", Object.class);insertAfter(ctClass, "getInputStream", "()Ljava/io/InputStream;", src, true);}public static void onExit(Object urlConnection) {try {if (isChecking.get() && !isExit.get() && URLConnectionRedirectHook.urlCache.get() != null) {// 以下会继续调用 getinpustream isExit 避免死循环isExit.set(true);HashMap<String, Object> cache = originCache.get();HashMap<String, Object> redirectCache = getSsrfParamWithURL(URLConnectionRedirectHook.urlCache.get());if (cache != null && redirectCache != null) {AbstractRedirectHook.checkRedirect(cache, redirectCache,((HttpURLConnection) urlConnection).getResponseMessage(), ((HttpURLConnection) urlConnection).getResponseCode());}}} catch (Exception e) {// ignore} finally {isChecking.set(false);originCache.set(null);isExit.set(false);URLConnectionRedirectHook.urlCache.set(null);}}private static HashMap<String, Object> getSsrfParamWithURL(URL url) {try {String host = null;String port = "";if (url != null) {host = url.getHost();int temp = url.getPort();if (temp > 0) {port = temp + "";}}if (url != null && host != null) {return getSsrfParam(url.toString(), host, port, "url_open_connection");}} catch (Exception e) {// ignore}return null;}public static void checkHttpConnection(URLConnection urlConnection) {if (!isChecking.get()) {isChecking.set(true);if (urlConnection != null) {URL url = urlConnection.getURL();HashMap<String, Object> param = getSsrfParamWithURL(url);checkHttpUrl(param);originCache.set(param);}}}}
- 主要是利用字节码技术 javaassist 对相应的字节码进行修改,插入hook逻辑
-
调用serverDetector.detectServer,检测服务器类型,比如是否为springboot、tomcat、weblogic
// springboot 判断,class是否为 org/apache/catalina/startup/Bootstrappublic boolean isClassMatched(String className) {return "org/apache/catalina/startup/Bootstrap".equals(className);}
-
- h、注册应用,向控制台注册应用,并且定期发送心跳、上报漏洞、异常、组件依赖信息等。
2.3、攻击检测:
- hook点拿到对应的参数信息后,交给hook指定攻击类型的checker处理(在启动时为每个攻击类型设置过对应的checker),大部分攻击使用V8AttackChecker检测
- V8AttackChecker调用v8引擎,通过js插件来进行判断是否为攻击、是否进行拦截
- V8AttackChecker -> jni方式调用chrome v8 -> js plugin
function check_ssrf(params, context, is_redirect) {var hostname = params.hostnamevar url = params.urlvar ip = params.ipvar reason = false// 算法1 - 当参数来自用户输入,且为内网IP,判定为SSRF攻击if (algorithmConfig.ssrf_userinput.action != 'ignore'){var retret = check_internal(params, context, is_redirect)// 过滤非HTTP请求(dubbo)var header = context.header || {}if (ret && Object.keys(header).length != 0) {return ret}}// 算法2 - 检查常见探测域名if (algorithmConfig.ssrf_common.action != 'ignore'){if (is_hostname_dnslog(hostname)){return {action: algorithmConfig.ssrf_common.action,message: _("SSRF - Requesting known DNSLOG address: %1%", [hostname]),confidence: 100,algorithm: 'ssrf_common'}}}<!-- domains: [--><!-- '.vuleye.pw',--><!-- '.ceye.io',--><!-- '.exeye.io',--><!-- '.vcap.me',--><!-- '.xip.name',--><!-- '.xip.io',--><!-- '.sslip.io',--><!-- '.nip.io',--><!-- '.burpcollaborator.net',--><!-- '.tu4.org',--><!-- '.2xss.cc',--><!-- '.bxss.me',--><!-- '.godns.vip',--><!-- '.pipedream.net' // requestbin 新地址--><!--]-->// 算法3 - 检测 AWS/Aliyun/GoogleCloud 私有地址: 拦截IP访问、绑定域名访问两种方式if (algorithmConfig.ssrf_aws.action != 'ignore'){if (ip == '169.254.169.254' || ip == '100.100.100.200'|| hostname == '169.254.169.254' || hostname == '100.100.100.200' || hostname == 'metadata.google.internal'){return {action: algorithmConfig.ssrf_aws.action,message: _("SSRF - Requesting AWS metadata address"),confidence: 100,algorithm: 'ssrf_aws'}}}// 算法4 - ssrf_obfuscate//// 检查混淆:// http://2130706433// http://0x7f001//// 以下混淆方式没有检测,容易误报// http://0x7f.0x0.0x0.0x1// http://0x7f.0.0.0if (algorithmConfig.ssrf_obfuscate.action != 'ignore'){var reason = falseif (!isNaN(hostname) && hostname.length != 0){reason = _("SSRF - Requesting numeric IP address: %1%", [hostname])}// else if (hostname.startsWith('0x') && hostname.indexOf('.') === -1)// {// reason = _("SSRF - Requesting hexadecimal IP address: %1%", [hostname])// }if (reason){return {action: algorithmConfig.ssrf_obfuscate.action,message: reason,confidence: 100,algorithm: 'ssrf_obfuscate'}}}// 算法5 - 特殊协议检查if (algorithmConfig.ssrf_protocol.action != 'ignore'){// 获取协议var proto = url.split(':')[0].toLowerCase()if (algorithmConfig.ssrf_protocol.protocols.indexOf(proto) != -1){return {action: algorithmConfig.ssrf_protocol.action,message: _("SSRF - Using dangerous protocol: %1%://", [proto]),confidence: 100,algorithm: 'ssrf_protocol'}}}return false}
2.4、iast漏洞检测:
- 非重放
- 对rasp的攻击事件数据按照stack合并去重后作为漏洞
- 重放
- 利用javaagent抓取请求信息,然后参数加上poc后重放(fuzz)
- 业界情况
- 大部分公司已经在开发
- 京东、阿里、华为、携程、58、美团、百度、腾讯、华泰、快手、字节
- 优点:
- java开源的比较成熟,可以快速拿来用,其他语言较少;
- 检测准确率高、覆盖率也不错(如果是测试环境取决于QA的测试覆盖度);
- 可以拿到完整的调用链,类似白盒的数据流但也不太一样;
- 可以很方便的支持dubbo之类的框架;
- 可以比较方便的拿到所有三方组件依赖信息;
- 不存在白盒那种路径爆炸、spring依赖注入之类的问题;
- 完整的获取API接口信息、请求和响应数据,检测敏感数据的流向、收集、输出很方便;
- 可以更精准的检测日志打印敏感数据、数据库存储敏感数据等目前黑白盒检测不了的风险。
- 可以检测web弱口令;中间件配置类安全问题;
- 缺点:
- 部署成本较高,生产环境对稳定性、性能要求较高
- 每种语言的实现方式不一样,需要适配多种开发语言,目前开源的方案java相对来说比较成熟,其他语言实践较少。
- 对业务应用有侵入性、如果存在bug可能导致应用出问题,比如crash;
- 跨线程促发的漏洞检测调用链不完整;
- 大部分公司已经在开发