Skip to content

[security] HackForger API 写端点普遍缺少 Owner/Admin 权限检查 #169

Description

@FatNine

问题概述

排查 #167 的过程中发现,HackForger 一系列 API 写端点(PUT / POST / DELETE)只校验登录 token(reqToken()),没有 Owner / Admin 权限检查。任何已登录用户都能修改、发布、取消、删除任意活动及其子资源(赛道、阶段、评委、评审标准等)。这是一个系统性的水平越权(IDOR-style)漏洞,不只是 #167 评论里发现的删除接口。

已确认受影响的端点(按风险分级)

🔴 高危:能修改/破坏已发布活动数据

端点 处理函数 现状
PUT /hackforger/hackathons/{id} UpdateHackathon (routers/api/v1/hackforger/hackathon.go:208) 无任何权限检查,可改任意活动的 Name / Description / MaxTeamSize / PrizeSummary
DELETE /hackforger/hackathons/{id} DeleteHackathon (routers/api/v1/hackforger/hackathon.go:257) 无权限检查(被 model 层 Draft 状态锁兜底,但守卫位置错位)
POST /hackforger/hackathons/{id}/publish PublishHackathon (routers/api/v1/hackforger/hackathon.go:303) 无权限检查
POST /hackforger/hackathons/{id}/cancel CancelHackathon (routers/api/v1/hackforger/hackathon.go:360) 无权限检查,任何登录用户可取消任意活动

🟠 中危:能修改活动子资源

端点 处理函数
POST /hackforger/hackathons/{id}/tracks CreateTrack
PUT /hackforger/hackathons/{id}/tracks/{tid} UpdateTrack
DELETE /hackforger/hackathons/{id}/tracks/{tid} DeleteTrack
POST /hackforger/hackathons/{id}/phases AddPhaseAPI
PUT /hackforger/hackathons/{id}/phases/{pid} UpdatePhaseAPI
DELETE /hackforger/hackathons/{id}/phases/{pid} DeletePhaseAPI
POST /hackforger/hackathons/{id}/phases:reorder ReorderPhasesAPI
POST /hackforger/hackathons/{id}/judges AddJudge
DELETE /hackforger/hackathons/{id}/judges/{uid} RemoveJudge
POST /hackforger/hackathons/{id}/criteria AddCriteriaAPI
PUT /hackforger/hackathons/{id}/criteria/{cid} UpdateCriteriaAPI
DELETE /hackforger/hackathons/{id}/criteria/{cid} DeleteCriteriaAPI
PUT /hackforger/hackathons/{id}/tracks/{tid}/criteria-override SetTrackCriteriaOverrideAPI
POST /hackforger/hackathons/{id}/finalize FinalizePreviewAPI / FinalizeConfirmAPI

✅ 已正确加权限的对照端点

  • POST /hackforger/hackathons (CreateHackathon) — 检查了 !ctx.Doer.IsAdmin
  • POST /hackforger/hackathons/{id}/submissions/{sid}/scores (SubmitScore) — 服务层通过 IsErrNotJudge 拦截
  • DELETE /hackforger/hackathons/{id}/submissions/{sid} (DeleteSubmission) — 检查了 submitter 或 hackathon owner

对照:Web UI 端是有权限保护的

Web 管理页面入口已正确加了权限守卫,模式很统一:

// routers/web/hackforger/hackathon.go:605 (ManageHackathon)
if ctx.Doer == nil || (ctx.Doer.ID != h.OwnerID && !ctx.Doer.IsAdmin) {
    ctx.Flash.Error(ctx.Tr("hackforger.hackathon.error.no_permission"))
    ctx.Redirect("/hackathon/" + h.Slug)
    return
}

也就是说:通过浏览器无法越权操作,但通过 API(带任意普通用户的 token)可以。 攻击面主要在第三方集成场景或恶意脚本。

复现方式

# 用户 A 创建活动 → OwnerID = A
# 用户 B(任意其他登录用户)执行以下命令:
curl -X PUT \
  -H "Authorization: token <B 的 token>" \
  -H "Content-Type: application/json" \
  -d '{"name": "被恶意改名"}' \
  https://<host>/api/v1/hackforger/hackathons/<A 的活动 ID>

# → 200 OK,活动名被改

建议修复方向(待讨论)

方案 A:每个 handler 内显式权限检查(与 Web 端一致)

最贴近现有代码风格,单点修复明确:

h, err := hackforger_model.GetHackathonByID(ctx, ctx.ParamsInt64(":id"))
// ...错误处理...
if h.OwnerID != ctx.Doer.ID && !ctx.Doer.IsAdmin {
    ctx.Error(http.StatusForbidden, "Forbidden",
        "only the hackathon owner or a site admin can perform this action")
    return
}

工作量:约 15–20 个 handler 各加 5 行代码。

方案 B:抽公共中间件 / helper

reqHackathonOwnerOrAdmin() 包装,在 routers/api/v1/api.go 路由注册时统一挂载。变更面更集中,但需要路由层能拿到活动 ID 做查库。

方案 C:把权限检查下沉到 service 层

参考 SubmitScores 的模式,service 函数接收 doer,内部判断。service 层签名变动较大,影响面广,可作为长期方向。

讨论问题

  1. 是否要把"组织者(LinkedOrgID 对应的 Org Owner)"也纳入权限范围?目前 Web 端报名流程考虑了 Org Owner(org.IsOwnedBy),但管理页 ManageHackathon 没有。需要统一口径。
  2. 选哪种修复方案?建议 A(单点修复 + 与 Web 一致)作为短期止血,方案 B/C 是否值得作为下一步重构。
  3. 是否需要补充集成测试用例覆盖"非 owner 用户访问写端点 → 403"→ 这块需要补一份集成测试规范。

建议优先级

🔴 高危条目(4 个 hackathon-level 端点)建议两周内修复,中危的子资源端点可以分批跟进。本 issue 暂不直接修复,待讨论后再开实施 PR。

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions