生活随笔
收集整理的這篇文章主要介紹了
【安全漏洞】Resin解析漏洞分析
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
前言
前陣子看有師傅在公眾號上發(fā)表了Resin解析漏洞分析,我們也知道有個常用的OA用的就是Resin,因此我認(rèn)為了解它的漏洞是十分必要的。
【學(xué)習(xí)資料】
原理分析
這個漏洞和IIS解析漏洞比較像,可以通過創(chuàng)建一個xxx.jsp的文件夾,并在其中放置一個txt文件,文件的內(nèi)容將會被當(dāng)作JSP解析。
我認(rèn)為要分析這個漏洞原理,首先得先了解訪問jsp文件時Resin是如何處理我們請求的。
首先看下*.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下斷點可以看到請求調(diào)用棧,但是在實際操作的過程中發(fā)現(xiàn)并沒有執(zhí)行到JspServlet中的方法就返回了,確實比較奇怪。
在Resin中發(fā)起HTTP請求一定會經(jīng)過HttpRequest#handleRequest方法處理,可以在這個方法中打斷點排查問題,經(jīng)過排查發(fā)現(xiàn)在PageFilterChain#doFilter中就完成了JSP的"編譯"和執(zhí)行工作,這點比較奇怪,因為之前分析Tomcat中"編譯JSP"的操作是在servlet中完成的。所以其實針對Resin對JSP文件處理的分析重點就在PageFilterChain#doFilter中。
- JSP編譯后會被封裝到Page對象中,而Page對象的引用被保存以pageRef屬性中,因此首先檢測pageRef是否為空,如果是則直接通過page.pageservice(req,
res);執(zhí)行請求,不經(jīng)過后面編譯的邏輯。 - 如果緩存中沒有page對象,則通過compilePage編譯JSP并封裝為Page對象返回,new
SoftReference創(chuàng)建引用對象,再通過pageservice執(zhí)行請求。
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
;if (pageRef
!= null
)page
= pageRef
.get();elsepage
= null
;if (page
== null
|| page
._caucho_isModified()) {try
{_pageRef
= null
;page
= compilePage(page
, req
, res
);if (page
!= null
) {_pageRef
= new SoftReference
<Page
>(page
);_isSingleThread
= page instanceof SingleThreadModel
;}} catch (FileNotFoundException e
) {page
= null
;notFound
= e
;}}if (page
== null
) {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
);if (uri
!= null
) {throw notFound
;}else if (forward
!= null
) {throw notFound
;}else if (errorUri
!= null
) {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
);}}elsepage
.pageservice(req
, res
);} catch (ServletException e
) {...}
Page#pageService–>JavaPage#service–>_aaa#_jspService,最后通過JSP生成類的_jspService方法完成請求。
如何進(jìn)入PageFilterChain?
通過上面的分析我們可以知道,在PageFilterChain中完成了對JSP的編譯和執(zhí)行,所以我們分析的重點就在于如何才能進(jìn)入PageFilterChain中?
追蹤創(chuàng)建PageFilterChain的過程,在WebApp#buildInvocation中,完成了PageFilterChain的創(chuàng)建,我摘了部分代碼分析。
- 首先從緩存中獲取FilterChains,如果有的話則直接獲取chains,緩存中保存的Chains和URL有關(guān)。
- 如果緩存沒有,則通過_servletMapper.mapServlet(invocation);獲取Chains。
public Invocation
buildInvocation(Invocation invocation
, boolean isTop
){...else {FilterChainEntry entry
= null
;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的內(nèi)容
String contextURI
= invocation
.getContextURI();try
{cleanUri
= Invocation
.stripPathParameters(contextURI
);} catch (Exception e
) {log
.warning(L
.l("Invalid URI {0}", contextURI
));return new
ErrorFilterChain(404);}
- 根據(jù)URL匹配獲取ServletMapping
ServletMapping servletMap
= _servletMap
.map(cleanUri
, vars
);
- 如果根據(jù)URL沒有匹配到Servlet處理則根據(jù)URL獲取資源內(nèi)容,并設(shè)置使用_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結(jié)尾則使用j_security_check作為Servlet
if (matchResult
== null
&& contextURI
.endsWith("j_security_check")) {servletName
= "j_security_check";}
- 如果匹配成功則設(shè)置servletPath和servletName等屬性到invocation對象中,根據(jù)Servletname從_servletManager獲取ServletConfigImpl對象,創(chuàng)建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);匹配到。
進(jìn)入到UrlMap#map中,發(fā)現(xiàn)默認(rèn)情況下*.jsp會交給.*.jsp(?=/)|..jsp\z正則處理。
主要出問題的是^..jsp(?=/)部分,這個正則的邏輯是匹配xxxx.jsp/xxxx所以我們傳入的路徑會被匹配到,這也是這個漏洞的本質(zhì)原因。
總結(jié)
其實我認(rèn)為Resin這么寫可能對作者來說這本身是個正常功能,因為之前Resin也實現(xiàn)了Invoker的功能,可以直接根據(jù)路徑加載任意類。
最后
關(guān)注我,持續(xù)更新······
私我獲取【網(wǎng)絡(luò)安全學(xué)習(xí)子·攻略】
總結(jié)
以上是生活随笔為你收集整理的【安全漏洞】Resin解析漏洞分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。