
批量分配
现在,我们将探讨大规模任务漏洞及其外观,以及几种避开这些漏洞的方法。首先,简要回顾一下:
批量分配是一个漏洞,其中 API 端点不限制用户可以修改其关联对象的哪些属性。
当使用库/框架时,可能会出现此漏洞,该库/框架允许将HTTP参数自动绑定到模型上,然后无需任何验证即可使用该模型。
使用从请求到对象的自动绑定有时会非常有用,但是如果模型的属性不允许用户访问,也会导致安全问题。
示例
我们将使用一个网页示例,用户可以在其中更改详细信息,例如姓名、电子邮件地址和其他类似内容。我们的用户模型定义为:
公共类 UserModel {
public long Id {获取;设置;}
公共字符串名称 {获取;设置;}
公共字符串 passwordHash {获取;设置;}
公共字符串 emailAddress {get; set;}
public bool isAdmin {获取;设置;}
}
前端部分定义表单如下。注意没有 `isAdmin` 值:
<form method="POST">
<input name="Id" type="hidden">
<input name="Name" type="text">
<input name="EmailAddress" type="text">
<input type="submit">
</form>
控制器定义端点如下。通过将 `UserModel` 作为参数,我们的框架会自动为我们将相应的属性映射到这个模型上:
[httpPost]
公共布尔更新用户(用户模型模型)
{
//确保用户只更新自己
model.id = request.user.user.UserID;
var 成功 = userService.updateUser(模型);
返回成功;
}
从这里我们可以假设 “UserService.updateUser” 方法不会在授权方面进行任何进一步的验证,而只是保存提供的用户对象。
(如果没有为属性提供任何值,它只会保留现有值)
这意味着用户可以使用 “isAdmin” 提交请求,这将覆盖当前值并使用户成为管理员,如下所示:
<form method="POST">
<input name="Id" type="hidden" value="666">
<input name="Name" type="text" value="Bad guy">
<input name="EmailAddress" type="text" value="hacker@attacker.com">
<input name="IsAdmin" type="hidden" value="true">
<input type="submit">
</form>
缓解策略
以下是避免批量分配漏洞时需要考虑的一些缓解策略。
避免在请求模型中重复使用数据模型
将数据模型(可能保存在数据库中)与与客户通信时使用的模型分开非常重要。处理向控制器提交的表单与在数据库中保存数据及其在数据库中的表示方式截然不同。这使得前端和持久层之间的耦合水平远高于实际水平。
在映射中要明确
自动绑定(或映射)的问题在于,由于缺少显式映射,很容易暴露模型上不应该访问的属性。通过明确地映射请求模型和后端其余部分,您可以从一开始就防止此类暴露。
这可以通过使用不同的请求和数据模型来实现。这并不能阻止您在请求和数据模型之间使用自动映射器,因为您的请求模型不应暴露特定请求不允许的属性。
更多示例
下面,我们用不同的语言提供了一些其他示例,说明了这可能是什么样子。
C#-不安全
公共课堂用户 {
public long Id {获取;设置;}
公共字符串 firstName {get; set;}
公共字符串 lastName {get; set;}
公共字符串 passwordHash {获取;设置;}
公共字符串国家 {获取;设置;}
公共字符串角色 {获取;设置;}
}
[httpPost]
公共视图结果编辑(用户用户)
{
//只需按提供的方式保存用户即可
userservice.updateUser(用户);
返回 Ok ();
}
C#-安全
公共课堂用户 {
public long Id {获取;设置;}
公共字符串 firstName {get; set;}
公共字符串 lastName {get; set;}
公共字符串 passwordHash {获取;设置;}
公共字符串国家 {获取;设置;}
公共字符串角色 {获取;设置;}
}
公共类更新用户视图模型 {
公共字符串 firstName {get; set;}
公共字符串 lastName {get; set;}
公共字符串国家 {获取;设置;}
}
[httpPost]
公共视图结果编辑(更新用户视图模型用户模型)
{
var 用户 = Request.User;
user.firstName = userModel.firstName;
user.lastName = usermodel.lastName;
User.country = 用户模型国家;
userservice.updateUser(用户);
返回 Ok ();
}
C#-替代方案-排除参数
公共课堂用户 {
public long Id {获取;设置;}
公共字符串 firstName {get; set;}
公共字符串 lastName {get; set;}
公共字符串 passwordHash {获取;设置;}
公共字符串国家 {获取;设置;}
公共字符串角色 {获取;设置;}
}
[httpPost]
公共视图结果编辑([绑定(包括 = “名字、姓氏、国家”)] 用户用户)
{
if (request.User.Id!= User.id) {
返回禁止(“请求更改其他用户”);
}
var existingUser = Request.User;
user.passwordHash = existingUser.PasswordHash;
user.role = existingUser.role;
userservice.updateUser(用户);
返回 Ok ();
}
Java-不安全
公共课堂用户 {
公共整数 ID;
公共字符串 firstName;
公共字符串 LastName;
公共字符串密码哈希;
公共字符串国家;
公共字符串角色;
}
@RequestMapping(值 = “/updateUser”,方法 = requestMethod.POST)
公共字符串 updateUser(用户用户){
userService.update(用户);
返回 “用户更新页面”;
}
Java-安全
公共类 UserViewModel {
公共字符串 firstName;
公共字符串 LastName;
公共字符串国家;
}
公共课堂用户 {
公共整数 ID;
公共字符串 firstName;
公共字符串 LastName;
公共字符串密码哈希;
公共字符串国家;
公共字符串角色;
}
@RequestMapping(值 = “/updateUser”,方法 = requestMethod.POST)
公共字符串更新用户 (@AuthenticationPrincipal 用户当前用户,userViewModel userViewModel UserViewModel) {
currentUser.firstName = userViewModel.first
currentUser.lastName = userViewModel.lastNam
currentUser.country = 用户视图模型国家;
Userservice.update(当前用户);
返回 “用户更新页面”;
}
Javascript-不
app.get ('/user/update', (req, res) => {
var 用户 = req.user;
object.assign(用户,请求正文);
userService.update(用户);
返回 “用户已更新”;
})
자바 스크립트
app.get ('/user/update', (req, res) => {
var 用户 = req.user;
user.firstName = req.body.firstName;
user.lastName = req.body.lastName;
用户国家 = req.body.country;
userService.update(用户);
返回 “用户已更新”;
})
Python-不安全
@app .route (“/user/update”,methods= ['POST'])
def update_user ():
用户 = request.user
表单 = request.form.to_dict (flat=True)
对于密钥,form.items () 中的值:
setattr(用户、密钥、值)
userservice.updateUser(用户
返回重定向(“/用户”,代码 = 201)
Python-安全