wxvl/doc/CVE-2024-45216 Authentication bypass in Apache Solr.md

22 KiB
Raw Blame History

CVE-2024-45216 Authentication bypass in Apache Solr

hulala14 呼啦啦安全 2024-11-19 03:33

前言最近solr爆出了新的身份绕过漏洞工作中要对该漏洞进行复现正好将分析的过程记录一下在通报中可以看到该漏洞的评级那是相当高CVSS3直接高达9.8分,在描述中可以得出这个漏洞主要是因为使用 PKIAuthenticationPlugin 的 Solr 实例(在使用 Solr 身份验证时默认启用)容易受到身份验证绕过的影响

在solr的身份验证中PKIAuthenticationPlugin 是 Apache Solr 中用于身份验证的一个插件。它主要用于在分布式 SolrCloud 环境中通过公钥基础设施PKI来实现节点之间的相互认证和通信安全而PKIAuthenticationPlugin的验证是通过什么数据来体现的呢通过查询官方文档可知对于每个传出的请求PKIAuthenticationPlugin添加一个特殊的标题'SolrAuth'其中包含使用该节点的私钥加密的时间戳和主体。公钥通过API公开任何节点只要需要就可以读取。
环境搭建当我们初步了解了漏洞的基本信息之后开始搭建环境因为之前咱们说的PKIAuthenticationPlugi主要用于在分布式 SolrCloud 环境中所以咱们要搭建一个SolrCloud集群环境集群环境基本身份验证需要我们首先创建一个security.json文件对于基本身份验证security.json文件必须有一个authentication部分它定义用于身份验证的类。可以在创建文件时添加用户名和密码例如sha256(password+salt) hash或者可以稍后使用基本验证API添加。下面的配置信息为1.启用基本身份验证和基于规则的授权插件。2.参数 "blockUnknown": true 表示不允许未经身份验证的请求通过。3.已定义了一个名为 "solr" 的用户,其中有密码 "SolrRocks"。4."admin" 角色已定义并且具有编辑安全设置的权限。5."solr" 用户已被定义为 "admin" 角色。``` { "authentication": { "class": "solr.BasicAuthPlugin", "credentials": { "solr": "IV0EHq1OnNrj6gvRCwvFwTrZ1+z1oBbnQdiVC3otuq0= Ndd7LKvVBAaZIF0QAVi1ekCfAJXr1GGfLtRUXhgrF8c=" }, "blockUnknown": false, "": { "v": 0 } }, "authorization": { "class": "solr.RuleBasedAuthorizationPlugin", "permissions": [ { "name": "security-edit", "role": "admin" } ], "user-role": { "solr": "admin" } } }

然后我们通过镜像来搭建集群环境下面的配置文件docker-compose.yaml中首先要注意的就是把上面的权限配置设置复制到容器中[your-local-path]security.json记得替换为自己的目录然后在文件中我们也打开了5006的java远程调试端口创建了一个solr的network网络```
version: '3.7'
services:
  solr1:
    image: solr:9.6.0
    container_name: solr1e
    ports:
     - "8983:8983"
     - "5006:5006"
    environment:
      - ZK_HOST=zoo1:2181,zoo2:2181,zoo3:2181
      - SOLR_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5006
    volumes:
      - [your-local-path]security.json:/var/solr/data/security.json
    networks:
      - solr
    depends_on:
      - zoo1
      - zoo2
      - zoo3


  solr2:
    image: solr:9.6.0
    container_name: solr2
    ports:
     - "8982:8983"
    environment:
      - ZK_HOST=zoo1:2181,zoo2:2181,zoo3:2181
    networks:
      - solr
    depends_on:
      - zoo1
      - zoo2
      - zoo3


  solr3:
    image: solr:9.6.0
    container_name: solr3
    ports:
     - "8981:8983"
    environment:
      - ZK_HOST=zoo1:2181,zoo2:2181,zoo3:2181
    networks:
      - solr
    depends_on:
      - zoo1
      - zoo2
      - zoo3


  zoo1:
    image: zookeeper:3.8
    container_name: zoo1
    restart: always
    hostname: zoo1
    ports:
      - 2181:2181
      - 7001:7000
    environment:
      ZOO_MY_ID: 1
      ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
      ZOO_4LW_COMMANDS_WHITELIST: mntr, conf, ruok
      ZOO_CFG_EXTRA: "metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider metricsProvider.httpPort=7000 metricsProvider.exportJvmInfo=true"
    networks:
      - solr

  zoo2:
    image: zookeeper:3.8
    container_name: zoo2
    restart: always
    hostname: zoo2
    ports:
      - 2182:2181
      - 7002:7000
    environment:
      ZOO_MY_ID: 2
      ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
      ZOO_4LW_COMMANDS_WHITELIST: mntr, conf, ruok
      ZOO_CFG_EXTRA: "metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider metricsProvider.httpPort=7000 metricsProvider.exportJvmInfo=true"
    networks:
      - solr

  zoo3:
    image: zookeeper:3.8
    container_name: zoo3
    restart: always
    hostname: zoo3
    ports:
      - 2183:2181
      - 7003:7000
    environment:
      ZOO_MY_ID: 3
      ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
      ZOO_4LW_COMMANDS_WHITELIST: mntr, conf, ruok
      ZOO_CFG_EXTRA: "metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider metricsProvider.httpPort=7000 metricsProvider.exportJvmInfo=true"
    networks:
      - solr

