Node.js 文件上传处理实战与安全加固
讲解 Node.js 文件上传的完整实现与安全加固方案,涵盖 multer 配置、文件类型与大小限制、存储策略、病毒扫描思路、常见攻击防护等,附代码示例,避免上传功能成为服务器后门。
# Node.js 文件上传处理实战与安全加固
> 文件上传功能如果没有做好校验和防护,等于给攻击者开了一扇后门。本文从实现到安全加固,把上传功能一次做扎实。
## 一、基础实现:multer
multer 是 Express 生态最常用的上传中间件:
```js
const multer = require("multer");
const path = require("path");
// 配置存储
const storage = multer.diskStorage({
destination: (req, file, cb) => cb(null, "uploads/"),
filename: (req, file, cb) => {
// 用随机名,别用原始文件名
const ext = path.extname(file.originalname);
cb(null, Date.now() + "-" + Math.round(Math.random() * 1e9) + ext);
}
});
const upload = multer({ storage });
app.post("/api/upload", upload.single("file"), (req, res) => {
res.json({ ok: true, file: req.file });
});
```
> 第一步就埋下隐患:直接用原始文件名存储,可能被路径穿越或覆盖。
## 二、文件类型校验
> ⚠️ 不能只信扩展名,Content-Type 也能伪造,要校验文件内容。
```js
const ALLOWED = [".jpg", ".png", ".gif", ".webp"];
const upload = multer({
storage,
fileFilter: (req, file, cb) => {
const ext = path.extname(file.originalname).toLowerCase();
if (!ALLOWED.includes(ext)) {
return cb(new Error("仅支持图片格式"));
}
cb(null, true);
}
});
```
## 三、大小限制
不限制大小,服务器磁盘和带宽分分钟被打满:
```js
const upload = multer({
storage,
limits: { fileSize: 5 * 1024 * 1024 } // 5MB
});
// 捕获超限错误
app.post("/api/upload", (req, res) => {
upload.single("file")(req, res, (err) => {
if (err) {
if (err.code === "LIMIT_FILE_SIZE") {
return res.status(413).json({ error: "文件不能超过 5MB" });
}
return res.status(400).json({ error: err.message });
}
res.json({ ok: true });
});
});
```
## 四、内容真实性校验
图片用魔数(Magic Number)校验文件头,防伪装:
```js
// 读取文件头判断真实类型
const fs = require("fs");
function sniffImageType(filePath) {
const buf = fs.readFileSync(filePath).subarray(0, 4);
if (buf[0] === 0xFF && buf[1] === 0xD8) return "jpg";
if (buf.toString("ascii", 1, 4) === "PNG") return "png";
return null;
}
```
> 也可以直接用 file-type 等库识别真实 MIME。
## 五、存储安全策略
- 上传目录禁止执行脚本:Nginx 配置去掉 php/node 执行权限
- 文件名随机化,杜绝路径穿越
- 按日期分子目录存储,避免单目录文件过多
- 敏感文件不落盘,直接存对象存储
## 六、常见攻击与防护
### 1. 恶意脚本上传
攻击者上传 .php/.js 木马,配合服务器解析漏洞执行。
```bash
# Nginx 禁止上传目录执行脚本
location ^~ /uploads/ {
default_type application/octet-stream;
location ~* .(php|asp|jsp)$ { return 403; }
}
```
### 2. 文件名路径穿越
原始文件名含 ../../ 覆盖服务器文件。解决:随机化文件名,丢弃原始路径。
### 3. 超大文件与并发打满带宽
限制大小 + 上传限速 + CDN 前置,Nginx 层限制请求体:
```bash
# Nginx 限制请求体 10MB
client_max_body_size 10m;
```
### 4. 病毒木马
图片类内容过一遍杀毒扫描,或上传到云服务用安全检测能力。
## 七、头像上传完整示例
```js
// 组合:类型 + 大小 + 随机名
const avatarUpload = multer({
storage: avatarStorage,
limits: { fileSize: 2 * 1024 * 1024 },
fileFilter: (req, file, cb) => {
const ext = path.extname(file.originalname).toLowerCase();
if (![".jpg", ".png", ".webp"].includes(ext)) {
return cb(new Error("头像仅支持 jpg/png/webp"));
}
cb(null, true);
}
});
app.post("/api/user/avatar", avatarUpload.single("avatar"), async (req, res) => {
if (!req.file) return res.status(400).json({ error: "请选择文件" });
// 读文件头校验真实图片类型
const realType = sniffImageType(req.file.path);
if (!realType) {
fs.unlinkSync(req.file.path);
return res.status(400).json({ error: "文件不是有效图片" });
}
res.json({ ok: true, url: "/uploads/" + req.file.filename });
});
```
## 八、检查清单
- [ ] 文件类型白名单 + 魔数校验
- [ ] 文件大小限制(应用层 + Nginx 层)
- [ ] 文件名随机化存储
- [ ] 上传目录禁止脚本执行
- [ ] 目录按日期分片
- [ ] 日志记录上传来源
上传功能按这个清单加固,能挡住绝大多数攻击。