Liferay集合提供程序授权缺失漏洞(CVE-2023-33952)深度剖析与修复
1. 项目概述:一次对Liferay授权边界的深度审视
最近在梳理一个基于Liferay DXP构建的企业门户安全基线时,无意间踩到了一个“授权缺失”的坑。这个漏洞的官方编号是CVE-2023-33952,听起来平平无奇,但其背后的逻辑和潜在影响,却让我对Liferay这类大型平台化产品的权限模型有了更深的思考。简单来说,这个漏洞的核心在于,Liferay Portal和DXP的“集合提供程序”功能,在某些特定配置下,未能正确校验当前用户是否有权限访问其聚合的某些子内容,导致低权限甚至未授权用户可以绕过前端限制,直接访问到本不该看到的数据。这可不是简单的页面越权,它触及了门户内容聚合架构的信任边界问题。
想象一下,你搭建了一个公司门户,首页上通过“最新文档”、“部门新闻”、“项目动态”等多个内容块(即“集合”)来聚合展示来自不同站点、不同权限区域的信息。作为管理员,你精心配置了每个集合的显示规则,以为万事大吉。但这个漏洞的存在,意味着攻击者可能通过直接调用集合提供程序的底层API,或者构造特定的请求,绕过你精心设计的展示层权限控制,像“透视”一样看到集合背后那些本应对他隐藏的条目。这对于依赖Liferay进行敏感信息分发的企业来说,无疑是一个需要严肃对待的风险。
本篇文章,我将从一个实践者的角度,彻底拆解这个授权缺失漏洞的成因、影响范围、复现方法以及修复方案。我不会只停留在官方公告的描述上,而是会结合Liferay的架构,深入代码层面分析漏洞触发的路径,并分享在真实环境中进行安全评估和加固的实操经验。无论你是Liferay的开发、运维还是安全工程师,理解这个漏洞都将帮助你构建更健壮的门户应用。
2. 漏洞核心原理与架构背景剖析
要理解这个漏洞,首先得搞清楚Liferay里的“集合”是什么,以及“集合提供程序”是如何工作的。这不仅仅是功能层面的了解,更需要深入到其数据获取和权限校验的流程。
2.1 Liferay集合与提供程序模型解析
在Liferay中,“集合”是一个逻辑概念,用于将分散的内容项(如Web内容文章、博客条目、文档等)动态地聚合在一起,并以小部件(Widget)的形式展示在页面上。你可以把它理解为一个可配置的、智能的内容查询和展示模块。
创建集合时,关键的一步是选择“集合提供程序”。Liferay提供了多种内置提供程序:
- 手动选择:管理员手动挑选特定内容项。
- 动态集合:基于预定义的“信息列表”或“类别”来自动筛选内容。
- 自定义:通过编写代码或使用其他扩展方式定义集合来源。
漏洞主要涉及的是那些能够动态获取内容的提供程序,尤其是基于“信息列表”和“类别”的提供程序。这些提供程序的工作流程可以简化为:
- 接收上下文:提供程序接收页面、用户、站点等上下文信息。
- 执行查询:根据配置的筛选条件(如列表ID、分类词汇、标签等),在后台执行内容查询。
- 应用权限过滤(关键步骤):理论上,查询结果在返回给前端展示之前,应该经过一层权限过滤,即检查当前用户对每一个查询到的内容项是否拥有“查看”权限。
- 返回结果:将过滤后的、用户有权访问的内容项列表返回给集合小部件进行渲染。
2.2 授权缺失漏洞的根源定位
漏洞就出现在上述流程的第3步——权限过滤的缺失或不完整。在某些版本的Liferay Portal和DXP中,部分集合提供程序在后台执行查询时,直接使用了不包含权限校验的底层数据访问方法,或者权限校验的上下文(如当前用户信息)未能正确传递到查询的最深层。
举个例子,假设我们有一个“部门敏感文档”信息列表,其权限设置为仅“财务部”成员可查看。我们在门户首页配置了一个集合,使用“动态集合”提供程序,来源指向这个“部门敏感文档”列表。管理员预期的是,只有财务部成员登录后,才能在首页看到这个集合及其中的文档。
然而,存在漏洞的版本中,当非财务部成员(甚至未登录用户)访问该页面时,集合提供程序在后台执行查询的代码路径可能类似于:
// 伪代码,示意有问题的流程 public List<ContentItem> getContentItems(CollectionProviderContext context) { // 获取配置的“信息列表”ID long infoListId = getConfiguredInfoListId(); // 直接调用底层服务,获取该列表下的所有条目,未传入用户上下文进行权限过滤 List<ContentEntry> allEntries = infoListEntryLocalService.getEntries(infoListId); // 转换为集合项并返回 return convertToContentItems(allEntries); }而正确的、安全的代码路径应该像这样:
public List<ContentItem> getContentItems(CollectionProviderContext context) { long infoListId = getConfiguredInfoListId(); // 调用服务时,明确传入权限检查所需的参数,如用户ID、作用域组ID等 List<ContentEntry> filteredEntries = infoListEntryService.getEntries( infoListId, context.getUserId(), context.getScopeGroupId(), QueryUtil.ALL_POS, // 起始 QueryUtil.ALL_POS // 结束 ); return convertToContentItems(filteredEntries); }或者,更常见的是,Liferay的Service层方法本身应该集成了权限检查。但如果集合提供程序错误地调用了LocalService(通常用于内部处理,绕过权限)而非Service(对外暴露,包含权限逻辑),就会导致漏洞。
注意:这里的代码是高度简化的示意,实际Liferay的权限模型涉及
PermissionChecker、ResourcePermission等复杂组件。漏洞的本质是权限校验上下文在“集合提供程序”这一特定调用链中丢失或未被应用。
2.3 影响范围与风险量化
这个漏洞的影响是直接的信息泄露。其严重程度取决于利用此漏洞能访问到的内容敏感度。
- 低风险场景:集合聚合的是公开新闻、产品手册等非敏感信息,漏洞影响有限。
- 高风险场景:集合聚合了包含员工个人信息、内部财务数据、未公开项目计划、合同文档等敏感内容的条目。攻击者利用此漏洞,可以系统性地“窥探”这些集合背后的数据池。
更令人担忧的是,这种漏洞的利用往往“静默”。攻击者不需要暴力破解,不需要触发异常日志,只需要像正常用户一样浏览页面,但通过工具拦截和修改对集合数据的异步请求(通常是通过JSON API),就可能获取到超权限的数据。在安全检测中,这类漏洞容易被传统的Web漏洞扫描器忽略,需要有针对性的业务逻辑安全测试才能发现。
3. 漏洞复现与验证实操指南
理论分析之后,我们必须在可控环境中验证漏洞,才能真正理解其触发条件。以下是我在测试环境(以Liferay DXP 7.4 GA75之前版本为例)中的复现步骤。
3.1 测试环境搭建与配置
- 环境准备:部署一个存在漏洞版本的Liferay DXP实例(例如7.4 GA70)。确保拥有管理员账号和一个普通用户账号(如
testuser,仅拥有默认权限)。 - 创建敏感内容:
- 以管理员身份登录,创建一个新的“信息列表”,命名为“高管通讯”。
- 在该列表中,添加几条条目,内容可以模拟为“Q1财报会议纪要”、“董事会决议草案”等。
- 关键步骤:进入“高管通讯”列表的“权限”设置,移除“Guest”和“普通用户”角色的“查看”权限。确保只有“管理员”等特定角色可以查看。
- 创建有漏洞的集合:
- 进入页面编辑模式,添加“集合”小部件到页面。
- 配置该集合:
- 集合来源:选择“动态集合”。
- 选择信息列表:指向刚才创建的“高管通讯”列表。
- 保存页面。此时,以管理员身份浏览,可以看到集合中显示了“高管通讯”里的条目。以
testuser身份浏览,预期应该看不到任何内容(因为无权限)。
3.2 漏洞验证与利用过程
现在,我们尝试以普通用户testuser的身份,绕过前端限制。
- 前端观察:使用
testuser登录,打开包含该集合的页面。浏览器开发者工具切换到“网络”(Network)选项卡。刷新页面,观察网络请求。你会看到页面加载后,通常会有异步请求去获取集合数据,请求URL可能包含/o/headless-delivery/...或/api/jsonws/...等路径,参数中会包含集合的ID或配置信息。 - 定位数据请求:在这些请求中,寻找返回数据格式为JSON、且内容看似为文章或条目列表的请求。这就是集合提供程序在后台获取数据的API调用。
- 权限绕过尝试:
- 直接复制这个API请求的URL和完整参数(包括Cookie,因为已登录)。
- 使用一个更强大的HTTP客户端工具(如Postman、cURL或Burp Suite Repeater)。
- 将请求发送出去。在存在漏洞的环境中,即使使用
testuser的会话,这个API请求也可能返回完整的“高管通讯”列表条目,包括其标题、内容摘要甚至完整内容。
- 对比验证:为了确认这是漏洞而非配置错误,可以尝试直接访问“高管通讯”信息列表的常规展示页面(如果有的话),
testuser应该被拒绝访问或看到空列表。同时,尝试调用明确需要权限检查的、获取单个条目详情的API,testuser也应该被拒绝。这种对比能帮助你确认,漏洞仅限于“集合提供程序”这个特定的数据获取路径。
实操心得:在实际测试中,浏览器的“网络”选项卡可能因为Liferay的客户端渲染框架而变得复杂。一个更有效的方法是直接查找集合小部件对应的Portlet ID或UUID,然后搜索Liferay的Java API文档,找到其对应的后端服务端点。使用工具直接调用这些端点,是验证这类服务层授权漏洞的最直接方法。
3.3 利用场景深度挖掘
基础的API直接调用只是开始。一个有经验的安全测试者会思考更多场景:
- 组合查询:如果集合提供程序支持复杂的筛选器(如按标签、分类、自定义字段过滤),攻击者是否可以修改这些过滤参数,在已越权的数据集中进行更精确的“数据挖掘”?
- 影响其他提供程序:漏洞报告可能只提及了“信息列表”提供程序,但基于相同框架的“分类”提供程序、“自定义SQL”提供程序是否也存在类似问题?需要做横向测试。
- 批量信息泄露:通过自动化脚本,遍历门户上所有的集合ID或配置,可以快速测绘出整个门户中所有存在授权缺失的集合点,形成一份敏感数据地图。
4. 修复方案与安全加固实践
确认漏洞存在后,接下来就是修复和加固。方案分为立即缓解和根本解决两个层面。
4.1 官方补丁分析与应用
Liferay官方针对CVE-2023-33952发布了安全修复补丁。对于DXP用户,通常以Fix Pack或Hotfix的形式提供。
- 获取补丁:登录Liferay官方客户支持门户,根据你的DXP版本(如7.4 GA),查找对应的安全修复公告,下载相关的Fix Pack。
- 分析补丁内容:在应用前,建议有条件的话,查看补丁修改的代码文件。通常修复会集中在
com.liferay.info.collection.provider.impl相关的类中。修复的核心逻辑必然是确保在InfoCollectionProvider的实现类中,所有数据获取方法都正确地传递并使用了InfoRequestContext(其中包含用户权限信息),或者调用了已经包含权限检查的Service层方法,而非LocalService。 - 应用补丁:按照官方指南,在测试环境先行部署Fix Pack。部署后,重复上述漏洞验证步骤,确认使用
testuser调用相关API时,返回的是空数组或权限错误,而非敏感数据。 - 回归测试:应用安全补丁后,必须对门户的关键功能进行回归测试,确保修复没有破坏正常的集合显示功能。特别是那些依赖复杂权限模型(如组织层级权限、资产库权限)的集合。
4.2 自定义代码的安全自查与修复
如果你在项目中自定义过集合提供程序(实现InfoCollectionProvider接口),那么你必须手动检查自己的代码。这是一个关键的自查清单:
| 检查点 | 安全做法 | 风险做法 |
|---|---|---|
| 数据获取入口 | 使用*ServiceUtil或*Service的带权限检查的方法。 | 使用*LocalServiceUtil或直接SQL查询。 |
| 上下文传递 | 方法参数中包含InfoRequestContext,并从中提取userId,scopeGroupId用于查询。 | 忽略InfoRequestContext,使用硬编码或默认值。 |
| 权限显式校验 | 在返回数据前,对每个条目调用PermissionChecker.hasPermission(...)进行二次校验。 | 假设底层服务或查询已完全处理权限。 |
| 服务调用方式 | 通过ServiceContextThreadLocal.getServiceContext()获取当前服务上下文。 | 新建一个空的或默认的ServiceContext。 |
对于发现问题的自定义提供程序,修复方法是重构数据获取逻辑。确保从InfoRequestContext中获取正确的用户和范围上下文,并将其传递到所有后续的服务调用中。
4.3 运维层面的监控与防护
在应用补丁的同时,可以在运维层面增加一些防护和监测措施:
- WAF规则:在Web应用防火墙中,可以针对Liferay集合相关的API路径(如
/o/headless-delivery/v1.0/sites/${siteId}/content-sets/${contentSetId}/contents)设置规则,对异常的访问模式(如未登录用户访问、低权限用户高频访问)进行告警或限流。 - 日志审计:启用Liferay的详细审计日志,特别是针对
InfoCollectionProvider相关类的日志。监控是否有大量来自同一低权限账号的对多个不同集合ID的访问请求,这可能是自动化探测的迹象。 - 权限最小化原则复查:借此机会,全面审查门户中所有集合的配置。确保每个集合所引用的源内容(信息列表、分类等),其本身的权限设置遵循最小化原则。即使提供程序存在漏洞,严格的内容源权限也能将损失降到最低。
5. 深度防御:构建安全的Liferay内容聚合架构
修复一个具体漏洞是治标,更重要的是建立“深度防御”的思想,从架构和开发流程上避免此类问题。
5.1 安全开发规范(SDL)集成
对于Liferay二次开发团队,应将以下条款纳入开发规范:
- 强制代码审查点:所有实现
InfoCollectionProvider、InfoFilterProvider等扩展点的代码,在评审时必须重点审查其权限上下文传递和数据获取方式。 - 使用安全工具:在CI/CD管道中集成静态代码分析工具,编写或使用现有规则来检测对
*LocalService的潜在危险调用(特别是在Web上下文可访问的代码中)。 - 单元测试要求:为自定义集合提供程序编写单元测试,必须包含权限测试用例。例如,模拟一个无权限用户调用
getInfoItems方法,断言返回结果为空或抛出权限异常。
5.2 权限校验的“纵深防御”策略
不要完全依赖Liferay框架或某一层的权限校验。在设计内容聚合功能时,考虑多层校验:
- 展示层校验:集合小部件本身可以根据当前用户角色,决定是否渲染。
- 服务层校验(核心):集合提供程序的业务逻辑必须进行权限校验,这是最主要的防线。
- 数据层校验:如果内容源来自自定义表或外部系统,在数据库查询或外部API调用中,也应尽可能加入基于用户身份的过滤条件。
- 内容级校验:对于极度敏感的内容,可以考虑在内容存储时进行加密,并在提供程序中集成解密逻辑,而解密密钥的获取与用户权限再次绑定。
5.3 定期安全评估与渗透测试指南
对于运行重要业务的门户,定期进行安全评估是必须的。针对集合和内容聚合功能,渗透测试应关注:
- 参数篡改:测试是否可以通过修改集合ID、站点ID、分类ID等参数,访问到其他站点的集合或内容。
- 权限提升:测试低权限用户能否通过集合API,获取到高权限用户才能看到的内容条目。
- 信息泄露:检查API返回的JSON数据是否包含了过多的信息,如内部ID、创建者详情、未发布的草稿内容等。
- 批量枚举:测试是否可以通过遍历数字ID的方式,枚举出系统中所有的集合配置,从而发现未链接到页面但可访问的敏感集合。
这个关于Liferay集合提供程序的授权缺失漏洞,给我最大的启示是:在复杂的、可扩展的平台架构中,权限校验的完整性极易在自定义扩展点或数据流转的边界处被破坏。作为开发者和架构师,我们不能假设框架提供的每个“插槽”都是天生安全的。每一次实现一个扩展接口,每一次从A点获取数据传递到B点,都必须清晰地自问:当前用户的权限上下文是否完整地、正确地传递到了每一个需要它的地方?这次漏洞分析,本质上是一次对数据流中权限上下文传递路径的追查之旅。在后续的所有Liferay项目中,我都会把对InfoRequestContext的传递和利用,作为代码审查清单里的必选项。
