Skip to content

Commit c6929cf

Browse files
authored
Merge pull request #312 from PSMRI/AMM-1927
fix: amm-1927 http options vulnerability fixed through cors
2 parents 1d12209 + 106515c commit c6929cf

File tree

3 files changed

+92
-18
lines changed

3 files changed

+92
-18
lines changed

src/main/java/com/iemr/common/config/CorsConfig.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@ public class CorsConfig implements WebMvcConfigurer {
1111

1212
@Value("${cors.allowed-origins}")
1313
private String allowedOrigins;
14-
1514
@Override
1615
public void addCorsMappings(CorsRegistry registry) {
1716
registry.addMapping("/**")
1817
.allowedOriginPatterns(
1918
Arrays.stream(allowedOrigins.split(","))
2019
.map(String::trim)
2120
.toArray(String[]::new))
22-
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
23-
.allowedHeaders("*")
21+
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
22+
.allowedHeaders("Authorization", "Content-Type", "Accept", "Jwttoken",
23+
"serverAuthorization", "ServerAuthorization", "serverauthorization", "Serverauthorization")
2424
.exposedHeaders("Authorization", "Jwttoken")
2525
.allowCredentials(true)
2626
.maxAge(3600);

src/main/java/com/iemr/common/utils/JwtUserIdValidationFilter.java

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
import java.io.IOException;
44
import java.util.Arrays;
5+
import java.util.HashMap;
6+
import java.util.Map;
7+
import java.util.Set;
58

69
import org.slf4j.Logger;
710
import org.slf4j.LoggerFactory;
@@ -23,6 +26,7 @@ public class JwtUserIdValidationFilter implements Filter {
2326
private final Logger logger = LoggerFactory.getLogger(this.getClass().getName());
2427
private final String allowedOrigins;
2528

29+
2630
public JwtUserIdValidationFilter(JwtAuthenticationUtil jwtAuthenticationUtil,
2731
String allowedOrigins) {
2832
this.jwtAuthenticationUtil = jwtAuthenticationUtil;
@@ -36,27 +40,63 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo
3640
HttpServletResponse response = (HttpServletResponse) servletResponse;
3741

3842
String origin = request.getHeader("Origin");
43+
String method = request.getMethod();
44+
String uri = request.getRequestURI();
3945

4046
logger.debug("Incoming Origin: {}", origin);
47+
logger.debug("Request Method: {}", method);
48+
logger.debug("Request URI: {}", uri);
4149
logger.debug("Allowed Origins Configured: {}", allowedOrigins);
42-
logger.info("Add server authorization header to response");
50+
51+
// STEP 1: STRICT Origin Validation - Block unauthorized origins immediately
52+
// For OPTIONS requests, Origin header is required (CORS preflight)
53+
if ("OPTIONS".equalsIgnoreCase(method)) {
54+
if (origin == null) {
55+
logger.warn("BLOCKED - OPTIONS request without Origin header | Method: {} | URI: {}", method, uri);
56+
response.sendError(HttpServletResponse.SC_FORBIDDEN, "OPTIONS request requires Origin header");
57+
return;
58+
}
59+
if (!isOriginAllowed(origin)) {
60+
logger.warn("BLOCKED - Unauthorized Origin | Origin: {} | Method: {} | URI: {}", origin, method, uri);
61+
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Origin not allowed");
62+
return;
63+
}
64+
} else {
65+
// For non-OPTIONS requests, validate origin if present
66+
if (origin != null && !isOriginAllowed(origin)) {
67+
logger.warn("BLOCKED - Unauthorized Origin | Origin: {} | Method: {} | URI: {}", origin, method, uri);
68+
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Origin not allowed");
69+
return;
70+
}
71+
}
72+
73+
// Determine request path/context for later checks
74+
String path = request.getRequestURI();
75+
String contextPath = request.getContextPath();
76+
77+
// STEP 3: Add CORS Headers (only for validated origins)
4378
if (origin != null && isOriginAllowed(origin)) {
44-
response.setHeader("Access-Control-Allow-Origin", origin);
45-
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
46-
response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept, Jwttoken, serverAuthorization, ServerAuthorization, serverauthorization, Serverauthorization");
79+
response.setHeader("Access-Control-Allow-Origin", origin); // Never use wildcard
80+
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
81+
response.setHeader("Access-Control-Allow-Headers",
82+
"Authorization, Content-Type, Accept, Jwttoken, serverAuthorization, ServerAuthorization, serverauthorization, Serverauthorization");
4783
response.setHeader("Access-Control-Allow-Credentials", "true");
48-
} else {
49-
logger.warn("Origin [{}] is NOT allowed. CORS headers NOT added.", origin);
84+
response.setHeader("Access-Control-Max-Age", "3600");
85+
logger.info("Origin Validated | Origin: {} | Method: {} | URI: {}", origin, method, uri);
5086
}
5187

52-
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
53-
logger.info("OPTIONS request - skipping JWT validation");
88+
// STEP 4: Handle OPTIONS Preflight Request
89+
if ("OPTIONS".equalsIgnoreCase(method)) {
90+
// OPTIONS (preflight) - respond with full allowed methods
91+
response.setHeader("Access-Control-Allow-Origin", origin);
92+
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
93+
response.setHeader("Access-Control-Allow-Headers",
94+
"Authorization, Content-Type, Accept, Jwttoken, serverAuthorization, ServerAuthorization, serverauthorization, Serverauthorization");
95+
response.setHeader("Access-Control-Allow-Credentials", "true");
5496
response.setStatus(HttpServletResponse.SC_OK);
5597
return;
5698
}
5799

58-
String path = request.getRequestURI();
59-
String contextPath = request.getContextPath();
60100
logger.info("JwtUserIdValidationFilter invoked for path: " + path);
61101

62102
// Log cookies for debugging
@@ -73,8 +113,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo
73113
}
74114

75115
// Log headers for debugging
76-
String jwtTokenFromHeader = request.getHeader("Jwttoken");
77-
logger.info("JWT token from header: ");
116+
logger.debug("JWT token from header: {}", request.getHeader("Jwttoken") != null ? "present" : "not present");
78117

79118
// Skip authentication for public endpoints
80119
if (shouldSkipAuthentication(path, contextPath)) {
@@ -138,14 +177,15 @@ private boolean isOriginAllowed(String origin) {
138177
.anyMatch(pattern -> {
139178
String regex = pattern
140179
.replace(".", "\\.")
141-
.replace("*", ".*")
142-
.replace("http://localhost:.*", "http://localhost:\\d+"); // special case for wildcard port
180+
.replace("*", ".*");
143181

144182
boolean matched = origin.matches(regex);
145183
return matched;
146184
});
147185
}
148186

187+
188+
149189
private boolean isMobileClient(String userAgent) {
150190
if (userAgent == null)
151191
return false;

src/main/java/com/iemr/common/utils/http/HTTPRequestInterceptor.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@
2222
package com.iemr.common.utils.http;
2323

2424
import java.nio.charset.StandardCharsets;
25+
import java.util.Arrays;
2526
import javax.ws.rs.core.MediaType;
2627

2728
import org.slf4j.Logger;
2829
import org.slf4j.LoggerFactory;
2930
import org.springframework.beans.factory.annotation.Autowired;
31+
import org.springframework.beans.factory.annotation.Value;
3032
import org.springframework.context.annotation.Configuration;
3133
import org.springframework.stereotype.Component;
3234
import org.springframework.web.servlet.HandlerInterceptor;
@@ -45,6 +47,9 @@ public class HTTPRequestInterceptor implements HandlerInterceptor {
4547

4648
Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName());
4749

50+
@Value("${cors.allowed-origins}")
51+
private String allowedOrigins;
52+
4853
@Autowired
4954
public void setValidator(Validator validator) {
5055
this.validator = validator;
@@ -140,7 +145,14 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons
140145

141146
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401
142147
response.setContentType(MediaType.APPLICATION_JSON);
143-
response.setHeader("Access-Control-Allow-Origin", "*");
148+
149+
String origin = request.getHeader("Origin");
150+
if (origin != null && isOriginAllowed(origin)) {
151+
response.setHeader("Access-Control-Allow-Origin", origin);
152+
response.setHeader("Access-Control-Allow-Credentials", "true");
153+
} else if (origin != null) {
154+
logger.warn("CORS headers NOT added for error response | Unauthorized origin: {}", origin);
155+
}
144156

145157
// Better to use getBytes().length for accurate byte size
146158
byte[] responseBytes = jsonErrorResponse.getBytes(StandardCharsets.UTF_8);
@@ -182,4 +194,26 @@ public void afterCompletion(HttpServletRequest request, HttpServletResponse resp
182194
throws Exception {
183195
logger.debug("In afterCompletion Request Completed");
184196
}
197+
198+
/**
199+
* Check if the given origin is allowed based on configured allowedOrigins.
200+
* Uses the same logic as JwtUserIdValidationFilter for consistency.
201+
*
202+
* @param origin The origin to validate
203+
* @return true if origin is allowed, false otherwise
204+
*/
205+
private boolean isOriginAllowed(String origin) {
206+
if (origin == null || allowedOrigins == null || allowedOrigins.trim().isEmpty()) {
207+
return false;
208+
}
209+
210+
return Arrays.stream(allowedOrigins.split(","))
211+
.map(String::trim)
212+
.anyMatch(pattern -> {
213+
String regex = pattern
214+
.replace(".", "\\.")
215+
.replace("*", ".*");
216+
return origin.matches(regex);
217+
});
218+
}
185219
}

0 commit comments

Comments
 (0)