严重漏洞:roster_import 使用 $_FILES 的原始扩展名拼 roster_YYYYMMDD_HHMMSS.<ext>
落在 Web 根目录内的 data/uploads/,既无扩展名白名单(可上传 .php),文件名又
可按秒枚举;而 bootstrap 只写了 data/.htaccess,nginx 不读 .htaccess,
生产环境(nginx + PHP-FPM)下可直接远程代码执行。
修复(api.php roster_import 四层防护):
- 扩展名白名单:仅 xlsx / xlsm / csv / txt
- 随机文件名:bin2hex(random_bytes(16)),杜绝枚举
- 解析完成后 @unlink 立即删除,Web 目录下不留存任何上传文件
- 20MB 大小限制 + UPLOAD_ERR 检查
加固(bootstrap.php):data 及 cache/uploads/snapshots 每个目录都写
.htaccess(Deny from all)+ index.html 占位;README 与文档补充 nginx
location ^~ /data/ { deny all; return 404; } 规则。
测试:新增 e2e tools/test-upload-guard.js(11 断言,覆盖 .php/.phtml 被拒、
无残留、合法 csv 导入成功且即删、无可枚举文件名),并入 tests/run-all.sh。
全套 90 项通过。
docs: 新增 VitePress 文档站(18 页)
- 使用指南 12 页:简介/部署/快速上手/系统设置/名单管理/统计口径/试卷设置/
快照趋势异常/导出打印/数据看板/随机抽取/常见问题
- 安全 2 页:安全总览(含本次漏洞复盘)与部署加固清单(P0-P2)
- 开发 4 页:架构与目录/接口一览/SQLite 存储/测试
- 配图迁移至 docs/public/images/;.gitignore 忽略文档构建产物
2.2 KiB
2.2 KiB
快照、趋势与异常检测
这一组功能用于跨时间看成绩,而不是只看当前这一刻。
快照
工具栏 存快照 打开快照面板,填写标签(留空自动用「快照 MM-DD HH:II」)后保存。
一份快照包含:
- 保存时间与标签;
- 试卷 ID 与名称;
- 当时的整体统计(总人数、已提交、平均分、合格率……);
- 每名学生的
学号 / 姓名 / 班级 / 组别 / 成绩 / 最高分 / 考试次数 / 最近提交时间(按学号排序)。
存储位置:data/snapshots/<projectId>/<unix秒>_<md5(标签)>.json。
快照按试卷分目录,所以不同试卷的快照互不干扰。
| 操作 | 位置 |
|---|---|
| 保存当前快照 | 快照弹窗 → 保存当前快照 |
| 刷新列表 | 快照弹窗 → 刷新列表 |
| 删除某份 | 列表每行的删除按钮 |
趋势
趋势 面板把同一试卷的多份快照横向铺开,按学号对齐,可以直接看出每名学生 在几个时间点上的分数变化(上升 / 下降 / 缺考)。
典型用法:
- 月考卷 A 考完 → 存快照「10月月考」;
- 期中卷 B 考完 → 存快照「期中」;
- 打开趋势,逐人对比两次成绩。
异常检测
工具栏 异常 打开异常线索面板。它不是判定作弊,只是把值得人工复核的对象挑出来, 避免在上千行数据里肉眼找。
短时长高分
命中条件:
有时长记录
且 时长 < max(60秒, 平均时长 × 40%)
且 成绩 ≥ 满分提示值 × 85%
「满分提示值」取当前数据中的最高分(
max_score),至少为 1。
高频重考
命中条件:考试次数 ≥ max(4, ceil(平均考试次数 + 3))
即:至少 4 次才算高频,且要明显高于整体平均水平。
雷同成绩
同一分数出现 ≥ 3 人 时列为疑似,按人数降序排列并列出成员。
::: warning 请人工复核 异常检测基于统计特征,误报很正常(例如题目全是选择题时同分很常见)。 请把它当作"值得看一眼"的线索,不要直接作为处理依据。 :::