日期: 2026-09-09
状态: 待实现
范围: backend、web-admin、mobile-wx
纪检业务附件含隐私。当前 MinIO bucket ajyy 若整桶匿名可读,知链即可下载,不满足安全要求。
目标:
| 对象 | 访问规则 |
|---|---|
二维码配置图片(qrcode/*) |
允许匿名可读(公示张贴、移动端展示) |
| 在线反馈 / 建言献策附件 | 禁止匿名读 |
| web-admin | 必须登录;经后端 JWT 代理拉流查看/下载 |
| mobile-wx | 仅上传界面用本地临时文件预览;不依赖远程可读 |
url / objectKey 形态,不迁表| 方案 | 说明 | 结论 |
|---|---|---|
| 1(采用) | qrcode/* 公开;其余私有;管理端 JWT 代理;移动端本地预览 |
采用 |
| 2 | 整桶私有,二维码也走公开代理 | 二维码多一跳,无必要 |
| 3 | 管理端短时预签名 | 与「代理 + 审计」决策不符 |
s3:GetObject,Resource 限定:arn:aws:s3:::ajyy/qrcode/*ajyy/* 匿名可读策略_ensure_bucket 时写入上述前缀策略(幂等覆盖)| 场景 | folder | 接口 |
|---|---|---|
| 二维码图片 | 固定 qrcode |
仅管理端 POST /api/web/admin/attachment/upload |
| 在线反馈附件 | 固定 feedback |
公开 POST /api/public/attachment/upload |
| 建言献策附件 | 固定 suggestion |
公开上传同上 |
feedback、suggestion;拒绝其它 folder(含 qrcode)qrcode;管理端若上传其它业务附件,不得依赖匿名读attachments JSON / image_url 仍存服务端返回的 url(完整 MinIO URL 或等价 object 地址)服务端把附件当静态对象存储,永不在服务端执行上传文件(不落地为可执行权限、不调用、不解压执行)。
在现有 upload_validate(扩展名白名单 + 20MB)基础上本期加固:
| 措施 | 要求 |
|---|---|
| 扩展名白名单 | 维持并明确禁止可执行与脚本类:如 .exe .dll .bat .cmd .ps1 .msi .scr .js .html .htm .svg .sh 等(不在白名单即拒) |
| 魔数校验 | 对图片等可校验类型:文件头 magic 须与扩展名一致(如 PNG/JPEG/GIF/WEBP/PDF);不一致则 400,防止「改后缀的 exe」 |
| 存储权限(local) | 写入文件权限不含执行位(如 0o644) |
| 代理下载响应头 | X-Content-Type-Options: nosniff;Content-Type 仅按白名单安全类型推断;非图片默认 Content-Disposition: attachment(避免浏览器当页面执行);禁止下发 application/x-msdownload 等可执行 MIME |
| 二维码上传 | 仅允许图片扩展名(管理端 qrcode folder) |
本期不做: 杀毒引擎扫描、沙箱动态检测、zip 内嵌木马深度扫描(.zip 仍允许作为证据压缩包;管理员本机打开需自行谨慎——写入验收说明即可)。
GET /api/web/admin/attachment/file?url=<库内存储的附件URL>
get_current_user(任一已登录后台用户)。页面入口仍由菜单 feedback / suggestion 控制;本期不额外叠加 require_menu,避免比详情接口更严导致「能看详情却打不开附件」url 解析 objectKey,必须属于当前 MINIO_BUCKET(或 local /uploads/ 前缀);拒绝外链、空参数、路径穿越。二维码图由前端直链访问,不依赖本代理StreamingResponse;安全 Content-Type + nosniff;图片可 inline,其它类型优先 attachmentLocal 存储模式:代理从 LOCAL_STORAGE_DIR 读文件,同样需登录。
<a href>;点击后用带 Authorization 的请求取 blob,再 URL.createObjectURL 打开/预览imageUrl(qrcode/* 公开),不走代理URL.createObjectURL(file)tempFilePathpreviewUrl,禁止用远程 MinIO url 作为 <image :src>revokeObjectURL{ id, url, fileName, size }{ previewUrl, url, fileName, size }
previewUrlattachmentUrls → url(服务端地址)涉及:mobile-wx/utils/upload.js、pages/feedback/index.vue、pages/suggest/index.vue
| 场景 | 期望 |
|---|---|
匿名 GET …/feedback/… 或 …/suggestion/… |
403 |
匿名 GET …/qrcode/… |
200 |
| 未登录调管理端代理 | 401 |
| 代理非法 url / 外链 | 400 |
公开上传 .exe / 假图片(错误魔数) |
400 |
| 移动端上传页 | 本地预览正常;远程私有 URL 匿名不可读 |
| 管理端详情 | 登录后可打开反馈/建言附件 |
| 二维码配置预览 | 匿名直链仍可显示 |
测试:
| 层 | 文件/模块(预期) |
|---|---|
| backend | app/core/storage.py(前缀策略、读对象);app/core/upload_validate.py(魔数);app/api/admin.py(代理接口);app/api/public.py(folder 白名单);相关 tests |
| web-admin | views/feedback/index.vue;views/suggestion/index.vue;api/admin.ts(代理打开辅助) |
| mobile-wx | utils/upload.js;反馈/建言页预览字段 |
| 运维 | 对已部署环境立即覆盖 bucket policy(启动时或一次性脚本) |
.zip 时,压缩包内可能含恶意文件——管理员本机解压需谨慎;本期不深度扫描