前言

最近,看到好多不错的关于“无文件Webshell”的文章,对其中利用上下文动态的注入Filter的技术做了一下简单验证,写一下测试总结,不依赖任何框架,仅想学习一下tomcat的filter。

先放几篇大佬的文章:

Filter介绍

详细介绍略,简单记录一下我的理解:

  • 过滤器(Filter):用来对指定的URL进行过滤处理,类似.net core里的中间件,例如登录验证过滤器可以用来限制资源的未授权访问;
  • 过滤链(FilterChain):通过URL匹配动态将所有符合URL规则的过滤器共同组成一个过滤链,顺序有先后,类似.net core的管道,不过区别在于过滤链是单向的,管道是双向;

同Servlet,一般Filter的配置方式:

  • web.xml
  • @WebFilter修饰

Filter注册调用流程

新建一个登录验证的Filter: SessionFilter.java

package com.reinject.MyFilter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper; /**
* 判断用户是否登录,未登录则退出系统
*/
@WebFilter(filterName = "SessionFilter", urlPatterns = "/*",
initParams = {@WebInitParam(name = "logonStrings", value = "index.jsp;addFilter.jsp"),
@WebInitParam(name = "includeStrings", value = ".jsp"),
@WebInitParam(name = "redirectPath", value = "/index.jsp"),
@WebInitParam(name = "disabletestfilter", value = "N")})
public class SessionFilter implements Filter { public FilterConfig config; public void destroy() {
this.config = null;
} public static boolean isContains(String container, String[] regx) {
boolean result = false; for (int i = 0; i < regx.length; i++) {
if (container.indexOf(regx[i]) != -1) {
return true;
}
}
return result;
} public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest hrequest = (HttpServletRequest)request;
HttpServletResponseWrapper wrapper = new HttpServletResponseWrapper((HttpServletResponse) response); String logonStrings = config.getInitParameter("logonStrings"); // 登录登陆页面
String includeStrings = config.getInitParameter("includeStrings"); // 过滤资源后缀参数
String redirectPath = hrequest.getContextPath() + config.getInitParameter("redirectPath");// 没有登陆转向页面
String disabletestfilter = config.getInitParameter("disabletestfilter");// 过滤器是否有效 if (disabletestfilter.toUpperCase().equals("Y")) { // 过滤无效
chain.doFilter(request, response);
return;
}
String[] logonList = logonStrings.split(";");
String[] includeList = includeStrings.split(";"); if (!this.isContains(hrequest.getRequestURI(), includeList)) {// 只对指定过滤参数后缀进行过滤
chain.doFilter(request, response);
return;
} if (this.isContains(hrequest.getRequestURI(), logonList)) {// 对登录页面不进行过滤
chain.doFilter(request, response);
return;
} String user = ( String ) hrequest.getSession().getAttribute("useronly");//判断用户是否登录
if (user == null) {
wrapper.sendRedirect(redirectPath);
return;
}else {
chain.doFilter(request, response);
return;
}
} public void init(FilterConfig filterConfig) throws ServletException {
config = filterConfig;
}
}

观察一个正常请求的函数栈:

_jspService:14, index_jsp (org.apache.jsp)
service:70, HttpJspBase (org.apache.jasper.runtime)
service:731, HttpServlet (javax.servlet.http)
service:439, JspServletWrapper (org.apache.jasper.servlet)
serviceJspFile:395, JspServlet (org.apache.jasper.servlet)
service:339, JspServlet (org.apache.jasper.servlet)
service:731, HttpServlet (javax.servlet.http)
internalDoFilter:303, ApplicationFilterChain (org.apache.catalina.core)
doFilter:208, ApplicationFilterChain (org.apache.catalina.core)
doFilter:52, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:241, ApplicationFilterChain (org.apache.catalina.core)
doFilter:208, ApplicationFilterChain (org.apache.catalina.core)
doFilter:66, SessionFilter (com.reinject.MyFilter)
internalDoFilter:241, ApplicationFilterChain (org.apache.catalina.core)
doFilter:208, ApplicationFilterChain (org.apache.catalina.core)
invoke:218, StandardWrapperValve (org.apache.catalina.core)
invoke:122, StandardContextValve (org.apache.catalina.core)
invoke:505, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:169, StandardHostValve (org.apache.catalina.core)
invoke:103, ErrorReportValve (org.apache.catalina.valves)
invoke:956, AccessLogValve (org.apache.catalina.valves)
invoke:116, StandardEngineValve (org.apache.catalina.core)
service:442, CoyoteAdapter (org.apache.catalina.connector)
process:1082, AbstractHttp11Processor (org.apache.coyote.http11)
process:623, AbstractProtocol$AbstractConnectionHandler (org.apache.coyote)
run:316, JIoEndpoint$SocketProcessor (org.apache.tomcat.util.net)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

