Files
ny-exam-platform/docs/security/index.md
T
nxxy335 25a2e8a42c 文档部署到 /ny-exam-platform 子路径 + 移除快照/趋势/异常 + 打印与 UI 全面完善
文档站(#88)
- base 默认改为 /ny-exam-platform/,可用 DOCS_BASE=/ 覆盖本地预览
- 新增 DOCS_OUT 环境变量,避免构建时清空输出目录失败导致产物半截
- 新增 tools/docs-preview.js:按子路径重写路由的本地预览服务
- 删除 docs/guide/snapshot.md,同步清理全部相关文档

移除存快照 / 趋势 / 异常(#89)
- 后端:删除 api.php 的 snapshot_save/list/delete/trend 四个 action 与
  $WRITE_ACTIONS 中的同名条目;删除 lib/Snapshot.php 与自动加载注册;
  DataService 移除 detectAnomalies() 及其调用
- 前端:移除三个按钮与三个弹窗、相关 JS 处理与 state 字段
- 测试:移除对应用例,补充 5 条名单导入列识别断言(17+9+18+32+29+11 全过)

打印完善(#90)
- 改用隐藏 iframe 输出,规避弹窗拦截;@page A4 支持纵向/横向
- 表头每页重复、行内不跨页断开、全表连续名次、可选「是否合格」列
- 分组小计(人数/平均/最高/最低/合格人数与合格率)、可附缺考名单另起一页
- 末尾签字栏;缺考名单打印改为同一套输出

UI / 交互 / Bug 修复(#91)
- dashboard.php:补「← 主系统 / 退出」入口(此前全屏后无法离开);
  修复成员下钻 onclick 里 JSON.stringify 双引号提前闭合属性导致点击报错,
  改为 data-sid 事件委托;补 Esc 关弹窗;后台标签页暂停轮询、切回自动补拉;
  刷新中断原因写入 title;数值字段统一兜底,避免 NaN / undefined%
- draw.php / draw.js:结果表新增「登陆账号」列(导出前可核对,缺失显示 学号 ⚠);
  弹窗只关最上层且支持 Esc;「全部」改为跟随候选池,手工改人数即解除;
  候选池不足时给出明确提示;自定义比例框隐藏时清空,避免旧权重被误读
- app.js / dashboard.php:成绩着色改为按「占本卷满分的百分比」判断,
  修复满分不是 100 时整列误标红
- style.css:数值列右对齐;新增 subbar、draw-stage-sub;
  看板顶栏窄屏下拉与按钮换行,消除横向滚动
2026-09-11 16:23:08 +08:00

81 lines
3.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 安全总览
平台内建了多层防护,但**部署方式决定了大部分安全性**——请务必同时阅读
[部署加固清单](./hardening.md)。
## 认证
- 采用 PHP Session 鉴权,未登录访问任意页面会被重定向到 `login.php`;
- 会话 Cookie 为 `NYSESS`,`HttpOnly` + `SameSite=Lax`;
- **登录凭据就是卷王后台账密**:平台用它向卷王换取令牌,不维护独立账号体系;
- 卷王令牌缓存在 `data/cache/token.json`;令牌失效时平台会自动重新登录;
若卷王侧也认证失败,前端会先调用 `logout.php` 清会话再跳转登录页,避免出现跳转死循环。
## 登录限流
连续 **5 次**登录失败 → 锁定 **10 分钟**。状态记录在 `data/login_fail.json`。
应急解锁:删除 `data/login_fail.json`。
## CSRF 防护
- 会话绑定 CSRF 令牌:`ny_csrf_token()` 生成,`ny_csrf_ok()` 校验;
- 令牌通过 `config_get` 接口下发给前端,前端在写请求的 JSON 体 `_csrf` 字段与
`X-CSRF-Token` 头中自动携带;
- 后端对写操作维护**白名单** `$WRITE_ACTIONS`,命中白名单的请求一律校验:
`config_save` / `roster_save` / `roster_import` / `roster_clear` /
`unmatched_map` / `project_config_save`。
## 文件上传
::: danger 历史风险(已修复)
早期版本中上传文件按 `roster_YYYYMMDD_HHMMSS.<原始扩展名>` 保存在 `data/uploads/`,
存在两个问题:
1. **扩展名未校验**——上传 `.php` 会被存成 `roster_20260910_180400.php`;
2. **文件名可枚举**——按时间 1 秒粒度即可遍历。
在 nginx + PHP-FPM 下(`.htaccess` 无效),这构成**任意文件上传 → 远程代码执行(RCE)**。
:::
当前实现(四层防护):
| 层 | 措施 |
| --- | --- |
| 1 | **扩展名白名单**:仅允许 `.xlsx` / `.xlsm` / `.csv` / `.txt`,其余直接拒绝 |
| 2 | **随机文件名**:`bin2hex(random_bytes(16))`,无法枚举 |
| 3 | **解析后立即删除**:`@unlink($dest)`,Web 目录下不留存任何上传文件 |
| 4 | **大小限制**:单个文件 ≤ 20 MB;同时建议 nginx 配置 `client_max_body_size 20m` |
另外,上传接口属于写操作,同样受 CSRF 保护。
## XSS / 注入
- 所有输出到 HTML 的值均经过 `htmlspecialchars()`;
- 导出 XLSX 时单元格统一写入**共享字符串**并施加 `numFmtId=49` 文本样式,
天然防止 CSV / Excel 公式注入,也避免长学号被转成科学计数法;
- 数据访问全部使用 PDO 预处理语句 / 参数绑定。
## 目录保护
程序启动时会在 `data/`、`data/cache/`、`data/uploads/`
自动生成 `.htaccess`(`Deny from all`)与 `index.html` 占位。
> **nginx 不读取 `.htaccess`**,必须显式配置。见 [部署加固清单](./hardening.md)。
## 建议的额外措施
| 措施 | 说明 |
| --- | --- |
| 全站 HTTPS | 登录密码经 RSA 加密,但仍建议全站 TLS |
| IP 白名单 | 内网使用时在 nginx 层限制来源 IP |
| 隐藏版本号 | 关闭 `expose_php`、隐藏 nginx 版本号 |
| 定期备份 | 备份 `data/`(配置、名单、每卷配置、SQLite 库) |
| 最小权限 | PHP-FPM 进程只对 `data/` 可写,其它目录只读 |
| 改默认密码 | 卷王后台默认 `admin/123456` 务必修改 |
## 相关
- [部署加固清单](./hardening.md)
- [环境要求与部署](../guide/install.md)