跳转到帖子

背景水平越权是代码审计和渗透测试里最常见、也最容易被低估的问题之一。它通常不依赖复杂利用链,根因往往是接口只校验了登录态,却没有校验当前用户是否有权访问目标资源。这类问题在业务系统中很隐蔽:页面入口可能没有暴露敏感操作,但前端请求里存在资源 ID;后端接口看起来也有鉴权中间件,但实际只做了“是否登录”的判断。长期看,水平越权比单点漏洞更值得关注,因为它反映的是权限模型和数据归属校验的缺失。典型场景一个常见的业务接口如下:用户登录后可以查看自己的订单详情,前端请求类似:GET /api/order/detail?id=10240
Cookie: session=...如果后端只根据 id 查询订单并返回结果,而没有判断订单所属用户是否等于当前登录用户,就可能导致用户 A 访问用户 B 的订单详情。类似场景还包括:通过用户 ID 查看个人资料、收货地址、发票信息。通过项目 ID、团队 ID 访问项目配置、成员列表。通过文件 ID 下载附件、合同、报表。通过任务 ID 修改工单、审批状态、备注内容。问题根因从代码审计角度看,水平越权通常不是“没有鉴权”,而是“鉴权粒度不够”。比较常见的几类根因如下。1. 只校验登录态很多系统会在统一中间件里判断用户是否登录,例如:if session.user_id is None:
return unauthorized()这个判断只能证明请求者是系统用户,不能证明他有权访问某条具体业务数据。真正关键的是资源归属校验。2. 直接信任前端传入的用户标识一些接口会接收 userId、accountId、tenantId 等参数,然后直接使用这些参数查询数据:GET /api/address/list?userId=10086如果后端没有将 userId 与当前登录用户绑定校验,那么参数被修改后就可能访问其他用户数据。比较稳妥的方式是:普通用户场景下,用户身份应从服务端会话或 token 中解析,而不是相信客户端传入。3. 查询条件缺少归属约束这是最常见的代码层面问题。错误写法类似:SELECT * FROM orders WHERE id = ?;更安全的写法应该把当前用户或租户作为查询条件的一部分:SELECT * FROM orders WHERE id = ? AND user_id = ?;对于多租户系统,还需要同时带上租户维度:SELECT * FROM project WHERE id = ? AND tenant_id = ?;4. 写操作只校验资源存在越权不只发生在读取接口,写接口风险更大。比如更新备注、删除附件、修改地址等操作,如果仅判断目标记录存在,就可能被其他用户修改。错误逻辑常见于:record = db.get_by_id(id)
if record is None:
return not_found()

record.remark = request.remark
db.save(record)应当先确认当前用户对该记录具备操作权限:record = db.get_by_id_and_owner(id, current_user.id)
if record is None:
return not_found_or_forbidden()