找到最开始的ApplicationFilterChain位置,调用者是StandardWrapperValveinvoke,再观察invoke代码不难看出是用ApplicationFilterFactory动态生成的ApplicationFilterChain

// Create the filter chain for this request
ApplicationFilterFactory factory =
ApplicationFilterFactory.getInstance();
ApplicationFilterChain filterChain =
factory.createFilterChain(request, wrapper, servlet);

createFilterChain根据xml配置动态生成一个过滤链,部分代码如下:

// Acquire the filter mappings for this Context
StandardContext context = (StandardContext) wrapper.getParent();
FilterMap filterMaps[] = context.findFilterMaps(); // If there are no filter mappings, we are done
if ((filterMaps == null) || (filterMaps.length == 0))
return (filterChain); // Acquire the information we will need to match filter mappings
String servletName = wrapper.getName(); // Add the relevant path-mapped filters to this filter chain
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersURL(filterMaps[i], requestPath))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
boolean isCometFilter = false;
if (comet) {
try {
isCometFilter = filterConfig.getFilter() instanceof CometFilter;
} catch (Exception e) {
// Note: The try catch is there because getFilter has a lot of
// declared exceptions. However, the filter is allocated much
// earlier
Throwable t = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(t);
}
if (isCometFilter) {
filterChain.addFilter(filterConfig);
}
} else {
filterChain.addFilter(filterConfig);
}
}

所有的filter可以通过context.findFilterMaps()方法获取,FilterMap结构如下:

FilterMap中存放了所有filter相关的信息包括filterNameurlPattern

有了这些之后,使用matchFiltersURL函数将每个filter和当前URL进行匹配,匹配成功的通过context.findFilterConfig获取filterConfigfilterConfig结构如下:

之后将filterConfig添加到filterChain中,最后回到StandardWrapperValve中调用doFilter进入过滤阶段。

这个图(@宽字节安全)能够很清晰的看到整个filter流程:

通过上面的流程,可知所有的filter信息都是从context(StandardContext)获取到的,所以假如可以获取到这个context就可以通过反射的方式修改filterMapfilterConfig从而达到动态注册filter的目的。

获取context

打开jconsole,获取tomcatMbean:

感觉其中好多地方都可以获取到context,比如RequestProcessorResourceProtocolHandlerWebappClassLoaderValue

Value获取

代码:

MBeanServer mBeanServer = Registry.getRegistry(null, null).getMBeanServer();
// 获取mbsInterceptor
Field field = Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor");
field.setAccessible(true);
Object mbsInterceptor = field.get(mBeanServer);
// 获取repository
field = Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository");
field.setAccessible(true);
Object repository = field.get(mbsInterceptor);
// 获取domainTb
field = Class.forName("com.sun.jmx.mbeanserver.Repository").getDeclaredField("domainTb");
field.setAccessible(true);
HashMap<String, Map<String, NamedObject>> domainTb = (HashMap<String,Map<String,NamedObject>>)field.get(repository);
// 获取domain
NamedObject nonLoginAuthenticator = domainTb.get("Catalina").get("context=/,host=localhost,name=NonLoginAuthenticator,type=Valve");
field = Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object");
field.setAccessible(true);
Object object = field.get(nonLoginAuthenticator);
// 获取resource
field = Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource");
field.setAccessible(true);
Object resource = field.get(object);
// 获取context
field = Class.forName("org.apache.catalina.authenticator.AuthenticatorBase").getDeclaredField("context");
field.setAccessible(true);
StandardContext standardContext = (StandardContext) field.get(resource);

