百度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);
  • g、对已经加载和后续加载的class进行transform,transform包括以下处理:
    • 判断class所在的文件路径是否为jar包,如果是则加入 loadedJarPaths ,这样可以获取所有加载的jar包,可用于三方组件检测

    • 调用hooks集合每个hook的isClassMatched与每个class进行匹配,判断是否需要hook,如果需要则进行hook处理

      • 主要是利用字节码技术 javaassist 对相应的字节码进行修改,插入hook逻辑
        // 类hook测ssrf个hook的sink
        @HookAnnotation
        public class URLConnectionHook extends AbstractSSRFHook {
         
        @Override
        public boolean isClassMatched(String className) { //前class要hook
        return "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,插入一段源码src
         
        insertBefore(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);
        }
        }
        }
        }
         
    • 调用serverDetector.detectServer,检测服务器类型,比如是否为springboot、tomcat、weblogic

      // springboot ,class为 org/apache/catalina/startup/Bootstrap
      public 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.hostname
    var url = params.url
    var ip = params.ip
    var reason = false
     
    // 法1 - 网IP为SSRF
    if (algorithmConfig.ssrf_userinput.action != 'ignore')
    {
    var ret
    ret = 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.0
    if (algorithmConfig.ssrf_obfuscate.action != 'ignore')
    {
    var reason = false
     
    if (!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;
      • 跨线程促发的漏洞检测调用链不完整;

3、参考

posted @ 2021-08-19 10:35  范世强  阅读(1837)  评论(1编辑  收藏  举报