record.remark = request.remark
db.save(record)审计思路排查水平越权时,不建议只盯接口路径。更有效的方法是围绕“资源 ID”和“权限边界”做梳理。第一步:识别资源型参数重点关注下列参数名,它们通常代表具体资源或归属关系:id、orderId、fileId、taskId、projectIduserId、memberId、accountIdtenantId、orgId、companyIdowner、creator、operator这些参数出现的位置不只在 URL 查询字符串,也可能在 JSON body、表单字段、路径参数里。第二步:确认接口权限模型审计时先不要急着改参数,而是先问清楚几个问题:这个资源属于用户、组织、租户,还是公共资源?当前接口是读操作还是写操作?是否存在管理员、成员、访客等不同角色?是否有跨组织协作、共享链接、委托授权等特殊业务规则?如果不了解业务权限模型,很容易误报或漏报。例如某些资料确实是组织内共享的,但附件下载可能只允许创建者或管理员访问。第三步:从代码入口追踪到数据查询以常见 MVC 项目为例,可以按这个链路看:Controller / Route
-> Service
-> Repository / DAO
-> SQL / ORM Query重点看两点:Controller 层是否从可信上下文获取当前用户身份。DAO 层查询是否包含用户、组织或租户约束。如果权限判断散落在多个 Service 中,建议关注是否存在遗漏接口。成熟一些的系统通常会把权限判断封装为统一方法,例如:permission.checkProjectAccess(currentUser, projectId)
permission.checkOrderOwner(currentUser, orderId)
permission.checkTenantScope(currentUser, tenantId)这类封装不是为了“好看”,而是降低遗漏概率。越权漏洞很多时候就出现在新增接口没有复用已有权限校验。第四步:对比同类接口实现同一业务模块里,接口实现往往存在复用模式。可以横向对比:订单详情接口有归属校验,订单导出接口是否也有?文件预览有权限判断,文件下载是否也有?列表接口按租户过滤,详情接口是否按租户过滤?页面接口做了权限判断,异步接口是否也做了?很多真实漏洞来自“边角接口”:导出、打印、下载、预览、批量查询、历史记录、回收站等。复现与验证建议在授权测试或自测环境中验证水平越权时,建议准备两个低权限测试账号,避免使用真实用户数据。验证过程要尽量可控、可回溯。一个基本的验证流程:使用账号 A 创建或查看一条资源,记录资源 ID。使用账号 B 登录,保持自身会话。用账号 B 请求账号 A 的资源 ID。观察返回状态码、响应内容、操作结果和审计日志。建议同时验证读和写两类接口,但不要对生产数据做破坏性操作。写接口可以使用备注、草稿、测试文件等低风险资源。# 示例:仅用于自测环境中确认接口是否绑定当前用户
curl -i ' \\
-H 'Cookie: session=USER_B_SESSION'如果返回了账号 A 的订单详情,基本可以判断存在水平越权。若返回 404 或 403,需要结合系统设计判断是否符合预期。很多团队会选择对无权限资源返回 404,以避免暴露资源存在性。修复建议1. 不信任客户端传入的身份字段普通用户请求中,userId、tenantId、orgId 等敏感身份字段应尽量从服务端上下文获取。即使业务上必须传入,也要和当前登录态进行一致性校验。current_user_id = auth.current_user_id()
# 不直接使用 request.userId 作为数据归属条件
orders = order_repo.find_by_user(current_user_id)2. 查询层强制加入归属条件对于资源详情、更新、删除、下载等接口,推荐使用“带归属条件的查询”。不要先查出资源,再在业务层随意判断,尤其不要在多个分支里重复写判断。order = order_repo.find_one(
id=order_id,
user_id=current_user.id
)

if order is None:
return not_found()3. 多租户系统优先校验租户边界多租户系统中,租户隔离是第一边界。即便用户 ID 不重复,也不能省略租户条件。ORM 全局过滤器、数据库视图、行级安全策略都可以作为补充,但业务层仍应保持清晰的权限判断。4. 统一权限检查函数把权限判断封装为明确的领域方法,比在接口里散写 if 更可靠。例如:if not permission.can_read_invoice(current_user, invoice_id):
return forbidden()这样代码审计时也更容易 grep 和追踪,后续新增接口时可以复用。5. 增加自动化测试用例水平越权很适合写成回归测试。至少应覆盖:用户 A 不能读取用户 B 的私有资源。用户 A 不能修改用户 B 的资源。普通成员不能访问管理员范围资源。租户 A 不能访问租户 B 的任何资源。def test_user_cannot_read_others_order():
order = create_order(owner=user_a)
login_as(user_b)

resp = client.get(f'/api/order/detail?id={order.id}')

assert resp.status_code in (403, 404)审计中的几个注意点不要只看前端路由。前端隐藏按钮不等于后端有权限控制。不要只测详情接口。导出、下载、批量查询更容易遗漏权限判断。注意缓存。如果缓存 key 只包含资源 ID,不包含用户或租户维度,也可能造成数据串读。注意异步任务。导出任务、消息通知、队列消费也需要带权限上下文。注意对象存储直链。文件接口鉴权通过后,如果返回长期有效直链,后续访问可能绕开业务权限控制。风险分级参考场景风险说明读取他人昵称、头像等低敏信息低到中取决于业务公开范围读取订单、地址、合同、发票高涉及个人信息或商业数据修改他人资料、订单、审批高影响数据完整性和业务流程跨租户访问项目、报表、文件高到严重多租户隔离失效,影响面通常较大总结水平越权的本质不是某个参数能不能改,而是系统有没有把“当前用户”和“目标资源”建立可靠的权限关系。审计时应从资源归属、租户边界、角色权限、数据查询条件几个方向同时看。比较稳妥的工程实践是:身份来自服务端上下文,资源查询带归属约束,权限判断统一封装,关键接口有回归测试。这样不仅能修掉单个漏洞,也能减少后续业务迭代中反复出现同类问题。个人经验里,越权问题最值得投入的是“接口清单”和“权限模型”的梳理。只靠临时修某个 ID 参数,通常很快会在导出、下载、批量接口里再次出现。

代码审计与漏洞定位示意图
代码审计、调用链与关键函数定位示意

0篇意见

推荐意见

没有意见。

游客
抱歉,你的帖子内容包括我们不允许的字词。请编辑你的帖子,删除下面高亮的屏蔽字。
添加意见…