反射弧:mBeanServer->mbsInterceptor->repository->domainTb->nonLoginAuthenticator->resource->context

通过StandardContext注册filter

通过filter流程分析可知,注册filter需要两步:

  • 修改filterConfigs
  • 将filter插到filterMaps0位置;

在此之前,先看一下我们比较关心的context中三个成员变量:

  • filterConfigs:filterConfig的数组
  • filterRefs:filterRef的数组
  • filterMaps:filterMap的数组

filterConfig的结构之前看过,filterConfig.filterRef实际和context.filterRef指向的地址一样:

Expression: ((StandardContext) context).filterConfigs.get("SessionFilter").filterDef == ((StandardContext) context).filterDefs.get("SessionFilter");

StandardContext类的方法看,可以调用StandardContext.addFilterDef()修改filterRefs,然后调用StandardContext.filterStart()函数会自动根据filterDefs重新生成filterConfigs

filterConfigs.clear();
for (Entry<String, FilterDef> entry : filterDefs.entrySet()) {
String name = entry.getKey();
if (getLogger().isDebugEnabled())
getLogger().debug(" Starting filter '" + name + "'");
ApplicationFilterConfig filterConfig = null;
try {
filterConfig =
new ApplicationFilterConfig(this, entry.getValue());
filterConfigs.put(name, filterConfig);
} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
getLogger().error
(sm.getString("standardContext.filterStart", name), t);
ok = false;
}
}

综上,修改filterRefsfilterConfigs的代码如下:

// Gen filterDef
filterDef = new FilterDef();
filterDef.setFilterName(filterName);
filterDef.setFilterClass(filter.getClass().getName());
filterDef.setFilter(filter);
// Add filterDef
context.addFilterDef(filterDef);
// Refresh filterConfigs
context.filterStart();

filterMaps就简单了,添加上去改一下顺序加到0位置:

// filterMap
filterMap.setFilterName(filterName);
filterMap.setDispatcher(String.valueOf(DispatcherType.REQUEST));
filterMap.addURLPattern(filterUrlPatern);
context.addFilterMap(filterMap);
// Order
Object[] filterMaps = context.findFilterMaps();
Object[] tmpFilterMaps = new Object[filterMaps.length];
int index = 1;
for (int i = 0; i < filterMaps.length; i++)
{
FilterMap f = (FilterMap) filterMaps[i];
if (f.getFilterName().equalsIgnoreCase(filterName)) {
tmpFilterMaps[0] = f;
} else {
tmpFilterMaps[index++] = f;
}
}
for (int i = 0; i < filterMaps.length; i++) {
filterMaps[i] = tmpFilterMaps[i];
}

通过ApplicationContext注册filter

多次调试发现有多处context,上面一直用的都是StandardContext,观察该结构发现还有一个私有变量context,类型为ApplicationContext,通过他的定义发现其实就是一个ServletContext

public class ApplicationContext implements ServletContext {
}

该结构中也有一些filter操作的方法:

public Map<String, ? extends FilterRegistration> getFilterRegistrations() {}
public FilterRegistration getFilterRegistration(String filterName) {}
public FilterRegistration.Dynamic addFilter(String filterName, Filter filter) {}

这三个函数返回值都是FilterRegistration,看一下结构:

public class ApplicationFilterRegistration implements FilterRegistration.Dynamic {
public void addMappingForServletNames(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... servletNames) {}
public void addMappingForUrlPatterns(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... urlPatterns) {}
public Collection<String> getServletNameMappings() {}
public Collection<String> getUrlPatternMappings() {}
public String getClassName() {}
public String getInitParameter(String name) {}
public Map<String, String> getInitParameters() {}
public String getName() {}
public boolean setInitParameter(String name, String value) {}
public Set<String> setInitParameters(Map<String, String> initParameters) {}
public void setAsyncSupported(boolean asyncSupported) {}
}

很明显打包了一些常用的注册Filter的函数,所以可以使用ApplicationContextFilterRegistration进行注册,测试代码如下:

