应直接用multipart.FileHeader.Open()获取io.Reader传给clamd的STREAM命令,避免文件落地;需校验文件名、Content-Type和magic number;三段超时(HTTP、连接、扫描)须独立设置;FOUND或ERROR必须返回403或503而非200。

上传后立即调用clamd扫描,别等文件落地再读取
ClamAV 的 clamd 守护进程支持 Stream 模式(通过 Unix socket 或 TCP),比先保存文件再执行 clamscan 命令快得多,也更安全——避免恶意文件写入磁盘。Go 服务收到 *multipart.FileHeader 后,应直接用 header.Open() 获取 io.Reader,传给 clamd 的 STREAM command,不经过 os.Create 或临时目录。
常见错误是:把文件 Save() 到 /tmp 再调用 exec.Command("clamscan", path),这会引入竞态(文件可能被篡改)、权限问题(/tmp 可能被挂载 noexec)、以及额外 I/O 开销。
- 确保
clamd.conf中启用了StreamSaveToDisk no(默认开启,但需确认) - 使用
net.Dial("unix", "/var/run/clamd.scan/clamd.sock")或net.Dial("tcp", "127.0.0.1:3310")连接,不要 fork 子进程 - STREAM 协议要求发送
"STREAM\n"后紧接原始字节流,结尾无需 EOF;响应格式为"stream: OK" / "stream: /dev/stdin: Win.Trojan.XYZ-123 FOUND"
处理 multipart/form-data 时别忽略 filename 和 content-type 校验
攻击者常伪造 filename="..%2f/etc/passwd" 或上传 content-type="text/html" 却塞入 PE 文件。Golang 的 ParseMultipartForm 不校验这些字段,必须手动拦截。
关键点不是“过滤危险字符”,而是做白名单式约束:
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 检查
fileHeader.Filename是否为空、是否含路径分隔符(/、\、%2f等 URL 编码变体),建议直接拒绝非 ASCII 字母数字 +_ . -的文件名 - 对
fileHeader.Header.Get("Content-Type")做 MIME 类型预判(如image/jpeg→ 允许;application/x-msdownload→ 拒绝或强检),但注意:Content-Type 可任意伪造,仅作辅助 - 真正可靠的检测是读取前 512 字节做 magic number 匹配(用
net/http.DetectContentType或filetype库),和 clamd 扫描结果交叉验证
超时控制必须覆盖三段:HTTP 上传、clamd 通信、clamd 扫描本身
一个大文件上传卡住、clamd socket 连接挂起、或病毒特征库过期导致扫描阻塞——这三类超时必须分开设限,否则整个 HTTP handler 会 hang 住 goroutine。
- HTTP 层:在
http.Server配置ReadTimeout和ReadHeaderTimeout(例如 30s),防止慢速上传耗尽连接 - clamd 连接层:用
net.Dialer{Timeout: 5 * time.Second},别依赖全局http.DefaultClient - clamd 扫描层:STREAM 协议无内置超时,需用
context.WithTimeout包裹整个读写流程(建议 ≤ 90s),并在写入流时用带超时的io.CopyN或分块Write
漏掉任一环节,都可能导致服务堆积大量僵尸 goroutine,最终 OOM。
扫描结果与 HTTP 状态码要严格对应,别用 200 掩盖 FOUND
很多实现把 clamd 返回的 "FOUND" 当作日志打掉,然后仍返回 200 OK 和成功 JSON,这会让前端误以为上传成功。实际应按语义返回标准状态码:
-
200 OK:仅当 clamd 明确返回"OK"或"Empty file" -
400 Bad Request:文件为空、filename 非法、magic 不匹配等客户端问题 -
403 Forbidden:clamd 返回"FOUND",且威胁等级高(如 TROJAN、WORM) -
503 Service Unavailable:clamd 连接失败、超时、或返回"ERROR"(如内存不足)
特别注意:clamd 对某些加壳/混淆样本可能返回 "ERROR" 而非 "FOUND",这时不能当作“无毒”放行,必须视为不可信并拒绝。

















