7.9 KiB
审核专家 B:CHFS-NG 代码安全审计报告
- 审核人:审核专家 B
- 日期:2026-10-02
- 对象:
/root/chfs-ng-research(Go,11 个非测试源文件,约 3900 行) - 生产环境:
chfs-ng.service(root 用户),bin/chfs-ng -file chfs.ini,监听 *:18999 - 验证:
go vet ./...干净;go test ./...全通过 - 结论:有条件通过 —— 代码层面的路径防御与权限检查扎实,但生产部署配置存在阻断项,另有 2 高/3 中需处理
【阻断】生产配置使用弱密码 admin123,明文存储且文件全局可读
【严重度:阻断】
【位置】 /root/chfs-ng-research/chfs.ini(生产配置文件)
【问题描述】
生产 chfs.ini 中 [admin] 的密码为明文 admin123,文件权限为 -rw-r--r--(全局可读)。
服务监听 *:18999(所有网卡),主机防火墙 INPUT 策略为 ACCEPT 且无规则,等同于直接暴露公网。
远程攻击者无需任何前置条件,直接用 admin/admin123 登录即获得全部文件读写删除权限。
【利用/触发条件】
curl -X POST http://<host>:18999/chfs/session -d "user=admin&pwd=admin123" 即得管理员 JWT。
【修复建议】
- 立即更换高强度密码;2.
chmod 600 chfs.ini;3. 配置allowIP 白名单或置于反向代理后; - 考虑主机防火墙限制 18999 入站。
【高】/chfs/exist 未鉴权的文件存在性预言机
【严重度:高】
【位置】 internal/api/server.go:512 func (s *Server) exist
【问题描述】
该端点全程未调用 account(r, s),未做任何权限检查:GET /chfs/exist?file=<path> 返回 201/404
直接泄露任意路径是否存在。所有其他文件端点都有 MaskFor 检查,唯独此处遗漏,
与 README 宣称的"完善的权限体系"不一致。
【利用/触发条件】 当前生产 guest=r,攻击者本就可列目录,实际影响有限;但在 guest 被收紧的部署中, 该端点绕过权限模型造成信息泄漏。
【修复建议】
加上 if !account(r, s).MaskFor(p).HasRead() { 403 },与其他端点保持一致。
【高】缩略图接口无文件大小限制,可被解压炸弹打 OOM
【严重度:高】
【位置】 internal/api/server.go:1164 func (s *Server) thumbnail
【问题描述】
image.Decode(f) 会把整个文件读入内存解码,而此前没有任何文件大小检查。
缓存层虽有 8MB/64 项上限(thumbnail_cache.go),但 decode 本身在缓存之外无限制。
攻击者(生产环境 guest 即有 read 权限)可请求超大文件或特制解压炸弹
(小文件声明超大宽高,如 PNG 头声明 100000x100000 → 数十 GB 内存分配),直接 OOM 杀死服务。
【利用/触发条件】
GET /chfs/thumbnail?file=<大文件或炸弹图片>,无需登录(guest 可读)。
【修复建议】
Stat 后检查 fi.Size(),超过阈值(如 32MB)直接拒绝;或对 decode 加内存上限。
【中】目录 ZIP 下载无总大小/数量限制,CPU 与带宽耗尽
【严重度:中】
【位置】 internal/api/server.go:1290 downloadSelectedZip、1368 downloadDir
【问题描述】
两个接口把目录树流式打包为 zip 直接写回 response,全程无总字节数、无文件数上限
(downloadSelectedZip 仅限制了顶层 path 参数 ≤100 个,但每个可为超大目录;downloadDir 无任何限制)。
zip.Deflate 是 CPU 密集型,多并发请求可打满 CPU 并占满带宽。guest 可读即触发。
【修复建议】 增加可配置的打包上限(总大小、文件数),超限返回 413;或对该接口加并发信号量。
【中】上传无单文件/总量配额,可填满磁盘
【严重度:中】
【位置】 internal/api/server.go:838 upload、937 upload2、savePart
【问题描述】
ParseMultipartForm 仅限制内存部分(64MB/128MB),savePart 的 io.Copy(f, in) 无任何大小上限。
有写权限的用户可上传超大文件填满磁盘。当前生产仅 admin 有写权限,实际影响有限,
但项目主打"动态权限控制"(会给普通用户开写权限),属于设计缺口。
【修复建议】
增加 upload.max-size 配置项,savePart 中用 io.LimitReader 截断并报错。
【中】默认删除进系统回收站且永不清理,磁盘被"已删除"文件占满
【严重度:中】
【位置】 internal/storage/storage.go Trash;internal/config/config.go 默认 FileRemove: 1
【问题描述】
默认 file.remove=1 走 wastebasket.Trash(~/.local/share/Trash),该库无自动清理机制
(只有模式 2 的 MoveToTrash 有 30 天清理)。长期运行的服务删除的文件永久占用磁盘,
df 与用户认知不一致,最终表现为神秘的磁盘占满。
【修复建议】 对模式 1 也加定期清理(如 30 天),或在文档中明确说明并给出清理方式。
【中低】search 无结果数量上限
【严重度:中低】
【位置】 internal/api/server.go:1009 func (s *Server) search
【问题描述】 全树递归遍历,结果集无上限一次性 JSON 返回。超大目录树下内存与响应体膨胀, guest 可直接触发。
【修复建议】
加 limit 参数(默认如 500),超限截断并提示。
【低】ipFilter 不识别 X-Forwarded-For,反代后 allow 白名单失效
【严重度:低】
【位置】 internal/api/server.go:2065 ipFilter / clientIP
【问题描述】
clientIP 只读 r.RemoteAddr。README 建议生产用 HTTPS 反向代理,
一旦接入反代,allow 看到的永远是代理 IP,白名单名存实亡(要么全放行要么全拒绝)。
【修复建议】
支持可信代理 + X-Forwarded-For 解析,或在文档中明确说明该限制。
【低】密码哈希为无盐 SHA256
【严重度:低】
【位置】 internal/auth/auth.go func PasswordHash
【问题描述】
users.json 中存储 hex(sha256(password)),无盐。文件泄漏后彩虹表可直接破解弱密码。
【修复建议】
改用 bcrypt(golang.org/x/crypto/bcrypt),并做旧哈希的兼容迁移。
已验证为安全的关键点(正面结论)
- JWT 密钥管理正确:
main.go在未配置jwt.secret时生成随机 32 字节密钥并以 0600 权限持久化 到users.json.jwt-secret(生产已生效,文件存在)。auth.go中的硬编码常量在 server 路径中不可达。 - 路径穿越防御扎实:
unsafePath(NUL +..段)与storage.real()双层防御;real()对最近存在祖先 做EvalSymlinks,连路径中间的符号链接逃逸都能拦住。多根目录经Multi路由后仍走同一检查。 - 权限检查完整:除
exist外所有 mutating 端点都有MaskFor检查;rename为 create-only;remove/transfer有递归 preflight;WebDAV 有独立webdavAuthFS包装,Move/Copy/RemoveAll 均有checkTree。 - 上传 preflight:
upload/upload2全量校验后再落盘,失败回滚已保存分片;createExclusive防并发覆盖。 - Cookie 安全:JWT cookie 为
HttpOnly;生产 guest 为只读。 - 生产密钥文件权限正确:
users.json.jwt-secret为 0600。
总体结论
有条件通过。
代码本身质量不错:路径穿越防御、权限模型、并发处理都经过认真设计,测试全过、vet 干净。
真正的风险在生产部署:admin123 弱密码 + 公网直连 + 无防火墙是阻断项,必须立即处理。
代码层面需修:exist 鉴权缺失(高)、缩略图 OOM(高),以及 ZIP/上传/回收站/搜索的资源限制(中)。
修完阻断项和高优先级后可认为达到生产安全标准。