仅设MaxRequestBodySize不安全,它只限总请求体大小,不校验文件类型、内容或后缀;攻击者可用shell.php.jpg等伪装文件绕过,必须手动解析multipart、读取Magic Bytes、白名单校验并重命名存储至Web根目录外。

只靠 MaxRequestBodySize 不行,它不校验文件内容
很多人以为给 Echo 设置 e.MaxRequestBodySize = 10 * 1024 * 1024 就安全了,其实这只是限制整个请求体大小,对 multipart/form-data 中的文件本身没做任何类型或内容检查。攻击者仍可上传 shell.php.jpg 或 webshell.phtml,只要总大小不超限,就能绕过。
根本问题在于:这个配置只是 HTTP 层的“流量闸门”,不是文件校验逻辑。它甚至无法区分哪段是表单字段、哪段是文件数据。
- 若中间件提前调用
r.ParseMultipartForm()或io.ReadAll(r.Body),MaxRequestBodySize可能完全失效 - 它对
Content-Type、文件后缀、Magic Bytes 都无感知 - 上传路径若在 Web 根目录下且服务器允许执行 PHP,恶意文件一落地就能被直接访问执行
必须手动解析 multipart 并校验文件头和扩展名
Echo 不提供开箱即用的文件内容校验,你得自己用 r.MultipartReader() 或 r.FormFile() 拿到文件句柄,再读取前几百字节判断真实类型。
典型做法是:先用白名单过滤扩展名(如 ["jpg", "png", "pdf"]),再用 file.Header 或 io.ReadFull() 读取 Magic Bytes,比对是否匹配图像/文档签名。PHP 里常用 getimagesize(),Go 里可用 http.DetectContentType() 或更精确的 github.com/h2non/filetype 库。
- 不要只依赖
file.Filename的后缀——攻击者可传shell.php.png - 不要只信
header.Get("Content-Type")——Burp 可随意篡改 - 必须打开文件句柄并读取原始字节,
filetype.Match()比DetectContentType()更可靠
重命名 + 存储隔离是防止执行的关键一步
即使文件内容合法,如果上传目录在 Web 可访问路径下(如 /var/www/html/uploads/),且服务器配置不当(如 Apache 启用 mod_php 或 Nginx 错误配置 fastcgi_pass),攻击者仍可能通过解析漏洞触发执行。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
最稳妥的做法是:把文件存到 Web 根目录之外(如 /data/uploads/),并通过一个受控的 Go handler 提供下载服务(如 http.ServeFile 或流式响应),确保所有访问都经过代码逻辑,不依赖 Web 服务器的文件直出。
- 用
uuid.NewString() + ext重命名,避免覆盖和预测路径 - 存储路径必须绝对路径,且
os.Stat()确认父目录不可执行(如无.htaccess、无php_flag) - 如果必须放 Web 目录下,需额外配置 Web 服务器禁止该目录下脚本执行(Nginx 的
location ~* \.php$ { deny all; })
别漏掉 ParseMultipartForm 的 maxMemory 参数
Echo 默认不调用 r.ParseMultipartForm(),但一旦你显式调用,就必须设好 maxMemory。这个参数控制内存中缓存的表单数据上限,超出部分会自动落盘——而落盘路径默认是系统临时目录,不受你控制,可能被滥用。
正确姿势是:在 handler 开头就调 r.ParseMultipartForm(8 (比如 8MB 内存缓存),再配合 <code>http.MaxBytesReader 限制总请求体大小。两者缺一不可。
-
maxMemory太小 → 小文件也落盘,增加 IO 和路径不可控风险 -
maxMemory太大 → 内存耗尽,OOM - 不设
maxMemory→ 全部进内存,极易被大字段拖垮
关键点往往卡在「你以为校验了,其实没校验」——比如只拦了后缀、没读文件头;或者路径看似隔离,但 Web 服务器配置允许执行;又或者 MaxRequestBodySize 被中间件提前消费导致失效。每层都要亲手确认,不能依赖框架默认行为。

