// Define
ApplicationContext applicationContext = new ApplicationContext(standardContext);
Filter filter = new TestApplicationContextAddFilter();
// Registe Filter
FilterRegistration.Dynamic filterRegistration = applicationContext.addFilter(filterName, filter);
// Create Map for urlPattern
filterRegistration.addMappingForUrlPatterns(EnumSet.of(javax.servlet.DispatcherType.REQUEST), false, new String[]{urlPatern});
// Order
Object[] filterMaps = standardContext.findFilterMaps();
Object[] tmpFilterMaps = new Object[filterMaps.length];
int index = 1;
for (int i = 0; i < filterMaps.length; i++)
{
FilterMap f = (FilterMap) filterMaps[i];
if (f.getFilterName().equalsIgnoreCase(filterName)) {
tmpFilterMaps[0] = f;
} else {
tmpFilterMaps[index++] = f;
}
}
for (int i = 0; i < filterMaps.length; i++) {
filterMaps[i] = tmpFilterMaps[i];
}

很不幸,有IllegalStateException异常:

严重: Servlet.service() for servlet [HelloWorldServlet] in context with path [] threw exception [Servlet execution threw an exception] with root cause
java.lang.IllegalStateException: Filters can not be added to context as the context has been initialised
at org.apache.catalina.core.ApplicationContext.addFilter(ApplicationContext.java:1005)
at org.apache.catalina.core.ApplicationContext.addFilter(ApplicationContext.java:970)
at com.reinject.test.TestApplicationContextAddFilter.<clinit>(TestApplicationContextAddFilter.java:61)
at com.reinject.MyServlet.HelloWorldServlet.doGet(HelloWorldServlet.java:50)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:624)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)

通过观察AddFilter报错的位置,发现是对standardContextstate校验的时候不达标抛出的异常:

if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
//TODO Spec breaking enhancement to ignore this restriction
throw new IllegalStateException(
sm.getString("applicationContext.addFilter.ise",
getContextPath()));
}

那么可以先修改一下stateLifecycleState.STARTING_PREP:

java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class.getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP);

再运行正常:

不过测试发现如果state不改回来,之后访问所有页面都会503

综上:

// Fix State
java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class.getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP);
// Define
ApplicationContext applicationContext = new ApplicationContext(standardContext);
Filter filter = new TestApplicationContextAddFilter();
// Registe Filter
FilterRegistration.Dynamic filterRegistration = applicationContext.addFilter(filterName, filter);
// Create Map for urlPattern
filterRegistration.addMappingForUrlPatterns(EnumSet.of(javax.servlet.DispatcherType.REQUEST), false, new String[]{urlPatern});
// Restore State
stateField = org.apache.catalina.util.LifecycleBase.class.getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTED);
// Order
Object[] filterMaps = standardContext.findFilterMaps();
Object[] tmpFilterMaps = new Object[filterMaps.length];
int index = 1;
for (int i = 0; i < filterMaps.length; i++)
{
FilterMap f = (FilterMap) filterMaps[i];
if (f.getFilterName().equalsIgnoreCase(filterName)) {
tmpFilterMaps[0] = f;
} else {
tmpFilterMaps[index++] = f;
}
}
for (int i = 0; i < filterMaps.length; i++) {
filterMaps[i] = tmpFilterMaps[i];
}

实验过程中的代码

获取 方式,git clone https://github.com/cnsimo/TomcatFilterInject.git

部署方式,idea + tomcat7.0.70

添加tomcat7.0.70/lib为依赖。