networks:
  solr:

在环境搭建之后进入到solr1e的容器内部执行命令这是因为在SolrCloude模式中必须上传security.json到ZooKeeper``` solr zk cp /var/solr/data/security.json zk:/security.json -z zoo1:2181,zoo2:2181,zoo3:2181

通过账号 solr/SolrRocks登录上之后因为咱们之前的blockUnknown设置的是false所以需要手动开一下禁止未经身份验证的请求通过  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM98uiaMAiaumQIUlXB1oEr8ibYGexlFeIk4gGF0K2J0nYOUpXtTvO0HkTfw/640?wx_fmt=png&from=appmsg "")  
漏洞分析前期分析在茫茫大海中搜寻找到了当时的issues该issues被创建于2024-8-21号  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9u6jtiat9zaRzgomKZgMianhZdRTtQ9tJAax46uMKm9ANkib7PUA4SNPdQ/640?wx_fmt=png&from=appmsg "")  
在描述中我们可以看到作者是这样描述的By using ":/admin/info/key" at the end of the URL, the PKIAuthenticationPlugin can be bypassed, so that non-authorized users can access protected APIs.通过这个我们找到了代码修复位置https://issues.apache.org/jira/secure/attachment/13071024/SOLR-17417.patch  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9NBH3SB49ibuRvNnYBw8dyLr1jDRBX1EoJaYedYwic2piaVD0bccic7K4JQ/640?wx_fmt=png&from=appmsg "")  
调试分析GET请求找到位置了开始调试分析具体过程先构造一个符合漏洞的请求包  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9RbxGMXyQIaNq23v61PxdP82myJS7Ycp08VHbfjlgNwO33S6ayH5tTA/640?wx_fmt=png&from=appmsg "")  
我们在dofilter链中的身份验证哪里进行断点开始调试运行到SolrDispatchFilter.java的authenticateRequest方法时刚开始authenticationPlugin还是BasicAuthPlugin  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9ic9JSweibWA74xdzic4NljUCJBTRVJyrURNbMg96jubjtq6a8diahhXQzA/640?wx_fmt=png&from=appmsg "")  
当继续运行后在下面会对是否为PKIAuthenticationPlugin进行验证当程序检测到我们呢请求头中的SolrAuth: aaaa时就会把当前的验证authenticationPlugin更换为PKIAuthenticationPlugin  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9ZR4rc7MpT7FCRZuKVTXHCX9Ttg1ic8Wmib07nPv5LxRaR0d6VRTtSLyA/640?wx_fmt=png&from=appmsg "")  
  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9gqyhZ2OXCsXA8NEd7IibEDqNFLMINOHdrDHBFVaC7YKxmd2FvdQcalg/640?wx_fmt=png&from=appmsg "")  
在最后根据authenticationPlugin的值执行不同身份验证插件的authenticate方法  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM95RylcRtd9mVxaExElkmHC6wrsedCa4ibREtibMpCibJB1X07DAyMI173w/640?wx_fmt=png&from=appmsg "")  
我们在这里就进入到了漏洞代码处也就是PKIAuthenticationPlugin.java的doAuthenticate方法  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9iakYJWelXHyY0NELicqRnpCGt9kkhtKg4USEU98RQmmthnT0ItibIguvA/640?wx_fmt=png&from=appmsg "")  
在下面的153行代码以后才是PKIAuthenticationPlugin的decipherHeader方法对于SolrAuth的提取以及有效性验证在最后如果验证全部通过之后会返回return ture  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9rezjokbr5TOoiaND0m72U10OMESB2tlfNWXaPWicsyRsG4EcA8mrgD6A/640?wx_fmt=png&from=appmsg "")  
我们着重查看142行-151行的这部分代码如果if语句符合条件然后顺利执行完就能返回true绕过了下来的验证部分。这里先是提取出来了requesturI然后检测requesturI是否是以PublicKeyHandler.PATH结尾  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9GudYPjLCpsgmeiavCdmUuMuldrKziaEfYfaWgp91zPsEZumgNDKI3DnA/640?wx_fmt=png&from=appmsg "")  
我们查找发现PATH的定义为/admin/info/key也就是说只要我们访问的url链接只要是以它结尾就能进入if代码块  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9uPBlvmkWnpmraNWxuEqEGOG948JWTOZhExaUBgU1s17LfhhgCfIARg/640?wx_fmt=png&from=appmsg "")  
然后通过filterChain.doFilter(request, response)将当前请求和响应传递给过滤器链中的下一个过滤器进行处理。当前只是探索出了绕过的第一步,就是在结尾加上/admin/info/key我们接着代码往下一个Filter过滤器分析在下一个filter过滤器中经过了dispatch()---->call()---->init(),直到init()方法中我们可以看到有一点微妙  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9xMzsbA7xzfeA3gcsloiaicTamkaCmdYRjoaAkYJoRIENct1ZYs874huw/640?wx_fmt=png&from=appmsg "")  
  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9nxc9wVv7nSLuvftpUZFicCiaYpGKFLnZldEVv49OoYvKA9ZAa0xicXMqA/640?wx_fmt=png&from=appmsg "")  
  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9NS97HhhGGIEYuVu9ZSib9TOibs5icSFC47f7wibI9Zg3KRvyJm9WUDuQ5A/640?wx_fmt=png&from=appmsg "")  
在init()方法中我们可以看到代码会对我们请求的url进行操作从“:”开始截断,只获取“:”之前的路径然后进入到cores.getRequestHandler对path进行检查  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9Fg9TBH68sRs8dJczGxvWo61FgJJ9u860wGwQj5ZxibMZ36oP7alxphQ/640?wx_fmt=png&from=appmsg "")  
在getRequestHandler方法中我们可以看到会对我们传入的path也就是handlerName进行检查我们需要保证handler的值不能为空如果是空的话在上面的代码init方法中我们就无法进入if代码块从而执行以下代码获得admin权限了requestType = RequestType.ADMIN;action = ADMIN;  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9W5OqUv2zaC32hrWhiaOjf6FuXQpTczY9kpyFT50mH9gMU3aNzewhjGA/640?wx_fmt=png&from=appmsg "")  
进入get方法发现只是查看name也就是咱们截断后的路径在不在registry中  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9jSj0zSw2GaibOeG0eluib2xVghiarnicpcBp3gDxewMorJ1NpFwfb2MYrQ/640?wx_fmt=png&from=appmsg "")  
registry为  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9vETYWibHsxS7TlKRwrIhjOPg0BlhUGpia27I1vtmJkX4rIJHR4ZPCImg/640?wx_fmt=png&from=appmsg "")  
整理后也就是下面的接口路径```
[/admin/zookeeper, /admin/configs, /admin/info/key, /admin/collections, /admin/authorization, /admin/cores, /admin/info, /admin/zookeeper/status, /admin/authentication, /admin/metrics]

