File disclosure through Spring ModelAndView

File disclosure through Spring ModelAndView

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 ModelAndView view 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 page parameter directly as the ModelAndView view 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.

Related CVEs

References