挺久没有沉下心来好好的去研究分析一个自认为有意思的东西
请求路径
/webshell.jsp/123.txt
调用栈
buildInvocation:4175, WebApp (com.caucho.server.webapp) buildInvocation:798, WebAppContainer (com.caucho.server.webapp) buildInvocation:753, Host (com.caucho.server.host) buildInvocation:319, HostContainer (com.caucho.server.host) buildInvocation:1068, ServletService (com.caucho.server.cluster) buildInvocation:250, InvocationServer (com.caucho.server.dispatch) buildInvocation:223, InvocationServer (com.caucho.server.dispatch) buildInvocation:1610, AbstractHttpRequest (com.caucho.server.http) getInvocation:1583, AbstractHttpRequest (com.caucho.server.http) handleRequest:825, HttpRequest (com.caucho.server.http) dispatchRequest:1393, TcpSocketLink (com.caucho.network.listen) handleRequest:1349, TcpSocketLink (com.caucho.network.listen) handleRequestsImpl:1333, TcpSocketLink (com.caucho.network.listen) handleRequests:1241, TcpSocketLink (com.caucho.network.listen)
调试解析流程来到
com.caucho.server.webapp.WebApp#buildInvocation(com.caucho.server.dispatch.Invocation, boolean)
//... if (isCache) { entry = (FilterChainEntry)this._filterChainCache.get(((Invocation)invocation).getContextURI()); } FilterChain chain; if (entry != null && !entry.isModified()) { //... } } else { chain = this._servletMapper.mapServlet((ServletInvocation)invocation); this._filterMapper.buildDispatchChain((Invocation)invocation, chain); chain = ((Invocation)invocation).getFilterChain(); chain = this.applyWelcomeFile(DispatcherType.REQUEST, (Invocation)invocation, chain); if (this._requestRewriteDispatch != null) { FilterChain newChain = this._requestRewriteDispatch.map(DispatcherType.REQUEST, ((Invocation)invocation).getContextURI(), ((Invocation)invocation).getQueryString(), chain); chain = newChain; } entry = new FilterChainEntry(chain, (Invocation)invocation); chain = entry.getFilterChain(); if (isCache) { this._filterChainCache.put(((Invocation)invocation).getContextURI(), entry); } } chain = this.buildSecurity(chain, (Invocation)invocation); chain = this.createWebAppFilterChain(chain, (Invocation)invocation, isTop); ((Invocation)invocation).setFilterChain(chain); ((Invocation)invocation).setPathInfo(entry.getPathInfo()); ((Invocation)invocation).setServletPath(entry.getServletPath()); if (this._oldWebApp != null && CurrentTime.getCurrentTime() < this._oldWebAppExpireTime) { Invocation oldInvocation = new Invocation(); oldInvocation.copyFrom((Invocation)invocation); oldInvocation.setWebApp(this._oldWebApp); this._oldWebApp.buildInvocation(oldInvocation); invocation = new VersionInvocation((Invocation)invocation, this, oldInvocation, oldInvocation.getWebApp(), this._oldWebAppExpireTime); } var26 = invocation; return (Invocation)var26; }
从_filterChainCache
通过请求路径获取缓存的FilterChainEntry
实体类,缓存中获取不到的话会调用this._servletMapper.mapServlet
来进行获取
entry._regexp.matcher(uri);
通过URI去匹配规则,因为^.*\.jsp(?=/)|^.*\.jsp\z
该正则的缘故,所以这里能将xxx.jsp/123.xxx给匹配为xxx.jsp
请求走了ServletMapping[url-pattern=*.jsp, name=resin-jsp]
的处理机制
com.caucho.server.dispatch.ServletMapper#mapServlet
下面代码这里会创建chain
创建完成后,到com.caucho.server.http.HttpRequest#handleRequest
调用invocation.service
执行filterChain走到com.caucho.server.dispatch.PageFilterChain#doFilter
进行jsp page的处理。
在/conf/app-default.xml
文件中有这么一项,php后缀文件使用com.caucho.quercus.servlet.QuercusServlet
来解析
使用默认的resin配置是会将这条规则进行加载的
QuercusServlet
会调用Quercus
来解析PHP
resin官方说明文档:http://quercus.caucho.com/
调用栈
splitQueryAndUnescape:254, InvocationDecoder (com.caucho.server.dispatch) buildInvocation:1594, AbstractHttpRequest (com.caucho.server.http) getInvocation:1583, AbstractHttpRequest (com.caucho.server.http) handleRequest:825, HttpRequest (com.caucho.server.http)
com.caucho.server.dispatch.InvocationDecoder#splitQueryAndUnescape
调用normalizeUriEscape
进行解码
com.caucho.server.dispatch.InvocationDecoder#splitQueryAndUnescape
代码
public void splitQueryAndUnescape(Invocation invocation, byte[] rawURI, int uriLength) throws IOException { String decodedURI; for(int i = 0; i < uriLength; ++i) { if (rawURI[i] == 63) { ++i; decodedURI = this.byteToChar(rawURI, i, uriLength - i, "ISO-8859-1"); invocation.setQueryString(decodedURI); uriLength = i - 1; break; } } String rawURIString = this.byteToChar(rawURI, 0, uriLength, "ISO-8859-1"); invocation.setRawURI(rawURIString); decodedURI = normalizeUriEscape(rawURI, 0, uriLength, this._encoding); //... String uri = this.normalizeUri(decodedURI); invocation.setURI(uri); invocation.setContextURI(uri);
com.caucho.server.dispatch.InvocationDecoder#normalizeUriEscape
代码
private static String normalizeUriEscape(byte[] rawUri, int i, int len, String encoding) throws IOException { //... try { while(i < len) { int ch = rawUri[i++] & 255; if (ch == 37) { i = scanUriEscape(converter, rawUri, i, len); } else { converter.addByte(ch); } } String result = converter.getConvertedString(); freeConverter(converter); return result; }
循环遍历路径每个字符匹配%
字符,匹配到则调用scanUriEscape
private static int scanUriEscape(ByteToChar converter, byte[] rawUri, int i, int len) throws IOException { int ch1 = i < len ? rawUri[i++] & 255 : -1; int ch2; int ch3; if (ch1 == 117) { ch1 = i < len ? rawUri[i++] & 255 : -1; ch2 = i < len ? rawUri[i++] & 255 : -1; ch3 = i < len ? rawUri[i++] & 255 : -1; int ch4 = i < len ? rawUri[i++] & 255 : -1; converter.addChar((char)((toHex(ch1) << 12) + (toHex(ch2) << 8) + (toHex(ch3) << 4) + toHex(ch4))); } else { ch2 = i < len ? rawUri[i++] & 255 : -1; ch3 = (toHex(ch1) << 4) + toHex(ch2); converter.addByte(ch3); } return i; }
scanUriEscape
该方法遍历每个字符,取四个字符进行Escape
解码,如%u0077
后后四位数。
请求路径:
http://localhost:8088/untitled/%u0077%u0065%u0062%u0073%u0068%u0065%u006c%u006c%u002e%u006a%u0073%u0070%u002f%u0031%u0032%u0033%u002e%u0074%u0078%u0074;
解码后为:/untitled/webshell.jsp/123.txt
注意到在com.caucho.server.dispatch.InvocationDecoder#splitQueryAndUnescape
中调用完normalizeUriEscape
对URI进行解码后,还会调用com.caucho.server.dispatch.InvocationDecoder#normalizeUri(java.lang.String)
对URI进行规范化处理。
public String normalizeUri(String uri, boolean isWindows) throws IOException { CharBuffer cb = new CharBuffer(); int len = uri.length(); if (this._maxURILength < len) { throw new BadRequestException(L.l("The request contains an illegal URL because it is too long.")); } else { char ch; if (len == 0 || (ch = uri.charAt(0)) != '/' && ch != '\\') { cb.append('/'); } for(int i = 0; i < len; ++i) { ch = uri.charAt(i); if (ch != '/' && ch != '\\') { if (ch == 0) { throw new BadRequestException(L.l("The request contains an illegal URL.")); } cb.append(ch); } else { while(i + 1 < len) { ch = uri.charAt(i + 1); if (ch != '/' && ch != '\\') { if (ch == ';') { throw new BadRequestException(L.l("The request contains an illegal URL.")); } if (ch != '.') { break; } if (len > i + 2 && (ch = uri.charAt(i + 2)) != '/' && ch != '\\') { if (ch == ';') { throw new BadRequestException(L.l("The request contains an illegal URL.")); } if (ch != '.') { break; } if (len > i + 3 && (ch = uri.charAt(i + 3)) != '/' && ch != '\\') { throw new BadRequestException(L.l("The request contains an illegal URL.")); } int j; for(j = cb.length() - 1; j >= 0 && (ch = cb.charAt(j)) != '/' && ch != '\\'; --j) { } if (j > 0) { cb.setLength(j); } else { cb.setLength(0); } i += 3; } else { i += 2; } } else { ++i; } } while(isWindows && cb.getLength() > 0 && ((ch = cb.getLastChar()) == '.' || ch == ' ')) { cb.setLength(cb.getLength() - 1); if (cb.getLength() > 0 && (ch = cb.getLastChar()) == '/' || ch == '\\') { cb.setLength(cb.getLength() - 1); } } cb.append('/'); } } while(isWindows && cb.getLength() > 0 && ((ch = cb.getLastChar()) == '.' || ch == ' ')) { cb.setLength(cb.getLength() - 1); } return cb.toString(); } }
遍历字符,如果不为/
则持续向cb中添加字符,如果为/
则遍历/
后面的字符为;
则报错。为.
则退出循环,但是这个.
并不会添加到cb中。当出现/.
字符的时候会检测后面第三位数是否是.
或者第四位是不是.
如果是的话则跳出循环,该第三或第四个字符后面的字符不为/
的话则抛异常。也就是说说当出现了/.
后,检测一下一个字符,如果还是.
会出循环再检测下面的字符,下面的字符必须得是/
,只能以/../
的方式请求
this._maxURILength
设置URI最多不能超过1024个字符/
后面不能拼接;
,/;
这种方式。/.
是可以的,但是不能/.;
这种方式是会报错的..;/
这种方式不能适用