通过之后我们继续看call()中init()后面的代码

在这其中我们着重看一下authorize()方法

我们先获取授权 authzPlugin = cores.getAuthorizationPlugin()正常的authzPlugin数据为

然后进入 authzPlugin.authorize(context)进行操作对权限进行了一次校验但是因为我们之前已经得到了admin权限很容易就通过了


接下来一路代码会默认的走在最后返回的时候有一个checkPathPerm()方法,进入该方法

在这段代码中一直在传递的permissions它的值为{"name":"security-edit","role":"admin"}这个其实就是咱们在刚开始的security.json中设置的admin权限


我们继续下来就是一些对于权限和咱们请求之间的判断一路走到getPermissionName()方法

在这里的话会对get请求和post请求两个区分开如果是get请求的话会返回security-readpost的话是返回security-edit这里要记住咱们之前在搭建环境的时候设置的就是admin权限为security-edit


随后会对咱们的security-edit和刚才获得的get请求的security-read进行了对比得到applies为false

那么返回到这里就是false会跳转到return null进行返回

为null之后checkPathPerm方法返回的就是rsp为200的一个返回值

最后返回到authorize方法我们可以看到statusCode就是刚才rsp.statusCode,也就是200然后一路跳过下面四个if判断成功返回null


执行完成返回到call()方法authzFailure经过一系列判断为null走到下面因为admin权限进入handleAdminRequest()执行,成功绕过了身份验证这一段得到了执行

