Description
Spring MVC resolves a ModelAndView view name through the configured ViewResolver; it does not always map to a JSP file. If unvalidated input controls the name, the configuration may allow unintended internal views, redirects or forwarding destinations to be selected.
Potential impact
- Information disclosure: A view without the required authorization may expose sensitive data.
- Unintended processing: Available views and resolver settings may permit internal operations or external redirects. Control over a view name alone does not establish arbitrary Java code execution.
Remediation
- Do not pass user input directly as the
ModelAndViewview name. - Prefer fixed, application-controlled view names.
- Map permitted choices to fixed views using an allow-list.
- If a redirect is needed, use only server-defined destinations and verify destination access.
sendRedirect()does not replace input validation.
Examples
View-resolver and authentication configuration is omitted. home and profile are example names defined by the application; profile data still requires per-user authorization.
Before
java
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
@Controller
public class UnsafeController {
@GetMapping("/view")
public ModelAndView unsafeView(HttpServletRequest request) {
String viewName = request.getParameter("page"); // User-controlled input
return new ModelAndView(viewName); // Unvalidated destination
}
}
After
java
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Controller
public class SafeController {
private static final Map<String, String> allowedViews = new HashMap<>();
static {
allowedViews.put("home", "home");
allowedViews.put("profile", "profile");
}
@GetMapping("/view")
public ModelAndView safeView(@RequestParam(name = "page", required = false, defaultValue = "home") String page) {
// Select from the fixed view mapping.
String safeView = allowedViews.getOrDefault(page, "home");
return new ModelAndView(safeView);
}
@GetMapping("/redirect")
public void safeRedirect(@RequestParam(name = "page", required = false, defaultValue = "home") String page,
HttpServletResponse response) throws IOException {
String redirectPath = allowedViews.getOrDefault(page, "/home");
response.sendRedirect(redirectPath); // Fixed redirect destination
}
}
Explanation:
- Before: Uses the
pageparameter directly as theModelAndViewview name. - After: Chooses a view name from a fixed mapping and falls back to
home. The separate redirect method also uses fixed destinations; verify how its relative URLs resolve under the deployed application path.