当前位置: 首页 > ds >正文

企业级管理平台横向越权问题及防护

横向越权简介

在企业级管理平台的开发过程中,安全性是至关重要的考量因素之一。其中,横向越权(Horizontal Privilege Escalation)是一种常见的安全漏洞,它允许用户访问或操作不属于自己的资源,但与自己的资源处于同一权限级别。本文将详细介绍横向越权的概念、示例以及防护措施,帮助开发者更好地理解和应对这一问题。

横向越权是指攻击者在没有提升自身权限的情况下,通过篡改请求参数等方式,访问或操作其他用户的数据。与纵向越权(Vertical Privilege Escalation)不同,纵向越权是攻击者通过漏洞提升自己的权限,从而访问更高权限级别的数据

横向越权通常发生在多租户系统中,例如企业级管理平台,每个用户都属于特定的组织或项目,但系统没有正确地限制用户只能访问属于自己的数据。这种漏洞可能导致数据泄露、数据篡改等严重后果。


横向越权示例

假设我们有一个企业级管理平台,用户可以通过以下 URL 访问项目详情:

GET /project/{projectId}

其中 {projectId} 是项目 ID。正常情况下,用户只能访问自己所属项目的详情。然而,如果系统没有正确地校验用户是否有权访问该项目,攻击者可以通过篡改 {projectId} 来访问其他项目的数据。例如,攻击者将 URL 改为:

GET /project/123

如果系统没有进行权限校验,攻击者就可以访问项目 ID 为 123 的项目详情,即使这个项目并不属于他。

横向越权防护

设计与实现 URL 统一标准格式

为了更好地管理权限和防止横向越权,建议采用统一的 URL 标准格式。例如:

GET /org/{orgId}/project/{projectId}

这种格式明确指定了组织 ID(orgId)和项目 ID(projectId),便于后续的权限校验。在实现时,可以通过中间件或拦截器来解析 URL 中的参数,并进行相应的权限检查。

保证 userId 可靠性

用户 ID(userId)是权限校验的关键参数,必须保证其可靠性和不可篡改性。建议从用户登录时生成的令牌(Token)中提取 userId。例如,使用 JWT(JSON Web Token)作为令牌,可以在 Token 的 payload 中存储 userId,并在每次请求时从 Token 中解析出 userId,从而确保其来源可靠。

public class JwtUtil {public static String getUserIdFromToken(String token) {// 解析 Token 并获取 userIdClaims claims = Jwts.parser().setSigningKey("secretKey").parseClaimsJws(token).getBody();return claims.getSubject();}
}

项目横向越权防护过滤器(全局防护)

全局防护过滤器可以对所有请求进行统一的权限校验,确保用户只能访问自己所属的项目数据。大致可以分为以下两种情况:

  • 查询或操作单一资源
  • 范围查询资源(例如,资源列表)

查询或操作单一资源

GET /org/{orgId}/project/{projectId}

必须携带 组织id项目id。过滤器可以直接根据当前用户的 userId 强制校验其操作的目标项目数据是否属于该用户。如果项目数据不属于当前用户,则直接拒绝访问。