POST请求上面的调试是GET请求进行的过程但是我发现大家使用poc全都是止步于get请求那么POST进行数据的修改可不可以呢我们进行尝试首先的话接口肯定要挑咱们上面分析的registry支持的接口整理后也就是下面的接口路径``` [/admin/zookeeper, /admin/configs, /admin/info/key, /admin/collections, /admin/authorization, /admin/cores, /admin/info, /admin/zookeeper/status, /admin/authentication, /admin/metrics]

刚刚好,添加用户的接口/admin/authentication就在其中咱们进行尝试奇怪的是竟然报了401  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9605pfDrdn13rhVXeibkMfN3QPcPJV8qTC5icxu5YV2Bxh3xKZUDG6DqA/640?wx_fmt=png&from=appmsg "")  
造成这样的原因是因为在上面的get与post请求进行区分的时候POST获得的是PermissionNameProvider.Name.SECURITY_EDIT_PERM即为security-edit  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9bxOLmmvBhqGOicy7S2iaJWaOcjxjSO7UCmNx4NyicLHVynK9UOnz2HYEQ/640?wx_fmt=png&from=appmsg "")  
咱们的admin权限为security-edit于是两个进行判断之后的applies值为true  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9ricfD9ibAWEmAscFZy6pfDawJu5rAJ8AXNicRAjDQHZiaQQiacTvInT1dZw/640?wx_fmt=png&from=appmsg "")  
在这里于是也因为if判断成功发生了变动返回的是permission并不是null  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9oibYHSKKicicJuoBjhsLibm5EMXYDq8Rza9Iw99R2IskYLicHtm8vFyLAZg/640?wx_fmt=png&from=appmsg "")  
在这里并没有直接返回MatchStatus.NO_PERMISSIONS_FOUND而是走到了下面determineIfPermissionPermitsPrincipal中进行了一次getUserPrincipal()  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9AI1azkWwibFBBTytYxjic2wbqpKOtapRiad7puaUTIicytxNaIIWyHg1RQ/640?wx_fmt=png&from=appmsg "")  
  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9hE7vHCnTd1HH6DAUJMK1HA3aSdyIgmPQ83HDClHtRCFBcvQCv3J3Dg/640?wx_fmt=png&from=appmsg "")  
  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9Vg5tW8OjicCz7zia4jvX1ichPOa4YHbjehT98GJFYWU1lbcXZ2ADgFy0g/640?wx_fmt=png&from=appmsg "")  
在这里进行了一次对当前身份的检测咱们因为是绕过走到这里的当然没有返回为null于是喜提401  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9PgXS96UR2F8ib5uXKfDspW2Y69pR5pZbgiagqag1tribsibJ1iaxtrl5FMQ/640?wx_fmt=png&from=appmsg "")  
  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9orQjrEVtiaoHicMt8gvPQzJEykiap32OIDaxVaibO9kiclgFPPe0LwibhAhg/640?wx_fmt=png&from=appmsg "")  
最后喜提401"Authentication failed, Response code: 401"那么根本原因是在哪里的呢就是这里post请求的security-edit和咱们当前admin请求的对比  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9ricfD9ibAWEmAscFZy6pfDawJu5rAJ8AXNicRAjDQHZiaQQiacTvInT1dZw/640?wx_fmt=png&from=appmsg "")  
POST请求成功的条件当我们修改最初的权限将admin的security-edit权限删去之后  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9JGAxo0OiaY86vnMlsTBR3Ocqv9KCvfE3rXMsXRlOluEyKsI16bTLM5A/640?wx_fmt=png&from=appmsg "")  
回到了perssions判断这里因为之前get请求时咱们的权限配置为{"name":"security-edit","role":"admin"}但是咱们这次将这些都删除了所以在这里perssions自然为null  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9xqLGfHUYia7DOj5wygdW5X5WbWbnJQtbciaDkvh0pkNULM1L6JgHYwiaw/640?wx_fmt=png&from=appmsg "")  
直接返回rep.statusCode=200下来就直接运行成功了  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9jGAM4o3iccmQqkRI2oCdgnibSsJAOG20WMeKRIV590AVMrB4fCqpiaytQ/640?wx_fmt=png&from=appmsg "")  
  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9EMuDAqeLmEPG2LP1b8jSYsIeIHQ0j0qhiazxx0BI2o7tBAaktMCndkQ/640?wx_fmt=png&from=appmsg "")  
  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9SjZGwf40HicQ9RZcwI85iazBnNzA9kkrbTVdiaTMWHBwdicNaibVslDghzQ/640?wx_fmt=png&from=appmsg "")  
账号添加成功  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9AfoavrGsNYcIdro0gOqOlD1AbBx1xciaILrSDkBvxVvgZ5PQsOuLubQ/640?wx_fmt=png&from=appmsg "")  
总结这次solr的权限绕过漏洞主要还是下面的这部分代码造成的开发者也已经删除漏洞代码。而在漏洞的复现中也是发现get和post竟然不都是成功的post请求的利用会更加苛刻一些。  
![](https://mmbiz.qpic.cn/mmbiz_png/L6MCxPWAoPnz7ngDSYNHJ6L3PpmdIZM9NBH3SB49ibuRvNnYBw8dyLr1jDRBX1EoJaYedYwic2piaVD0bccic7K4JQ/640?wx_fmt=png&from=appmsg "")  
  
  
本文章于今天早上首发于奇安信社区,可能因为是第一次提交,审核稍稍慢了一丢丢,不过这次文章通过了之后加了助理微信,下来就可以大大加快审核速度,文章在社区的链接为  
  
https://forum.butian.net/article/623  
  
  
本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者及本公众号不为此承担任何责任  
。  
  
  
ps:如果想一块讨论  
渗透  
&代码审计&APP逆向可以添加下面的微信号相互交流  
  
![](https://mmbiz.qpic.cn/mmbiz_jpg/L6MCxPWAoPmgTQ0gAcmbtASnqDvCc0f1Ba8h6X8azg1ec3U2v09dgj4iclzgLMCYJkPWEMrndzVZ0LvPXyzhuPw/640?wx_fmt=other&from=appmsg&wxfrom=5&wx_lazy=1&wx_co=1&tp=webp "")  
欢迎关注公众号“呼啦啦安全”,原创技术文章第一时间推送。