漏洞1:OAuth2客户端模式绕过
▍漏洞原理
攻击者通过篡改grant_type=client_credentials
绕过用户认证流程,直接获取基础token。虽然符合OAuth2规范(RFC 6749),但在需要用户上下文的业务场景中存在安全隐患。
▍修复方案
@Bean
public TokenEnhancer tokenEnhancer() {
return (accessToken, authentication) -> {
// 拦截客户端模式请求
if (CLIENT_CREDENTIALS.equals(authentication.getOAuth2Request().getGrantType())) {
throw new InvalidGrantException("客户端模式已禁用");
}
// 注入用户身份特征
CustomUser user = (CustomUser) authentication.getPrincipal();
accessToken.getAdditionalInformation().put("user_id", user.getId());
return accessToken;
};
}
现在的操作一般都需要用户信息才能操作,直接抛出错误即可,或者根据字段过滤。
漏洞2:用户枚举攻击登录绕过
▍攻击特征
请求类型 | 系统响应 |
---|---|
存在用户手机号 | "验证码已发送" |
不存在手机号 | "用户不存在" |
▍防御策略
// 统一响应模板
public ResponseEntity<?> handleLoginRequest(LoginRequest request) {
boolean isValid = userService.validateCredentials(request);
return ResponseEntity.ok()
.body(new StandardResponse(isValid ? 200 : 200, "处理完成")); // 始终返回相同状态码
}
// 验证码发送逻辑
public void sendSmsCode(String phone) {
if (!phone.matches(REGEX_PHONE)) {
throw new ValidationException("手机号格式错误");
}
// 不提示用户是否存在
smsService.send(phone, generateCode());
}
统一返回信息即可,用户不存在时也不抛出错误。
漏洞3:API文档未授权访问
▍Nginx防护矩阵
# 三级防护体系
location /api-docs {
# 第一层:IP白名单
allow 192.168.1.0/24;
deny all;
# 第二层:双因素认证
auth_basic "API Documentation";
auth_basic_user_file /etc/nginx/api_docs.htpasswd;
# 第三层:访问时段限制
satisfy all;
# 第四层:HTTPS强制
if ($scheme != "https") {
return 301 https://$host$request_uri;
}
# 第五层:访问日志审计
access_log /var/log/nginx/api_docs.log;
}
▍操作步骤
- 定位API文档路径
确认Nginx配置中API文档的URL路径(如/api-docs
)。 - 编辑Nginx配置文件
在对应的server
块中添加上述限制规则。 - 测试配置并重载
sudo nginx -t # 检查语法
sudo nginx -s reload # 重载配置
漏洞4:Actuator端点泄露
▍漏洞原理
检查该应用发现其使用的SpringBoot Actuator存在未授权访问,通过访问trace、env发现大量敏感信息,攻击者可盗取这些信息从而进一步攻击。
▍Spring Boot安全配置
在application.yml中指定actuator的端口以及开启security功能,配置访问权限验证,这时再访问actuator功能时就会弹出登录窗口,需要输入账号密码验证后才允许访问。
management:
endpoint:
env:
enabled: false
server:
port: -1
漏洞5:明文传输风险
▍漏洞原理
系统采用了明文传输,数据以未加密的形式在客户端和服务器之间进行交换
▍HTTPS强制配置模板
server{
# 80端口是http正常访问的接口
listen 80;
server_name XXX.com;
#(第一种)把http的域名请求转成https
return 301 https://$host$request_uri;
#(第二种)强制将http的URL重写成https
rewrite ^(.*) https://$server_name$1 permanent;
}
server {
listen 443 ssl http2;
server_name example.com;
# 证书路径(需替换为实际路径)
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
# 协议配置优化
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on; # ✨ 应设为on以增强TLSv1.2安全性
# 现代密码套件配置(新增关键配置)
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
# 会话缓存修正
ssl_session_cache shared:SSL:10m; # ✨ 修正缓存标识符
ssl_session_timeout 1d;
ssl_session_tickets off;
# 安全增强配置(新增)
ssl_stapling on; # ✨ OCSP装订
ssl_stapling_verify on;
resolver 8.8.8.8 valid=300s;
# 安全响应头
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
# 建议根据实际情况配置CSP
add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' *.example.com; img-src https: data:";
}
配置完重启nginx
sudo nginx -t # 检查语法
sudo nginx -s reload # 重载配置
漏洞6:短信轰炸攻击
▍漏洞原理
应用存在短信验证码控制缺失问题,通过不间断的请求短信发送接口,即可发送大量短信验证码。如果被攻击者利用,可对任意用户造成短信轰炸。
▍修复方案
加入短信发送数量限制,基于时间窗口的验证码次数限制
public void checkSmsCodeLimit(String phone) {
long now = System.currentTimeMillis() / 1000;
String key = UserCenterConstant.REDIS_PHONE_CODE_KEY_LIMIT_TIME + phone;
long window = customProperties.getLimitTime();
int limit = customProperties.getLimitSum();
// 使用Lua脚本保证原子操作
String luaScript =
"local key = KEYS[1]\n" +
"local now = tonumber(ARGV[1])\n" +
"local window = tonumber(ARGV[2])\n" +
"local limit = tonumber(ARGV[3])\n" +
"\n" +
"redis.call('zremrangebyscore', key, 0, now - window)\n" +
"local count = redis.call('zcard', key)\n" +
"if count >= limit then\n" +
" return 0\n" +
"end\n" +
"redis.call('zadd', key, now, now)\n" +
"redis.call('expire', key, window)\n" +
"return 1";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(luaScript);
redisScript.setResultType(Long.class);
Long result = (Long) redisTemplate.execute(redisScript, Collections.singletonList(key), now, window, limit);
if (result != null && result == 0) {
throw new ValidateException("请求过于频繁,请稍后再试");
}
}