问题概述
排查 #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 层签名变动较大,影响面广,可作为长期方向。
讨论问题
- 是否要把"组织者(
LinkedOrgID 对应的 Org Owner)"也纳入权限范围?目前 Web 端报名流程考虑了 Org Owner(org.IsOwnedBy),但管理页 ManageHackathon 没有。需要统一口径。
- 选哪种修复方案?建议 A(单点修复 + 与 Web 一致)作为短期止血,方案 B/C 是否值得作为下一步重构。
- 是否需要补充集成测试用例覆盖"非 owner 用户访问写端点 → 403"→ 这块需要补一份集成测试规范。
建议优先级
🔴 高危条目(4 个 hackathon-level 端点)建议两周内修复,中危的子资源端点可以分批跟进。本 issue 暂不直接修复,待讨论后再开实施 PR。
问题概述
排查 #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)DELETE /hackforger/hackathons/{id}DeleteHackathon(routers/api/v1/hackforger/hackathon.go:257)POST /hackforger/hackathons/{id}/publishPublishHackathon(routers/api/v1/hackforger/hackathon.go:303)POST /hackforger/hackathons/{id}/cancelCancelHackathon(routers/api/v1/hackforger/hackathon.go:360)🟠 中危:能修改活动子资源
POST /hackforger/hackathons/{id}/tracksCreateTrackPUT /hackforger/hackathons/{id}/tracks/{tid}UpdateTrackDELETE /hackforger/hackathons/{id}/tracks/{tid}DeleteTrackPOST /hackforger/hackathons/{id}/phasesAddPhaseAPIPUT /hackforger/hackathons/{id}/phases/{pid}UpdatePhaseAPIDELETE /hackforger/hackathons/{id}/phases/{pid}DeletePhaseAPIPOST /hackforger/hackathons/{id}/phases:reorderReorderPhasesAPIPOST /hackforger/hackathons/{id}/judgesAddJudgeDELETE /hackforger/hackathons/{id}/judges/{uid}RemoveJudgePOST /hackforger/hackathons/{id}/criteriaAddCriteriaAPIPUT /hackforger/hackathons/{id}/criteria/{cid}UpdateCriteriaAPIDELETE /hackforger/hackathons/{id}/criteria/{cid}DeleteCriteriaAPIPUT /hackforger/hackathons/{id}/tracks/{tid}/criteria-overrideSetTrackCriteriaOverrideAPIPOST /hackforger/hackathons/{id}/finalizeFinalizePreviewAPI/FinalizeConfirmAPI✅ 已正确加权限的对照端点
POST /hackforger/hackathons(CreateHackathon) — 检查了!ctx.Doer.IsAdminPOST /hackforger/hackathons/{id}/submissions/{sid}/scores(SubmitScore) — 服务层通过IsErrNotJudge拦截DELETE /hackforger/hackathons/{id}/submissions/{sid}(DeleteSubmission) — 检查了 submitter 或 hackathon owner对照:Web UI 端是有权限保护的
Web 管理页面入口已正确加了权限守卫,模式很统一:
也就是说:通过浏览器无法越权操作,但通过 API(带任意普通用户的 token)可以。 攻击面主要在第三方集成场景或恶意脚本。
复现方式
建议修复方向(待讨论)
方案 A:每个 handler 内显式权限检查(与 Web 端一致)
最贴近现有代码风格,单点修复明确:
工作量:约 15–20 个 handler 各加 5 行代码。
方案 B:抽公共中间件 / helper
reqHackathonOwnerOrAdmin()包装,在routers/api/v1/api.go路由注册时统一挂载。变更面更集中,但需要路由层能拿到活动 ID 做查库。方案 C:把权限检查下沉到 service 层
参考
SubmitScores的模式,service 函数接收doer,内部判断。service 层签名变动较大,影响面广,可作为长期方向。讨论问题
LinkedOrgID对应的 Org Owner)"也纳入权限范围?目前 Web 端报名流程考虑了 Org Owner(org.IsOwnedBy),但管理页ManageHackathon没有。需要统一口径。建议优先级
🔴 高危条目(4 个 hackathon-level 端点)建议两周内修复,中危的子资源端点可以分批跟进。本 issue 暂不直接修复,待讨论后再开实施 PR。