Wildcard CORS Origins
Setting Access-Control-Allow-Origin: * to allow any origin, exposing APIs to cross-origin attacks.
Information disclosure vulnerabilities occur when Cross-Origin Resource Sharing (CORS) is configured to allow any origin (*) or null origin to access resources, potentially exposing sensitive data to malicious websites.
@RestControllerpublic class ApiController { @GetMapping("/api/user-data") public ResponseEntity<UserData> getUserData(HttpServletRequest request, HttpServletResponse response) { // VULNERABLE: Wildcard allows any origin response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Credentials", "true"); UserData userData = userService.getCurrentUserData(); return ResponseEntity.ok(userData); } @PostMapping("/api/sensitive-action") public ResponseEntity<String> performAction(HttpServletResponse response) { // VULNERABLE: Allowing null origin String origin = request.getHeader("Origin"); if (origin == null || origin.equals("null")) { response.setHeader("Access-Control-Allow-Origin", "null"); } else { response.setHeader("Access-Control-Allow-Origin", origin); } response.setHeader("Access-Control-Allow-Credentials", "true"); // Perform sensitive action... return ResponseEntity.ok("Action completed"); }}@Componentpublic class CorsFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse httpResponse = (HttpServletResponse) response; // VULNERABLE: Global wildcard CORS httpResponse.setHeader("Access-Control-Allow-Origin", "*"); httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE"); httpResponse.setHeader("Access-Control-Allow-Headers", "*"); httpResponse.setHeader("Access-Control-Allow-Credentials", "true"); chain.doFilter(request, response); }}@Configuration@EnableWebSecuritypublic class SecurityConfig { @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); // VULNERABLE: Allowing all origins configuration.setAllowedOriginPatterns(Arrays.asList("*")); configuration.setAllowedMethods(Arrays.asList("*")); configuration.setAllowedHeaders(Arrays.asList("*")); configuration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; }}@RestControllerpublic class ApiController { private static final Set<String> ALLOWED_ORIGINS = Set.of( "https://myapp.com", "https://www.myapp.com", "https://admin.myapp.com" ); @GetMapping("/api/user-data") public ResponseEntity<UserData> getUserData(HttpServletRequest request, HttpServletResponse response) { String origin = request.getHeader("Origin"); // SECURE: Validate origin against allowlist if (origin != null && ALLOWED_ORIGINS.contains(origin)) { response.setHeader("Access-Control-Allow-Origin", origin); response.setHeader("Access-Control-Allow-Credentials", "true"); response.setHeader("Vary", "Origin"); } UserData userData = userService.getCurrentUserData(); return ResponseEntity.ok(userData); } @PostMapping("/api/sensitive-action") public ResponseEntity<String> performAction(HttpServletRequest request, HttpServletResponse response) { String origin = request.getHeader("Origin"); // SECURE: Strict origin validation if (origin == null || !ALLOWED_ORIGINS.contains(origin)) { return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Origin not allowed"); } response.setHeader("Access-Control-Allow-Origin", origin); response.setHeader("Access-Control-Allow-Credentials", "true"); response.setHeader("Vary", "Origin"); // Perform sensitive action... return ResponseEntity.ok("Action completed"); }}@Componentpublic class SecureCorsFilter implements Filter { private static final Set<String> ALLOWED_ORIGINS = Set.of( "https://myapp.com", "https://www.myapp.com", "https://api.myapp.com" ); private static final Set<String> ALLOWED_METHODS = Set.of( "GET", "POST", "PUT", "DELETE", "OPTIONS" ); private static final Set<String> ALLOWED_HEADERS = Set.of( "Content-Type", "Authorization", "X-Requested-With" ); @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; String origin = httpRequest.getHeader("Origin"); String method = httpRequest.getMethod(); // SECURE: Validate origin against allowlist if (origin != null && ALLOWED_ORIGINS.contains(origin)) { httpResponse.setHeader("Access-Control-Allow-Origin", origin); httpResponse.setHeader("Vary", "Origin"); // Handle preflight requests if ("OPTIONS".equals(method)) { String requestMethod = httpRequest.getHeader("Access-Control-Request-Method"); String requestHeaders = httpRequest.getHeader("Access-Control-Request-Headers"); if (requestMethod != null && ALLOWED_METHODS.contains(requestMethod)) { httpResponse.setHeader("Access-Control-Allow-Methods", requestMethod); } if (requestHeaders != null) { String[] headers = requestHeaders.split(","); List<String> allowedRequestHeaders = Arrays.stream(headers) .map(String::trim) .filter(ALLOWED_HEADERS::contains) .collect(Collectors.toList()); if (!allowedRequestHeaders.isEmpty()) { httpResponse.setHeader("Access-Control-Allow-Headers", String.join(", ", allowedRequestHeaders)); } } httpResponse.setHeader("Access-Control-Max-Age", "3600"); httpResponse.setStatus(HttpServletResponse.SC_OK); return; } httpResponse.setHeader("Access-Control-Allow-Credentials", "true"); } chain.doFilter(request, response); }}@Configuration@EnableWebSecuritypublic class SecurityConfig { @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); // SECURE: Specific allowed origins only configuration.setAllowedOrigins(Arrays.asList( "https://myapp.com", "https://www.myapp.com", "https://admin.myapp.com" )); configuration.setAllowedMethods(Arrays.asList( "GET", "POST", "PUT", "DELETE", "OPTIONS" )); configuration.setAllowedHeaders(Arrays.asList( "Content-Type", "Authorization", "X-Requested-With" )); configuration.setAllowCredentials(true); configuration.setMaxAge(3600L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins( "https://myapp.com", "https://www.myapp.com" ) .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("Content-Type", "Authorization") .allowCredentials(true) .maxAge(3600); } }; }}The vulnerable code was updated to address the security issue.
Setting Access-Control-Allow-Origin: * to allow any origin, exposing APIs to cross-origin attacks.
Sourcery automatically identifies information disclosure via permissive cors configuration and many other security issues in your codebase.