Tomcat Filter之动态注入的更多相关文章

  1. Java 扫描实现 Ioc 动态注入,过滤器根据访问url调用自定义注解标记的类及其方法

    扫描实现 Ioc 动态注入 参考: http://www.private-blog.com/2017/11/16/java-%e6%89%ab%e6%8f%8f%e5%ae%9e%e7%8e%b0-i ...

  2. 基于ImportBeanDefinitionRegistrar和FactoryBean动态注入Bean到Spring容器中

    基于ImportBeanDefinitionRegistrar和FactoryBean动态注入Bean到Spring容器中 一.背景 二.实现方案 1.基于@ComponentScan注解实现 2.基 ...

  3. .Net Core MVC 网站开发(Ninesky) 2.3、项目架构调整(续)-使用配置文件动态注入

    上次实现了依赖注入,但是web项目必须要引用业务逻辑层和数据存储层的实现,项目解耦并不完全:另一方面,要同时注入业务逻辑层和数据访问层,注入的服务直接写在Startup中显得非常臃肿.理想的方式是,w ...

  4. [ASP.NET MVC] 利用动态注入HTML的方式来设计复杂页面

    原文:[ASP.NET MVC] 利用动态注入HTML的方式来设计复杂页面 随着最终用户对用户体验需求的不断提高,实际上我们很多情况下已经在按照桌面应用的标准来设计Web应用,甚至很多Web页面本身就 ...

  5. 教你怎么用Mono Cecil - 动态注入 (注意代码的注释)

    原文 教你怎么用Mono Cecil - 动态注入 (注意代码的注释) 使用 Mono Cecil 进行反编译:using Mono.Cecil; using Mono.Cecil.Cil; //.. ...

  6. iOS中动态注入JavaScript方法。动态给html标签添加事件

    项目中有这样一种需求,给html5网页中图片添加点击事件,并且弹出弹出点击的对应的图片,并且可以保持图片到本地 应对这样的需求你可能会想到很多方法来实现. 1. 最简单的方法就是在html5中添加图片 ...

  7. 使用Autofac动态注入启动Api服务

    Autofac Autofac(https://autofac.org/)是一款.NET的IOC组件,它可以和Owin, Web Api, ASP.NET MVC, .NET Core完美结合,帮助开 ...

  8. java高级-动态注入替换类Instrumentation

    介绍 利用java.lang.instrument(容器类) 做动态 Instrumentation(执行容器) 是 Java SE 5 的新特性. 使用 Instrumentation,开发者可以构 ...

  9. Java Filter过滤xss注入非法参数的方法

    http://blog.csdn.NET/feng_an_qi/article/details/45666813 Java Filter过滤xss注入非法参数的方法 web.xml: <filt ...

随机推荐

  1. Java实现LeetCode 139 单词拆分

    public boolean wordBreak(String s, List<String> wordDict) { if(s.length() == 0){ return false; ...

  2. java实现低碳生活大奖赛

    某电视台举办了低碳生活大奖赛.题目的计分规则相当奇怪: 每位选手需要回答 10 个问题(其编号为 1 到 10),越后面越有难度. 答对的,当前分数翻倍:答错了则扣掉与题号相同的分数(选手必须回答问题 ...

  3. JDBC连接泄露问题的排查过程总结

    当前使用的Spring JDBC版本是5.0.0.RC1,HikariCP版本是3.1.0. 今天测试同学反馈在前端页面点击次数多了,就报500错误,数据显示不出来.于是我在后台服务日志中观察发现Hi ...

  4. Firewalld 的基本使用

    RHEL 7 系统中集成了多款防火墙管理工具,其中 firewalld(Dynamic Firewall Manager of Linux systems,Linux 系统的动态防火墙管理器)服务是默 ...

  5. 介绍几种给你的Python代码加上酷炫的进度条的方式

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 大家好,在下载某些文件的时候你一定会不时盯着进度条,在写代码的时候使用进度 ...

  6. springMVC 异常

    springMVC  异常 0.依赖(不只是本次案例所需) <?xml version="1.0" encoding="UTF-8"?> <p ...

  7. Openshift 4.4 静态 IP 离线安装系列:准备离线资源

    本系列文章描述了离线环境下以 UPI (User Provisioned Infrastructure) 模式安装 Openshift Container Platform (OCP) 4.4.5 的 ...

  8. Linux服务器安装python3.6

    CentOS 7上默认安装的python版本是2.7.5,系统自带的旧版本python被系统很多其他软件环境依赖,因此不能卸载原Python,直接选择Python3.6.5进行全新安装. 1 安装Py ...

  9. cb16a_c++_顺序容器的选用_排序_二分查找

    /*cb16a_c++_顺序容器的选用_排序_二分查找顺序容器: 1.vector的优点与缺点 vector优点:排序利用下标,快速排序,做二分查找非常快 2.list的优点与缺点 list优点:插入 ...

  10. 05.DRF-Django REST framework 简介

    一.明确REST接口开发的核心任务 分析一下上节的案例,可以发现,在开发REST API接口时,视图中做的最主要有三件事: 将请求的数据(如JSON格式)转换为模型类对象 操作数据库 将模型类对象转换 ...