
核心要点(TL;DR)
- 上传漏洞防护的核心是三重防线:目录不可执行、类型白名单校验、服务端二次改名。
- 上传目录应独立于站点根目录之外,属主设为Web用户且去掉执行权限。
- 仅校验前端或Content-Type不可靠,必须服务端校验扩展名、MIME与文件头。
- Nginx用location匹配上传目录并deny脚本解析,是最低成本的高危拦截手段。
很多站长在服务器被植入webshell后才发现:问题不在密码强度,而在一个看似无害的「上传头像」接口。任意文件上传长期位居Web高危漏洞前列,攻击者上传一个.php文件就能拿到服务器控制权。本文面向要立刻动手的运维和站长,给出上传目录权限收敛、文件类型校验、Web层拦截三套可直接复制的配置与检查清单,照着做完即可显著降低上传漏洞被利用的概率。
一、上传漏洞为什么总在同一个地方翻车
上传漏洞的本质是「用户可控内容进入了可执行路径」。常见成因有三类:
- 目录可执行:上传目录位于站点根目录内,且Web服务器会解析其中的脚本文件。
- 类型校验缺失:只校验前端后缀,或仅信任请求头里的
Content-Type。 - 文件名可控:直接使用用户上传的原始文件名,导致
shell.php.jpg、.htaccess等绕过手法生效。
危害不止于拿shell:攻击者还可利用上传功能做存储型XSS、覆盖配置文件、塞入钓鱼页面,甚至把服务器当作流量中转。防护思路必须从「拦截坏文件」转为「让坏文件即使落地也无法执行」。
二、第一步:上传目录权限与位置收敛
先解决「落地后能不能跑」的问题。核心原则:上传目录独立、不可执行、属主隔离。
操作步骤
- 把上传目录移出站点根目录,例如站点在
/var/www/site,上传目录放/var/www/uploads,通过Nginx别名或后端接口输出。 - 设置属主与权限:
chown -R www-data:www-data /var/www/uploads find /var/www/uploads -type d -exec chmod 755 {} \; find /var/www/uploads -type f -exec chmod 644 {} \; - 禁止目录内脚本执行(Nginx示例):
location ^~ /uploads/ { location ~ \.(php|php5|phtml|jsp|asp|aspx|sh)$ { deny all; } } - Apache环境在上传目录放置
.htaccess:php_flag engine off Options -ExecCGI Require all denied
仅保留静态资源访问时,可直接用Require all denied配合后端下载接口。
注意:chmod 777是上传漏洞的放大器。任何情况下都不要给上传目录全局可写权限。三、第二步:文件类型校验的正确分层
单点校验都能被绕过,必须做多层。推荐顺序如下:
| 层级 | 校验内容 | 是否可信 |
|---|---|---|
| 前端JS | 后缀提示 | 仅体验,不可信 |
| 服务端扩展名 | 白名单比对 | 可信,必做 |
| Content-Type | 请求头MIME | 可伪造,辅助 |
| 文件头 | magic number | 可信,必做 |
| 二次编码 | 图片重压缩 | 可信,图片必做 |
关键实现要点
- 白名单而非黑名单:只允许
jpg/png/gif/pdf等业务必需类型,扩展名统一转小写后再比对。 - 文件头校验:PHP用
finfo_file()或getimagesize()读取真实类型,与白名单交叉验证。 - 强制重命名:存储名用
date('Ymd').'-'.bin2hex(random_bytes(8))生成,后缀由服务端按白名单映射决定,彻底丢弃原始文件名。 - 图片二次处理:用GD或ImageMagick重新编码,可破坏内嵌的恶意代码段。
- 限制大小与频率:在Nginx设
client_max_body_size 5m;,接口层做次数限流。
四、第三步:排查现有站点是否已中招
防护之外,先确认历史是否已被写入后门。以下命令可快速排查:
# 查找上传目录中的脚本文件 find /var/www -path '*upload*' -type f \( -name '*.php' -o -name '*.jsp' -o -name '*.phtml' \) -ls # 查找近期被修改的可疑文件 find /var/www -type f -mtime -7 -name '*.php' | head -50 # 检查异常进程与连接 ps aux | grep -E 'www-data.*(sh|bash|nc)' netstat -antp | grep ESTABLISHED
发现可疑文件先隔离样本再删除,同时检查Web访问日志中针对上传路径的POST请求,确认攻击入口后修复再上线。
五、上线前检查清单
- 上传目录位于站点根目录之外,或已禁止脚本解析。
- 目录权限为755、文件644,属主为Web运行用户,无777。
- 服务端扩展名白名单、MIME校验、文件头校验三项齐全。
- 文件存储名随机化,不保留用户原始文件名与后缀。
- 上传接口有登录鉴权、大小限制与频率限制。
- 错误回显不暴露绝对路径,上传行为写入日志。
- 图片类上传执行二次编码处理。
在防护体系建设上,AegisX可提供上传接口的风险检测与主机层文件监控能力,配合上述配置形成「入口校验+落地不可执行+事后发现」的闭环。
六、行动建议
不要等被入侵再补。今天就做三件事:把上传目录移出站点根目录并去掉执行权限;在Nginx或Apache层对上传路径deny脚本解析;检查代码中是否只做了前端后缀校验。三件事完成后,任意文件上传被利用的门槛会大幅提高。随后把本文清单固化为上线检查项,每次迭代上传功能都过一遍,才能真正把上传漏洞挡在门外。
常见问题(FAQ)
上传目录设置了755权限,为什么还是被上传了webshell?
权限位不是拦截上传的关键。755只决定谁能读写执行,攻击者通过正常上传接口写入文件时用的是Web进程自身权限,天然有写权限。真正要收紧的是:目录属主与运行用户隔离、关闭该目录的脚本解析、文件名强制重命名、上传后不可直接访问。建议把上传目录移出站点根目录,并在Nginx中对该路径deny执行PHP等脚本。
只在前端限制文件后缀,够安全吗?
完全不够。前端校验可被绕过,攻击者用Burp改包或直接构造POST请求即可上传任意文件。服务端必须做独立校验:扩展名白名单、Content-Type检查、文件头magic number比对,三者缺一不可。校验通过后还要重命名为随机字符串并去掉原始扩展名,最终以白名单映射的方式决定存储后缀。
Nginx怎么禁止上传目录执行PHP?
在server块中为上传路径单独加location,例如location ^~ /uploads/ { location ~ \.(php|php5|phtml|jsp|asp)$ { deny all; } },更彻底的做法是直接return 403或只允许静态资源类型。同时确认fastcgi_pass不会命中该路径。Apache则可在上传目录放.htaccess写php_flag engine off并Require all denied。
图片上传如何防止伪装成jpg的webshell?
不能只看扩展名和Content-Type。用getimagesize()或fileinfo读取文件头,确认真实类型属于允许的图片格式;对图片做二次压缩或重新编码,可彻底破坏内嵌的恶意代码;存储时重命名为随机ID并统一后缀。若业务需要保留原图,也应存到非Web可访问目录,通过带鉴权的下载接口输出。
上传功能上线前应该检查哪些项?
建议按清单逐项确认:上传目录是否在站点根目录外、目录是否禁止脚本解析、服务端是否做扩展名白名单、是否校验MIME与文件头、文件是否重命名、大小与数量是否限流、上传接口是否有鉴权与频率限制、错误信息是否泄露路径、日志是否记录上传行为。任一项缺失都应在上线前修复。