    /*** 检查项目越权** @param orgId 组织id* @param projectId 项目id* @param userId 用户id*/private void checkProjectPE(Long orgId, Long projectId, Long userId) {// 检查组织项目关系LambdaQueryWrapper<ProjectDo> projectWrapper = Wrappers.lambdaQuery();projectWrapper.eq(ProjectDo::getOrganizationId, orgId);projectWrapper.eq(ProjectDo::getProjectId, projectId);Integer projectCount = projectMapper.selectCount(projectWrapper);if (projectCount <= 0) {throw new UnauthorizedException("当前组织下不存在该项目");}// 检查组织越权LambdaQueryWrapper<OrgWithAccount> lambdaQueryWrapper = Wrappers.lambdaQuery();lambdaQueryWrapper.eq(OrgWithAccount::getOrgId, orgId);lambdaQueryWrapper.eq(OrgWithAccount::getAccountId, userId);Integer orgWithAccountCount = orgWithAccountMapper.selectCount(lambdaQueryWrapper);if (orgWithAccountCount <= 0) {throw new UnauthorizedException("当前组织没有操作权限");}// 检查项目越权LambdaQueryWrapper<ProjectWithAccountDo> projectWithAccountWrapper = Wrappers.lambdaQuery();projectWithAccountWrapper.eq(ProjectWithAccountDo::getProjectId, projectId);projectWithAccountWrapper.eq(ProjectWithAccountDo::getAccountId, userId);Integer projectWithAccountCount = projectWithAccountMapper.selectCount(projectWithAccountWrapper);if (projectWithAccountCount <= 0) {throw new UnauthorizedException("当前项目没有操作权限");}}

范围查询资源

大致可以分为以下两种情况:

  1. 子公司范围查询:
GET /org/{orgId}/project/-1
  1. 总公司范围查询:
GET /org/-1/project/-1

为了提高系统的灵活性和易用性,引入一个特殊语义参数 -1。当用户请求的项目 ID 为 -1 时,系统自动将该用户有权限的所有项目 ID 列表注入上下文,供后续业务逻辑进行批量或范围查询。

    /*** 检查项目越权** @param orgId 组织id* @param projectId 项目id* @param userId 用户id*/private void checkProjectPE(Long orgId, Long projectId, Long userId) {// 检查组织项目关系if (!ID_WITH_FIND_ALL_AUTHORIZED_RESOURCES.equals(orgId) && !ID_WITH_FIND_ALL_AUTHORIZED_RESOURCES.equals(projectId)) {LambdaQueryWrapper<ProjectDo> projectWrapper = Wrappers.lambdaQuery();projectWrapper.eq(ProjectDo::getOrganizationId, orgId);projectWrapper.eq(ProjectDo::getProjectId, projectId);Integer projectCount = projectMapper.selectCount(projectWrapper);if (projectCount <= 0) {throw new UnauthorizedException("当前组织下不存在该项目");}}// 检查组织越权if (!ID_WITH_FIND_ALL_AUTHORIZED_RESOURCES.equals(orgId)) {LambdaQueryWrapper<OrgWithAccount> lambdaQueryWrapper = Wrappers.lambdaQuery();lambdaQueryWrapper.eq(OrgWithAccount::getOrgId, orgId);lambdaQueryWrapper.eq(OrgWithAccount::getAccountId, userId);Integer orgWithAccountCount = orgWithAccountMapper.selectCount(lambdaQueryWrapper);if (orgWithAccountCount <= 0) {throw new UnauthorizedException("当前组织没有操作权限");}}// 检查项目越权if (!ID_WITH_FIND_ALL_AUTHORIZED_RESOURCES.equals(projectId)) {LambdaQueryWrapper<ProjectWithAccountDo> projectWithAccountWrapper = Wrappers.lambdaQuery();projectWithAccountWrapper.eq(ProjectWithAccountDo::getProjectId, projectId);projectWithAccountWrapper.eq(ProjectWithAccountDo::getAccountId, userId);Integer projectWithAccountCount = projectWithAccountMapper.selectCount(projectWithAccountWrapper);if (projectWithAccountCount <= 0) {throw new UnauthorizedException("当前项目没有操作权限");}}}/*** 填充信息到上下文** @param context 上下文* @param userId 用户id* @param orgId 组织id* @param projectId 项目id*/private void fillToContext(RequestContext context, Long userId, Long orgId, Long projectId) {context.setUserId(userId);Account account = accountMapper.selectById(userId);Optional.ofNullable(account).map(Account::getAccountName).ifPresent(context::setUserName);List<String> roleNames = roleMapper.listRoleNameByUserId(userId);if (roleNames.stream().anyMatch(CommonConstant.ROLE_ROOT.getCode()::equals)) {     // 超级管理员拥有所有组织权限context.setIsAdmin(true);}// 填充组织id到上下文 非superAdmin场景,一个用户只会在一个组织下if (ID_WITH_FIND_ALL_AUTHORIZED_RESOURCES.equals(orgId)) {LambdaQueryWrapper<OrgWithAccount> orgWithAccountWrapper = Wrappers.lambdaQuery();orgWithAccountWrapper.eq(OrgWithAccount::getAccountId, userId);orgWithAccountWrapper.select(OrgWithAccount::getOrgId);context.setOrgId(orgWithAccountMapper.selectList(orgWithAccountWrapper).stream().limit(1).map(OrgWithAccount::getOrgId).findFirst().orElseThrow(() -> new UnauthorizedException("当前用户不在任何组织下")));} else {context.setOrgId(orgId);}// 填充项目id到上下文// (1.超级管理员处于总公司下,总公司下没有项目 2.超级管理员可以操作任何资源)if (!ID_WITH_FIND_ALL_AUTHORIZED_RESOURCES.equals(projectId)) {context.setProjectIds(Collections.singletonList(projectId));} else if(context.getIsAdmin()) {context.setProjectIds(Collections.emptyList());} else {LambdaQueryWrapper<ProjectWithAccountDo> projectWithAccountWrapper = Wrappers.lambdaQuery();projectWithAccountWrapper.eq(ProjectWithAccountDo::getAccountId, userId);projectWithAccountWrapper.select(ProjectWithAccountDo::getProjectId);List<Long> projectIds = projectWithAccountMapper.selectList(projectWithAccountWrapper).stream().map(ProjectWithAccountDo::getProjectId).collect(Collectors.toList());if (CollectionUtils.isEmpty(projectIds)) {throw new UnauthorizedException("当前用户不在任何项目下");}context.setProjectIds(projectIds);}}

资源横向越权防护(按需防护)

  • 如果项目中只有单一资源,则可以直接在上面的过滤器中同时对资源进行横向越权防护;
  • 如果系统中存在多种资源,例如,设备(sn)、任务(taskId)、应用(appId)等。可以在 Controller 层 按需实现 横向越权防护。
/*** 设备基础控制器** @author xxx* @since 2025-08-20*/
public abstract class DeviceBaseController extends BaseController {// 入参中有可能出现的资源keyprivate static final List<String> PARAM_SN_FIELD_NAMES = Arrays.asList("sn", "deviceId", "deviceIds", "pushTarget","deviceSnList");@Resourceprivate DeviceMapper deviceMapper;/*** 横向越权防护** @param request request* @throws IOException io异常*/@ModelAttributepublic void checkResource(HttpServletRequest request) throws IOException {List<String> snList = extractFromRequest(request, PARAM_SN_FIELD_NAMES);// 检查sn是否越权checkSnPE(snList);}/*** 检查Sn越权** @param snList sn列表*/private void checkSnPE(List<String> snList) {snList.forEach(sn -> {LambdaQueryWrapper<Device> deviceWrapper = Wrappers.lambdaQuery();WrapperUtils.fillProjectToQueryWrapper(deviceWrapper);deviceWrapper.eq(Device::getSn, sn);Device device = deviceMapper.selectOne(deviceWrapper);if (device == null) {throw new UnauthorizedException("当前设备不存在或没有操作权限:" + sn);}});}
}

完整代码实现

横向越权防护代码实现

总结

横向越权是一种常见的安全漏洞,可能导致严重的数据泄露和篡改问题。通过采用统一的 URL 标准格式、保证 userId 的可靠性、实现全局防护过滤器以及按需进行资源防护,可以有效防止横向越权的发生。在实际开发过程中,开发者应始终将安全性放在首位,确保系统能够抵御各种安全威胁。


至此,本次分享到此结束啦!!!

http://www.xdnf.cn/news/18591.html

相关文章:

  • Elasticsearch高能指南
  • SYBASE ASE、Oracle、MySQL/MariaDB、SQL Server及PostgreSQL在邮件/短信发送功能上的全面横向对比报告
  • Simulink不连续模块库(Hit Crossing/PWM/Rate Limiter/Rate Limiter Dynamic)
  • 【Day01】堆与字符串处理算法详解
  • uniapp轮播 轮播图内有定位样式
  • Oracle DB 10g 升级至 11.2.0.4报错-ORA-00132
  • Python办公之Excel(openpyxl)、PPT(python-pptx)、Word(python-docx)
  • NVM-Windows 命令大全
  • 量化交易 - 上证50利用动态PE增强模型 - python
  • React 学习笔记1 组件、State
  • 线性回归学习笔记
  • JAVA-15 (2025.08.20学习记录)
  • 集成电路学习:什么是Template Matching模版匹配
  • week3-[循环嵌套]好数
  • 基于Python与Tkinter开发的微博多功能自动化助手
  • Android焦点窗口变化导致遥控键值监听失效问题分析
  • # 重磅发布 | onecode 3.0.1 Base 源码正式开源:AI赋能的企业级开发框架
  • XXL-Job REST API 工具类完全解析:简化分布式任务调度集成
  • (第二十期上)HTML 超链接标签 a
  • 【python与生活】如何从视频中提取关键帧?
  • FPGA DP1.4 With DSC解决方案
  • 【华为OD-C卷-019 对称字符串 100分(python、java、c++、js、c)】
  • Vitest 测试框架完全指南 – 极速单元测试解决方案
  • C++ 常见的排序算法详解
  • AI 产业落地:从 “实验室神话” 到 “车间烟火气” 的跨越
  • Spring Cloud Netflix学习笔记06-Zuul
  • 机器学习中的集成算法与 k 均值聚类算法概述
  • uniapp跨域怎么解决
  • Go 并发编程-channel
  • 详解开源关键信息提取方案PP-ChatOCRv4的设计与实现