Files
ny-exam-platform/docs/guide/install.md
T
nxxy335 a3be81b261 fix(security): 修复上传名单文件可被 URL 直连访问(nginx 下可 getshell)
严重漏洞: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 忽略文档构建产物
2026-09-10 22:42:13 +08:00

134 lines
4.1 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.
# 环境要求与部署
## 环境要求
| 项目 | 要求 | 说明 |
| --- | --- | --- |
| PHP | ≥ 8.0 | 推荐 8.1+ |
| `curl` | 必需 | 调用卷王 API |
| `json` / `mbstring` | 必需 | 数据编解码、中文处理 |
| `openssl` | 推荐 | 登录时对密码做 RSA 加密;缺失时降级为明文提交 |
| `zip` | 可选 | 缺失时用内置解析器读取 xlsx(写入 xlsx 内置实现无需此扩展) |
| `pdo_sqlite` | 推荐 | 启用 SQLite 存储;缺失时自动降级为 JSON 文件存储 |
| 数据库 | 无 | 不需要 MySQL / PostgreSQL |
| Composer | 无 | 没有任何第三方依赖 |
> 用 `php -m` 可以查看已加载的扩展。
## 部署方式
### 方式一:PHP 内置服务器(体验 / 内网临时使用)
```bash
cd ny-exam-platform
php -S 0.0.0.0:8080
```
浏览器打开 <http://localhost:8080>。Windows 下也可双击 `启动平台.bat`。
::: warning 仅用于内网体验
PHP 内置服务器是单进程、无并发能力的开发服务器,**不要**用于生产。
并且它**不解析 `.htaccess`**,`data/` 目录会可被直接访问,请务必参考
[安全加固清单](../security/hardening.md)。
:::
### 方式二:Nginx + PHP-FPM(推荐生产)
假设站点根目录为 `/www/sites/sj.exam.nxxy335.top/index`,那么把仓库内容放在该目录下即可。
```nginx
server {
listen 80;
server_name sj.example.com;
root /www/sites/sj.exam.nxxy335.top/index;
index index.php index.html;
charset utf-8;
client_max_body_size 20m; # 名单文件导入上限,与后端 20MB 校验一致
# ---- 安全:禁止直接访问数据目录(nginx 不读 .htaccess,必须显式配置)----
location ^~ /data/ {
deny all;
return 404;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# 不要暴露隐藏文件
location ~ /\. {
deny all;
return 404;
}
}
```
校验并重载:
```bash
nginx -t && nginx -s reload
```
### 方式三:Apache
仓库会在 `data/` 及其子目录自动生成 `.htaccess`(内容 `Deny from all`),
Apache 下 `data/` 默认即被保护。仍需确认:
- 虚拟主机 `AllowOverride` 不为 `None`(否则 `.htaccess` 被忽略);
- `mod_rewrite` 若启用,确认没有规则把 `/data/` 放行。
### 方式四:宝塔面板
1. 新建站点 → 根目录指向仓库目录;
2. PHP 版本选 8.0+,安装 `fileinfo`、`opcache`(可选);
3. 站点 → 配置文件,在 `server {}` 内加入上面的 `location ^~ /data/` 段;
4. 站点 → 目录权限,确保 `data/` 可写(运行用户通常为 `www`);
5. 若使用反向代理,确认 `/api.php` 的 `action` 参数未被缓存。
## 目录权限
| 路径 | 需要 | 说明 |
| --- | --- | --- |
| `data/` | 可写 | 配置、名单、令牌、快照的根目录 |
| `data/ny.db` | 可写 | SQLite 主库(启用 `pdo_sqlite` 时) |
| `data/uploads/` | 可写 | 导入名单时的临时落盘,解析后立即删除 |
| `data/cache/` | 可写 | 卷王令牌缓存、结果缓存 |
| `data/snapshots/` | 可写 | 成绩快照 |
```bash
chown -R www:www data
chmod -R 755 data
```
Windows 下给 `IIS_IUSRS` 或站点应用程序池身份赋予 `data` 的「修改」权限。
## 升级
仓库是纯文件部署,升级即覆盖文件:
```bash
git pull
```
覆盖后 `data/` 保留(其中存放配置与名单,并且在 `.gitignore` 中),
程序会在首次访问时自动完成目录初始化与数据迁移。
::: tip 关于 SQLite 迁移
首次在有 `pdo_sqlite` 的环境运行时,`data/config.json` 与 `data/roster.json`
会被自动迁移进 `data/ny.db`,之后以数据库为准,文件作为备份继续双写。
迁移是幂等的,重复执行不会覆盖已入库的数据。
:::
## 下一步
- [快速上手](./quickstart.md)
- [安全加固清单](../security/hardening.md)