结论:批量赋值漏洞发生在框架把请求参数直接绑定到数据模型时——User.update(request.body) 这类写法把请求里的所有键写进对象,攻击者多传一个 "role": "admin""balance": 99999 就改了本不该开放的字段。GitHub 2012 年著名的 Rails 事件(公钥被注入)就是此漏洞。

展开:成因是便利与安全的冲突:框架为省 CRUD 样板提供自动绑定,但模型字段多于表单字段(is_admin、内部状态、关联 ID 都在模型上)。防御按层:1)白名单显式声明——Rails 的 strong parameters(params.require(:user).permit(:name, :email))、Laravel 的 $fillable、ASP.NET 的 [Bind];2)专用 DTO——入参用独立结构体只含可写字段,手工或映射器拷贝到领域模型,API 层的 schema 校验(JSON Schema/pydantic)天然实现白名单,是现代 API 的最干净做法;3)框架级保护——Spring 的 @InitBinder setAllowedFields、Jackson 的 @JsonIgnoreProperties(ignoreUnknown=false) 让多余字段直接报错。易错点:1)黑名单($guarded)随模型加字段失效——默认开放、加新敏感字段时忘了补黑名单;2)嵌套对象绑定(address[owner_id])与 JSON 深层属性({"org": {"role": "admin"}})同样要审;3)GET 参数绑定(Rails 老问题)让批量赋值连 CSRF 防护一起绕。

# pydantic DTO:请求体里多出 role 直接 422
class UserUpdate(BaseModel):
    model_config = ConfigDict(extra="forbid")
    name: str | None = None
    email: str | None = None

追问方向:GraphQL 的对应风险(内省 + 嵌套 mutation)、序列化方向的过暴露(response 带出密码哈希)为何是同一问题的镜像。