Python 文件上传的病毒扫描

1次阅读

必须校验文件后缀、mime类型及内容:用python-magic检测前1024字节,仅允白名单mime;clamav须用instream内存扫描并处理连接异常,禁跳过小文件扫描。

Python 文件上传的病毒扫描

上传前必须校验文件后缀和 MIME 类型

光靠 filename 后缀判断类型极不可靠,攻击者随便改个 .jpg 后缀就能传入恶意可执行文件。浏览器发来的 Content-Type 也完全不可信,能被任意篡改。

真正有效的做法是读取文件前几百字节,用 python-magic 做实际内容检测

import magic<br>mime = magic.from_buffer(file_bytes, mime=True)

只允许白名单 MIME 类型,比如 image/jpegapplication/pdf,拒绝 application/x-executabletext/html(HTML 文件可能含 xss 或下载诱导脚本)。

  • 别用 file.content_type —— 它只是 http 请求头里的字符串,毫无约束力
  • magic.from_file() 不能用于临时上传流,必须用 from_buffer() 配合 read(1024)
  • 某些 PDF 病毒会伪装成 application/pdf,但真实扫描需更深层分析,这一步只是基础过滤

调用 ClamAV 扫描时别直接传文件路径

用户上传的文件若直接落盘再传给 clamdscanclamscan,存在竞态条件:文件刚写完、还没扫完,就被另一个进程或脚本读取执行了。更糟的是,如果路径可控,还可能触发路径遍历(比如传 ../../../.ssh/id_rsa)。

立即学习Python免费学习笔记(深入)”;

正确姿势是走内存或 socket 通信,避免落地:

import clamd<br>cd = clamd.ClamdAgnostic()<br>result = cd.instream(file_bytes)
  • instream() 把二进制内容直接发给 clamd 守护进程,不写磁盘,无路径风险
  • 确保 clamd 配置里 StreamMaxLength 足够大(默认 25M),否则大文件会返回 INSTREAM Error: Stream: File size limit exceeded
  • 如果用 clamscan 命令行,必须配合 tempfile.NamedTemporaryFile(delete=False) + 扫描后立即 os.unlink(),但依然不如 instream 干净

ClamAV 返回结果要区分 “未发现” 和 “扫描失败”

很多人只检查 result['stream'] == 'OK' 就放行,但 ClamAV 的响应结构其实有三种关键状态:

  • 'stream': 'OK' → 无病毒(安全)
  • 'stream': 'FOUND' → 发现病毒,result['stream'] 值是病毒名,如 Win.Trojan.Keylogger-12345
  • 根本没返回 'stream' 键,或抛出 clamd.ConnectionError / socket.timeout → 扫描异常,不能当“干净”处理

漏掉第三种情况等于留后门:ClamAV 服务挂了、超时了、配置错了,上传就自动通过。生产环境必须显式拦截这类异常,并记录日志,例如:

try:<br>    result = cd.instream(file_bytes)<br>except (clamd.ConnectionError, socket.timeout):<br>    raise RuntimeError("Virus scan unavailable")

小文件跳过扫描?别省这点 CPU

有人觉得 1KB 的文本文件不可能带病毒,就加逻辑跳过扫描。错。很多 Office 文档宏病毒、LNK 文件、恶意 PDF 元数据都只有几 KB,且 ClamAV 对小文件的 instream 调用耗时通常在毫秒级。

真正该优化的是并发和超时,不是按大小跳过:

  • ClamAV 默认单线程处理 instream,高并发下会排队,建议启用 clamdMaxThreads 配置
  • instream()timeout=30 参数,避免卡死(ClamAV 处理畸形 PDF 有时会 hang 住)
  • 如果真要限流,用请求队列或令牌桶,而不是基于文件大小做信任降级

病毒不挑体积,只挑疏忽。只要走上传流程,就得走全链路校验 —— 后缀、MIME、内容扫描,缺一不可。

text=ZqhQzanResources