fix(阶段4): 两份独立安全评审的 Critical/Important 全部修掉
## 权限边界评审(后台 86 个 handler) 守卫本身一个没漏,问题全在对象级归属校验: - C1 跨题单删奖章:先按 (id, problemsetId) 校验奖章归属再删, 否则拿自己的题单 id + 别人的奖章 id 就能把别人的 user_badge 删掉 - C2 make-public 无归属校验:补 canEdit,越权者拿不到题面 - I1 两个分析端点被 `/problems/:id` 遮蔽 —— Hono 按注册顺序匹配, 不是静态优先。挪到 `/problem-analytics/*` - I2 from-public 只校验目标比赛归属:源题也必须是公开题库题 - I3 克隆比赛回传原比赛明文密码:克隆一律 password: null - I4 upload-image 守卫比旧后端严,教师写题面会 403:收回 requireAdmin 两个互不可见的教师账号实跑复验,六条全部拦住。 ## SQL 判题沙箱评审 - I-1 查询题只读被一句 `PRAGMA query_only=0` 关掉,实测 DML 拿到 AC。 query_only 自己就是个 PRAGMA,旧实现靠 authorizer 把 SQLITE_PRAGMA 一律拒了才没这个洞。现在 runStudent 逐语句拦 PRAGMA(用 sqlite3_normalized_sql 判关键字,注释和大小写由 SQLite 抹平), 并在每条语句前重放 query_only 和 max_page_count 兜底。 顺带修掉 M-1 里 max_page_count 学生可自行调大的部分。 - I-2 单条语句进了 step() 就打断不了,只能等父进程 SIGKILL, 而兜底时限是整个作业一口价 25s —— 1s 限的题要 26s 才判 TLE, 判题池只有 2 个槽,几发死循环就能把所有人堵住。 改成分阶段:子进程用 stderr 报 prepare/student/display, 父进程边读边换表,一进学生 SQL 就把兜底收到「题目时限 + 2s」。 实测 26s → 3.06s。 归因也跟着修了:卡在受信脚本(出题人的初始化脚本、标准答案) 现在报 SYSTEM_ERROR,不再当成学生超时甩 TLE。 engine.ts 头部那张防护对照表按实测重写 —— 原来那版把 query_only 写成等价于 authorizer 白名单,是不成立的。另记一笔:stock sql.js 的 wasm 没导出 progress_handler / interrupt / set_authorizer / limit, 想要得自己编,别再去翻了。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e3faa689e7
commit
0f94e7ecbd
11 files changed
+859
-67
No files matched your search
@@ -189,7 +189,11 @@ adminContestRoutes.post("/contests/:id/clone", requireTeacher, async (c) => {
|
||||
title: original.contest.title,
|
||||
description: original.contest.description,
|
||||
tag: original.contest.tag,
|
||||
password: original.contest.password,
|
||||
// 不复制原比赛的密码。两个理由:一是克隆出来是一场新比赛、时间也是新的,
|
||||
// 沿用旧密码意味着拿着旧密码的学生直接能进;二是本接口不校验归属
|
||||
// (旧后端也不校验,教师可以拿别人的比赛做模板),复制过来就等于把别人的
|
||||
// 比赛密码原样回传给调用者。克隆者自己重新设一个。
|
||||
password: null,
|
||||
// 克隆出来的一律不可见:时间是拍脑袋定的 10 分钟后,直接开放会让学生看到一场没准备好的赛
|
||||
visible: false,
|
||||
allowedIpRanges: original.contest.allowedIpRanges,
|
||||
|
||||
Reference in new issue
Block a user