Massenzuweisung
Im Folgenden werden wir uns mit den Mass Assignment-Schwachstellen befassen, wie sie aussehen und welche Möglichkeiten es gibt, sie zu vermeiden. Zunächst eine kurze Zusammenfassung:
Mass Assignment ist eine Schwachstelle, bei der API-Endpunkte nicht einschränken, welche Eigenschaften des zugehörigen Objekts von einem Benutzer geändert werden können.
Diese Sicherheitslücke kann bei der Verwendung einer Bibliothek/eines Frameworks auftreten, das die automatische Bindung von HTTP-Parametern an ein Modell ermöglicht, das dann ohne jegliche Validierung verwendet wird.
Die Verwendung der automatischen Bindung von einer Anfrage an ein Objekt kann manchmal sehr hilfreich sein, aber sie kann auch zu Sicherheitsproblemen führen, wenn das Modell Eigenschaften hat, die für den Benutzer nicht zugänglich sein sollen.
Beispiel
Wir werden das Beispiel einer Webseite verwenden, auf der ein Benutzer Details wie seinen Namen, seine E-Mail-Adresse und andere ähnliche Dinge ändern kann. Wir haben ein Benutzermodell definiert als:
public class UserModel {
public long Id { get; set; }
public string Name { get; set; }
public string PasswordHash { get; set; }
public string EmailAddress { get; set; }
public bool IsAdmin { get; set; }
}
The frontend part defines a form as following. Note the absence of the `IsAdmin` value:
<form method="POST">
<input name="Id" type="hidden">
<input name="Name" type="text">
<input name="EmailAddress" type="text">
<input type="submit">
</form>
The controller defines an endpoint as following. By having the `UserModel` as a parameter, our framework will automatically map the respective properties onto this model for us:
[HttpPost]
public bool UpdateUser(UserModel model)
{
// Ensure the user only updates themselves
model.Id = Request.User.UserId;
var success = UserService.UpdateUser(model);
return success;
}
Von hier aus können wir davon ausgehen, dass die Methode "UserService.UpdateUser" keine weitere Validierung in Bezug auf die Autorisierung durchführt, sondern einfach nur das bereitgestellte Benutzerobjekt speichert.
(Wenn für eine Eigenschaft kein Wert angegeben wird, wird der vorhandene Wert beibehalten)
Das bedeutet, dass ein Benutzer eine Anfrage mit der Option "IsAdmin" stellen könnte, die den aktuellen Wert überschreibt und den Benutzer zu einem Administrator macht:
<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>
Strategien zur Schadensbegrenzung
Nachfolgend finden Sie einige Strategien zur Vermeidung von Schwachstellen bei der Massenzuweisung, die Sie berücksichtigen sollten.
Vermeiden Sie die Wiederverwendung von Datenmodellen für Anfragemodelle
Es ist wichtig, Ihre Datenmodelle (die in einer Datenbank persistiert werden können) von den Modellen zu trennen, die bei der Kommunikation mit einem Client verwendet werden. Die Verarbeitung einer Formularübermittlung an einen Controller ist eine ganz andere Angelegenheit als die Persistenz von Daten in einer Datenbank und deren Darstellung in der Datenbank. Dies führt zu einer viel stärkeren Kopplung zwischen dem Frontend und der Persistenzschicht, als es gut ist.
Seien Sie explizit in Ihren Zuordnungen
Das Problem bei der automatischen Bindung (oder Zuordnung) besteht darin, dass das Fehlen expliziter Zuordnungen dazu führt, dass Eigenschaften, auf die im Modell nicht zugegriffen werden soll, leicht offengelegt werden können. Durch explizite Mappings zwischen Anfragemodellen und dem Rest Ihres Backends können Sie diese Art von Offenlegung von Anfang an verhindern.
Dies könnte durch die Verwendung unterschiedlicher Modelle für Anfragen und Daten geschehen. Dies hindert Sie nicht daran, einen automatischen Mapper zwischen dem Anforderungs- und dem Datenmodell zu verwenden, da Ihr Anforderungsmodell keine Eigenschaften offenlegen sollte, die für die spezifische Anforderung nicht zulässig sind.
Weitere Beispiele
Im Folgenden finden Sie einige weitere Beispiele in verschiedenen Sprachen, die zeigen, wie dies aussehen kann.
C# - unsicher
public class User {
public long Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PasswordHash { get; set; }
public string Country { get; set; }
public string Role { get; set; }
}
[HttpPost]
public ViewResult Edit( User user)
{
// Just saves the user as provided
UserService.UpdateUser(user);
return Ok();
}
C# - sicher
public class User {
public long Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PasswordHash { get; set; }
public string Country { get; set; }
public string Role { get; set; }
}
public class UpdateUserViewModel {
public string FirstName { get; set; }
public string LastName { get; set; }
public string Country { get; set; }
}
[HttpPost]
public ViewResult Edit(UpdateUserViewModel userModel)
{
var user = Request.User;
user.FirstName = userModel.FirstName;
user.LastName = userModel.LastName;
user.Country = userModel.Country;
UserService.UpdateUser(user);
return Ok();
}
C# - alternativ - schließt Parameter aus
public class User {
public long Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PasswordHash { get; set; }
public string Country { get; set; }
public string Role { get; set; }
}
[HttpPost]
public ViewResult Edit([Bind(Include = "FirstName,LastName,Country")] User user)
{
if(Request.User.Id != user.Id) {
return Forbidden("Requesting changing of another user");
}
var existingUser = Request.User;
user.PasswordHash = existingUser.PasswordHash;
user.Role = existingUser.Role;
UserService.UpdateUser(user);
return Ok();
}
Java - unsicher
public class User {
public int id;
public String firstName;
public String lastName;
public String passwordHash;
public String country;
public String role;
}
@RequestMapping(value = "/updateUser", method = RequestMethod.POST)
public String updateUser(User user) {
userService.update(user);
return "userUpdatedPage";
}
Java - sicher
public class UserViewModel {
public String firstName;
public String lastName;
public String country;
}
public class User {
public int id;
public String firstName;
public String lastName;
public String passwordHash;
public String country;
public String role;
}
@RequestMapping(value = "/updateUser", method = RequestMethod.POST)
public String updateUser(@AuthenticationPrincipal User currentUser, UserViewModel userViewModel) {
currentUser.firstName = userViewModel.firstName;
currentUser.lastName = userViewModel.lastName;
currentUser.country = userViewModel.country;
userService.update(currentUser);
return "userUpdatedPage";
}
Javascript - unsicher
app.get('/user/update', (req, res) => {
var user = req.user;
Object.assign(user, req.body);
UserService.Update(user);
return "User has been updated";
})
Javascript - sicher
app.get('/user/update', (req, res) => {
var user = req.user;
user.firstName = req.body.firstName;
user.lastName = req.body.lastName;
user.country = req.body.country;
UserService.Update(user);
return "User has been updated";
})
Python - Unsicher
@app.route("/user/update", methods=['POST'])
def update_user():
user = request.user
form = request.form.to_dict(flat=True)
for key, value in form.items():
setattr(user, key, value)
UserService.UpdateUser(user)
return redirect("/user", code=201)
Python - Sicher