【学习资料】

事理剖析

这个漏洞和IIS解析漏洞比较像,可以通过创建一个xxx.jsp的文件夹,并在个中放置一个txt文件,文件的内容将会被当作JSP解析。

我认为要剖析这个漏洞事理,首先得先理解访问jsp文件时Resin是如何处理我们要求的。

jsp打断点平安破绽Resin解析破绽剖析 JavaScript

首先看下.jsp是被哪个Servlet处理的,从配置app- default.xml中可以看出,我们的要求会被com.caucho.jsp.JspServlet处理。

<servlet servlet-name="resin-jsp" servlet-class="com.caucho.jsp.JspServlet"> <init> <load-tld-on-init>false</load-tld-on-init> <page-cache-max>1024</page-cache-max> </init> <load-on-startup/> </servlet><servlet-mapping url-pattern=".jsp" servlet-name="resin-jsp" default="true"/>

本来以为在JspServlet下断点可以看到要求调用栈,但是在实际操作的过程中创造并没有实行到JspServlet中的方法就返回了,确实比较奇怪。

在Resin中发起HTTP要求一定会经由HttpRequest#handleRequest方法处理,可以在这个方法中打断点排查问题,经由排查创造在PageFilterChain#doFilter中就完成了JSP的"编译"和实行事情,这点比较奇怪,由于之前剖析Tomcat中"编译JSP"的操作是在servlet中完成的。
以是实在针对Resin对JSP文件处理的剖析重点就在PageFilterChain#doFilter中。

JSP编译后会被封装到Page工具中,而Page工具的引用被保存于pageRef属性中,因此首先检测pageRef是否为空,如果是则直接通过page.pageservice(req, res);实行要求,不经由后面编译的逻辑。
如果缓存中没有page工具,则通过compilePage编译JSP并封装为Page工具返回,new SoftReference创建引用工具,再通过pageservice实行要求。

public void doFilter(ServletRequest request, ServletResponse response) throws ServletException, IOException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; FileNotFoundException notFound = null; SoftReference<Page> pageRef = _pageRef; Page page; //首先从换从中获取Page工具的引用,如果有就不再编译。
if (pageRef != null) page = pageRef.get(); else page = null; //如果缓存为空或者page工具被修正过则编译 if (page == null || page._caucho_isModified()) { try { _pageRef = null; page = compilePage(page, req, res); //得到page的引用并保存 if (page != null) { _pageRef = new SoftReference<Page>(page); _isSingleThread = page instanceof SingleThreadModel; } } catch (FileNotFoundException e) { page = null; notFound = e; } } if (page == null) { // jsp/01cg if (notFound == null) return; String errorUri = (String) req.getAttribute(RequestDispatcher.ERROR_REQUEST_URI); String uri = (String) req.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI); String forward = (String) req.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI); // jsp/01ch if (uri != null) { //throw new FileNotFoundException(uri); throw notFound; } else if (forward != null) { //throw new FileNotFoundException(req.getRequestURI()); throw notFound; } else if (errorUri != null) { //throw new FileNotFoundException(errorUri); throw notFound; } else { log.log(Level.FINER, notFound.toString(), notFound); } ((HttpServletResponse) res).sendError(HttpServletResponse.SC_NOT_FOUND); } else if (req instanceof HttpServletRequest) { try { if (_isSingleThread) { synchronized (page) { //实行要求 page.pageservice(req, res); } } else page.pageservice(req, res); } catch (ServletException e) { ... }

Page#pageService-->JavaPage#service-->_aaa#_jspService,末了通过JSP天生类的_jspService方法完成要求。

如何进入PageFilterChain?

通过上面的剖析我们可以知道,在PageFilterChain中完成了对JSP的编译和实行,以是我们剖析的重点就在于如何才能进入PageFilterChain中?

追踪创建PageFilterChain的过程,在WebApp#buildinvocation中,完成了PageFilterChain的创建,我摘了部分代码剖析。

首先从缓存中获取FilterChains,如果有的话则直接获取chains,缓存中保存的Chains和URL有关。
如果缓存没有,则通过_servletMapper.mapServlet(invocation);获取Chains。

public Invocation buildInvocation(Invocation invocation, boolean isTop) { ... else { FilterChainEntry entry = null; // jsp/1910 - can't cache jsp_precompile String query = invocation.getQueryString(); boolean isCache = true; if (query != null && query.indexOf("jsp_precompile") >= 0) isCache = false; else if (_requestRewriteDispatch != null) isCache = false; if (isCache) entry = _filterChainCache.get(invocation.getContextURI()); if (entry != null && ! entry.isModified()) { chain = entry.getFilterChain(); invocation.setServletName(entry.getServletName()); if (! entry.isAsyncSupported()) invocation.clearAsyncSupported(); invocation.setMultipartConfig(entry.getMultipartConfig()); } else { chain = _servletMapper.mapServlet(invocation); ... }

在mapServlet中,紧张做了下面的操作

从ServletInvocation中获取URL并去除;xxx的内容

String contextURI = invocation.getContextURI(); try { cleanUri = Invocation.stripPathParameters(contextURI); } catch (Exception e) { log.warning(L.l("Invalid URI {0}", contextURI)); return new ErrorFilterChain(404); }根据URL匹配获取ServletMapping

ServletMapping servletMap = _servletMap.map(cleanUri, vars);如果根据URL没有匹配到Servlet处理则根据URL获取资源内容,并设置利用_defaultServlet处理。

servletName = servletMap.getServletName();if (servletName == null) { try { InputStream is; is = _webApp.getResourceAsStream(contextURI); if (is != null) { is.close(); servletName = _defaultServlet; } } catch (Exception e) { }如果URL以j_security_check结尾则利用j_security_check作为Servlet

if (matchResult == null && contextURI.endsWith("j_security_check")) { servletName = "j_security_check"; }如果匹配成功则设置servletPath和servletName等属性到invocation工具中,根据Servletname从_servletManager获取ServletConfigImpl工具,创建FilterChains

ArrayList<String> vars = new ArrayList<String>();vars.add(contextURI);String servletPath = vars.get(0);invocation.setServletPath(servletPath);invocation.setServletName(servletName);ServletConfigImpl newConfig = _servletManager.getServlet(servletName);FilterChain chain= _servletManager.createServletChain(servletName, config, invocation);

以是这个漏洞的重点在于为什么/test.jsp/xxx.txt可以被_servletMap.map(cleanUri, vars);匹配到。

进入到UrlMap#map中,创造默认情形下.jsp会交给^.\.jsp(?=/)|^.\.jsp\z正则处理。

紧张出问题的是^.\.jsp(?=/)部分,这个正则的逻辑是匹配xxxx.jsp/xxxx以是我们传入的路径会被匹配到,这也是这个漏洞的实质缘故原由。

总结

实在我认为Resin这么写可能对作者来说这本身是个正常功能,由于之前Resin也实现了Invoker的功能,可以直接根据路径加载任意类。

末了

关注我,持续更新······

私我获取【网络安全学习子·攻略】