公司项目在做等保测评的时候发现网关项目存在Actuator未授权访问漏洞,在 Actuator 启用的情况下,如果没有做好相关权限控制,非法用户可通过访问默认的执行器端点(endpoints)来获取应用系统中的监控信息,从而导致信息泄露甚至服务器被接管的事件发生。默认配置会出现接口未授权访问,部分接口会泄露网站流量信息和内存信息等,使用Jolokia库特性甚至可以远程执行任意代码,获取服务器权限。
直接通过访问接口 http://服务器ip:程序端口/actuator/env,就返回了服务器的一些重要信息。
网上解决方案大概就两种:
① 直接为了等保测评将Actuator监控直接关掉,等测评通过后再打开。
management:
endpoints:
enabled: false
这个就完全是为了应付等保测评了,方案不可取
②引入security
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
开启Security,配置访问权限
spring:
security:
user:
username: security账号
password: security密码
management:
security:
enabled: true
再次访问 http://服务器ip:程序端口/actuator/env
发现需要登录了,这个方案在一般的项目都是可取的,也能起到权限拦截的作用,但是由于我们这个是网关项目,项目内的接口都是对外暴露的,项目内有自己的一套安全认证机制,包括权限认证,接口转发,统一响应等等,如果引入security,项目启动之后,外部访问项目接口都会最先走security的认证机制,就不会走网关项目认证机制,直接返404或其他错误,所以这种方案对于网关项目是不可取的。
由于security的安全方案不可取,干脆我自己弄一个权限认证机制,对于访问路径是以/actuator开头的,统统需要账号密码进行权限认证:
import cn.hutool.core.collection.CollectionUtil;
import com.sztech.gateway.plugin.api.result.GatewayResultWarp;
import com.sztech.gateway.plugin.api.util.WebFluxResultUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import sun.misc.BASE64Decoder;
import java.util.List;
/**
* 用于处理Actuator未授权访问漏洞的过滤器
*
* @author wenchao
*/
@Component
@Slf4j
public class ActuatorFilter implements WebFilter {
@Value("${com.sztech.prometheus.username}")
public String userName;
@Value("${com.sztech.prometheus.password}")
public String password;
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
log.info("into ActuatorFilter ...");
ServerHttpRequest request = exchange.getRequest();
String url = request.getPath().toString();
log.info("url: {}", url);
//只拦截actuator相关的接口 如果项目接口有前缀需自行添加进行判断
if (url.startsWith("/actuator")){
List<String> authList = request.getHeaders().get("Authorization");
if (CollectionUtil.isEmpty(authList)){
Object error = GatewayResultWarp.error("401", "未授权", null);
return WebFluxResultUtils.result(exchange, error);
}
String auth = authList.get(0);
if (StringUtils.isBlank(auth)){
Object error = GatewayResultWarp.error("401", "未授权", null);
return WebFluxResultUtils.result(exchange, error);
}
auth = getFromBASE64(auth.substring(6));
log.info("auth: {}", auth);
try {
String[] authArray = auth.split(":");
if (authArray[0].equals(userName) && authArray[1].equals(password)){
return chain.filter(exchange);
}else {
Object error = GatewayResultWarp.error("401", "认证失败", null);
return WebFluxResultUtils.result(exchange, error);
}
}catch (Exception e){
e.printStackTrace();
Object error = GatewayResultWarp.error("401", "认证失败", null);
return WebFluxResultUtils.result(exchange, error);
}
}
return chain.filter(exchange);
}
private String getFromBASE64(String s) {
if (s == null) {
return null;
}
BASE64Decoder decoder = new BASE64Decoder();
try {
byte[] b = decoder.decodeBuffer(s);
return new String(b);
} catch (Exception e) {
return null;
}
}
}
配置文件同步配置一下账号密码:
com:
sztech:
prometheus:
username: 账号
password: 密码
重启项目之后重新访问接口
由于我们项目是借助于prometheus对actuator监控信息进行收集和展示,所以prometheus的配置文件也需要修改一下,将账号密码配置一下,再重启prometheus即可。
basic_auth:
username: 账号
password: 密码