最后更新:2026-04-17
跨域资源共享(Cross-Origin Resource Sharing,CORS)配置错误会导致恶意网站可以访问目标 API,造成敏感数据泄露。常见错误包括:信任任意来源、动态反射 Origin、忽略 null Origin 等。
| 维度 | 评级 |
|---|---|
| OWASP Top 10 | A05:2025 - Security Misconfiguration |
| CWE | CWE-942 / CWE-346 |
| 严重程度 | 中危 |
| 类型 | 说明 |
|---|---|
| 通配符 + 凭证 | Access-Control-Allow-Origin: * 同时允许携带 Cookie |
| 动态反射 Origin | 直接将请求的 Origin 回显,无白名单校验 |
null Origin 信任 |
信任 null Origin,沙箱 iframe 可利用 |
| 子域名信任过宽 | 允许 *.example.com,攻击者控制子域即可利用 |
// [VULNERABLE] 允许所有来源且允许携带凭证
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("*") // 危险:允许任意来源
.allowCredentials(true) // 危险:允许凭证(与 * 组合无效,但配置意图危险)
.allowedMethods("*");
}
}
// [VULNERABLE] 动态反射 Origin,无白名单
@Component
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 危险:直接将请求的 Origin 回写,任何来源都被信任
String origin = request.getHeader("Origin");
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Credentials", "true");
chain.doFilter(req, res);
}
}
// [SECURE] 严格白名单校验
@Configuration
public class SecureWebConfig implements WebMvcConfigurer {
private static final List<String> ALLOWED_ORIGINS = List.of(
"https://app.example.com",
"https://www.example.com"
);
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins(ALLOWED_ORIGINS.toArray(new String[0]))
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("Authorization", "Content-Type", "X-XSRF-TOKEN")
.allowCredentials(true) // 凭证仅对白名单域名有效
.maxAge(3600);
}
}
// [VULNERABLE] Spring Security 中错误配置 CORS
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors(cors -> cors.configurationSource(request -> {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*"); // 危险:通配符
config.addAllowedMethod("*");
config.setAllowCredentials(true); // 危险:* 与凭证组合会被浏览器拒绝,但体现不安全意图
return config;
}));
return http.build();
}
// [SECURE] Spring Security 集成严格 CORS
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of(
"https://app.example.com",
"https://www.example.com"
));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
config.setAllowCredentials(true);
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", config);
return source;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors(cors -> cors.configurationSource(corsConfigurationSource()));
return http.build();
}
// [SECURE] 需要动态校验时,使用精确匹配
@Component
public class StrictCorsFilter extends OncePerRequestFilter {
private static final Set<String> ALLOWED_ORIGINS = Set.of(
"https://app.example.com",
"https://www.example.com"
);
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String origin = request.getHeader("Origin");
if (origin != null && ALLOWED_ORIGINS.contains(origin)) {
// 安全:仅对白名单 Origin 设置响应头
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Vary", "Origin"); // 必须设置 Vary,防止缓存污染
}
if ("OPTIONS".equals(request.getMethod())) {
response.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE");
response.setHeader("Access-Control-Allow-Headers", "Authorization,Content-Type");
response.setHeader("Access-Control-Max-Age", "3600");
response.setStatus(HttpServletResponse.SC_OK);
return;
}
chain.doFilter(request, response);
}
}
Origin: https://evil.com,检查响应头Origin: null,观察是否被信任allowedOrigins("*")、addAllowedOrigin("*")*,列出具体允许的域名Vary: Origin:防止 CORS 响应被缓存并用于其他 Originnull Origin:沙箱 iframe 和本地文件会发送 nullallowCredentials(true) 必须配合明确的 Origin,不能与 * 同用*