等保渗透测试六大高危漏洞及实战修复指南

52 阅读3分钟

漏洞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;
}

操作步骤

  1. 定位API文档路径
    确认Nginx配置中API文档的URL路径(如/api-docs)。
  2. 编辑Nginx配置文件
    在对应的server块中添加上述限制规则。
  3. 测试配置并重载
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("请求过于频繁,请稍后再试");
        }
    }