Nginx可通过map指令将特定Content-Type(如multipart/form-data)映射为标记值,再结合limit_req_zone实现定向限频;同时配合if校验、client_max_body_size和limit_conn等策略,达成内容类型级的精准请求控制与多层防御。

Nginx 本身不支持直接按 Content-Type 值做请求频率限制(比如“每秒只允许 2 个 multipart/form-data 请求”),因为 limit_req_zone 的 key 只能基于变量(如 $binary_remote_addr、$request_uri),而 limit_req 不支持条件式触发。但你可以通过组合策略,间接实现对特定 Content-Type 请求的定向限频,核心思路是:先识别,再标记,最后限频。
利用变量 + map 构建 Content-Type 分类标识
Nginx 的 map 指令可将 Content-Type 映射为自定义变量,再将其作为 limit_req_zone 的 key 或配合 if 做前置拦截:
http {
# 将危险/高风险 Content-Type 映射为标记值(如 "upload"),其他设为空
map $content_type $ct_class {
default "";
"~*multipart/form-data" "upload";
"~*application/octet-stream" "upload";
"~*text/plain" "upload"; # 防绕过:攻击者常伪造此类型上传 Webshell
}
# 定义针对 upload 类请求的独立限频区域(每秒最多 3 个,突发 5 个)
limit_req_zone $ct_class zone=upload_limit:10m rate=3r/s;
server {
location /upload/ {
# 强制校验 Content-Type 格式(防空头、非法值)
if ($content_type = "") { return 400; }
if ($content_type !~ "^multipart/form-data;.*boundary=") {
return 403;
}
# 应用针对 upload 类的限频(仅当 $ct_class 非空时生效)
limit_req zone=upload_limit burst=5 nodelay;
# 同时限制单次上传大小(硬门槛)
client_max_body_size 5M;
proxy_pass http://backend;
}
}
}✅ 关键点:
map把multipart/form-data等类型统一映射为"upload",limit_req_zone就只对这个值计数;其他请求(如application/json)因$ct_class为空,不计入该 zone,完全不受影响。
结合请求体大小与频率做双控
单纯限频不够——攻击者可能用大量小文件刷请求。建议叠加 client_max_body_size 和连接数限制:
- 对
/upload/路径:-
client_max_body_size 5M;—— 拦截明显超规大文件(Webshell 很少小于 5M,但正常头像/证件照极少超此值) -
limit_conn conn_limit 3;—— 单 IP 最多 3 个并发上传连接(防多线程爆破) -
limit_req zone=upload_limit burst=3 nodelay;—— 每秒最多 3 个合法 multipart 请求
-
这样即使攻击者伪造 Content-Type,只要不是标准 multipart/form-data 格式,第一步 if 就会 403;若格式正确但频率超标,立即 503;若单次过大,直接 413。
补充防御:拒绝可疑 Content-Type 组合
有些恶意上传会故意设置矛盾头,例如:
-
Content-Type: multipart/form-data但没boundary -
Content-Type: text/plain却带filename=参数
可在 location 中加一层校验:
if ($content_type ~* "multipart/form-data" && $content_type !~ "boundary=") {
return 400;
}
if ($args ~* "filename=" && $content_type !~ "multipart/form-data") {
return 403;
}这类规则不依赖后端,Nginx 在解析请求头阶段就拦截,开销极低。
注意事项
-
map必须定义在http块内,且不能放在server或location中; -
$content_type是只读变量,大小写敏感,推荐用~*不区分大小写匹配; -
limit_req的burst缓冲区会暂存超额请求,若设nodelay,超出即刻返回 503,更利于快速阻断扫描行为; - 若前端使用分片上传(如 tus、cos-js-sdk),实际每个分片是独立请求,需按业务调整
rate值(例如rate=20r/s支持 5 个用户同时上传)。
不复杂但容易忽略


















