媒体上传被 403 拦截:没人记录的 AWS WAF 8 KB 限制
发布于 2026年7月19日

症状:只在上传时 403,且只在超过某个大小时
一个 WordPress 站点的编辑报告上传图片出错——但只有部分。小文件能过;大一些的就 403。公开站点正常。查看 WAF 日志,被拦的请求很清楚:
action=BLOCK uri=/wp-admin/async-upload.php method=POST
terminatingRule=AWS-AWSManagedRulesCommonRuleSet ruleId=SizeRestrictions_BODY
bodySize=85701原因:SizeRestrictions_BODY 与 8 KB 检查上限
SizeRestrictions_BODY 托管规则(来自 AWS 的 Common Rule Set)会拦截大的请求体。但有个微妙处:WAF 只检查请求体的前 8 KB(更高档位为 16 KB)。一个 84 KB 的上传同时撞破规则上限和检查上限——在到达 WordPress 之前就被拦下。
Common Rule Set 默认很好用,直到这条大小规则把合法的图片上传当成攻击。任何接收上传的应用,都需要为其上传路由设一个有意识的例外。
修复需要一个标签匹配——而且顺序要对
出路不是禁用规则(会丢失防护)。而是把它置于 COUNT 模式以发出一个 label,然后创建你自己的、优先级更低的规则,当 label 存在且路径是上传时放行该请求。如果托管规则仍处于 BLOCK,它会在你的规则运行之前就拦截——这正是 COUNT 必不可少的原因。
CloudFront WAF 的坑:没有 RegexMatchStatement
要匹配路径 /wp-admin/async-upload.php,本能是用正则。但关联到 CloudFront 分发的 WAF(CLOUDFRONT/全局作用域)不支持 RegexMatchStatement。你必须用 ByteMatchStatement 配 ENDS_WITH/STARTS_WITH。而通过 --cli-input-json 提交时,SearchString 用 base64:
{
"Name": "allow-wp-upload",
"Priority": 0,
"Action": { "Allow": {} },
"Statement": {
"ByteMatchStatement": {
"SearchString": "L3dwLWFkbWluL2FzeW5jLXVwbG9hZC5waHA=",
"FieldToMatch": { "UriPath": {} },
"PositionalConstraint": "ENDS_WITH",
"TextTransformations": [{ "Priority": 0, "Type": "NONE" }]
}
},
"VisibilityConfig": { "SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true, "MetricName": "AllowWpUpload" }
}耗费时间的细节:Description 字段的最小长度为 1(空字符串会校验失败),并且只有当发出 label 的托管规则处于 COUNT(而非 BLOCK)时,label 才生效。Pro 功能(override、label、SizeConstraint、对 Cookie 的 ByteMatch)在 Free 套餐里不存在。
经验教训
1. SizeRestrictions_BODY 默认拦截大上传。每条上传路由都需要例外;WAF 只检查请求体的前 8 KB。
2. 托管规则必须置于 COUNT,label 才生效。处于 BLOCK 时,它会在你的放行规则之前结束评估。
3. CloudFront WAF 没有 RegexMatchStatement。用 ByteMatch(ENDS_WITH/STARTS_WITH);通过 CLI/JSON 时,SearchString 是 base64。
结论
Common Rule Set 默认防护得很好——默认也把你编辑的图片上传当成可疑载荷。这个 403 既不是攻击,也不是 WordPress 的 bug:是大小规则在不知上下文的情况下尽职。一个顺序正确的 label 匹配,配上 ByteMatch(因为 CloudFront 不接受正则),在不降低防护的前提下把上传还了回来。