zoukankan      html  css  js  c++  java
  • 百度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、参考

  • 相关阅读:
    Kibana详细入门教程
    Python爬取食品商务网蔬菜价格数据,看看蔬菜最近的价格情况
    用Python爬取某蔬菜网的行情,分析底哪个地区的蔬菜便宜
    ES启动失败;java.lang.IllegalStateException: No factory method found for class org.apache.logging.log4j.c
    ELK 5.0 组件后台启动
    linux中/etc/security/limits.conf配置文件说明
    redis面试常见问题
    单线程的Redis为什么能支持10w+的QPS?
    Redis大Key优化
    redis中查找大key方法汇总
  • 原文地址:https://www.cnblogs.com/fsqsec/p/15160586.html
Copyright © 2011-2022 走看看