Massenzuweisung
Wir beschäftigen uns nun mit Mass-Assignment-Schwachstellen, wie sie aussehen und wie man sie vermeidet. Zuerst eine kurze Zusammenfassung:
Mass Assignment ist eine Schwachstelle, bei der API-Endpunkte nicht einschränken, welche Eigenschaften ihrer zugehörigen Objekte von einem Benutzer geändert werden können.
Diese Schwachstelle kann auftreten, wenn eine Bibliothek oder ein Framework verwendet wird, das die automatische Bindung von HTTP-Parametern an ein Modell ermöglicht, welches anschließend ohne Validierung weiterverwendet wird.
Die automatische Bindung einer Anfrage an ein Objekt kann zwar sehr hilfreich sein, führt jedoch zu Sicherheitsrisiken, wenn das Modell Eigenschaften enthält, auf die der Benutzer eigentlich keinen Zugriff haben sollte.
Beispiel
Wir betrachten das Beispiel einer Webseite, auf der ein Benutzer Details wie seinen Namen, seine E-Mail-Adresse und Ähnliches ändern kann. Wir haben ein User-Modell 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; }
}
Das Frontend definiert ein Formular wie folgt. Beachten Sie, dass der Wert `IsAdmin` fehlt:
<form method="POST">
<input name="Id" type="hidden">
<input name="Name" type="text">
<input name="EmailAddress" type="text">
<input type="submit">
</form>
Der Controller definiert einen Endpunkt wie folgt. Da das `UserModel` als Parameter dient, ordnet unser Framework die entsprechenden Eigenschaften automatisch diesem Modell zu:
[HttpPost]
public bool UpdateUser(UserModel model)
{
// Sicherstellen, dass der Benutzer nur sich selbst aktualisiert
model.Id = Request.User.UserId;
var success = UserService.UpdateUser(model);
return success;
}
Wir können davon ausgehen, dass die Methode ‚UserService.UpdateUser‘ keine weitere Autorisierungsprüfung durchführt und das bereitgestellte Benutzerobjekt einfach speichert.
(Wenn für eine Eigenschaft kein Wert angegeben ist, bleibt der bestehende Wert erhalten)
Das bedeutet, dass ein Benutzer eine Anfrage mit dem Feld ‚IsAdmin‘ senden könnte, wodurch der aktuelle Wert überschrieben und der Benutzer wie folgt zum Administrator befördert würde:
<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>
Gegenmaßnahmen
Im Folgenden finden Sie einige Strategien zur Vermeidung von Mass-Assignment-Schwachstellen.
Vermeiden Sie die Wiederverwendung von Datenmodellen für Anfragemodelle
Es ist wichtig, Ihre Datenmodelle (die möglicherweise in einer Datenbank gespeichert sind) von den Modellen zu trennen, die bei der Kommunikation mit einem Client verwendet werden. Die Verarbeitung einer Formularübermittlung an einen Controller ist etwas völlig anderes als das Speichern von Daten in einer Datenbank und deren dortige Repräsentation. Eine Vermischung führt zu einer viel stärkeren Kopplung zwischen Frontend und Persistenzschicht, als es ratsam ist.
Seien Sie explizit bei Ihren Mappings
Das Problem bei der automatischen Bindung (oder dem Mapping) ist, dass fehlende explizite Zuweisungen es leicht machen, Eigenschaften offenzulegen, die auf dem Modell eigentlich nicht zugänglich sein sollten. Indem Sie Mappings zwischen Anfragemodellen und dem Rest Ihres Backends explizit definieren, können Sie solche Sicherheitslücken von vornherein verhindern.
Dies lässt sich durch die Verwendung unterschiedlicher Modelle für Anfragen und Daten erreichen. Das hindert Sie nicht daran, einen automatischen Mapper zwischen Anfrage- und Datenmodell zu verwenden, da Ihr Anfragemodell keine Eigenschaften offenlegen sollte, die für die spezifische Anfrage nicht zulässig sind.
Weitere Beispiele
Nachfolgend finden Sie einige weitere Beispiele in verschiedenen Sprachen, 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)
{
// Speichert den Benutzer einfach wie übergeben
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# - Alternative - Parameter ausschließen
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("Änderung eines anderen Benutzers angefordert");
}
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 "Benutzer wurde aktualisiert";
})
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 "Benutzer wurde aktualisiert";
})
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
@app.route("/user/update", methods=['POST'])
def update_user():
user = request.user
form = request.form
user.firstName = form.firstName
user.lastName = form.lastName
UserService.UpdateUser(user)
return redirect("/user", code=201)
Sichere Programmierung in der Praxis
Entdecken Sie Richtlinien für sicheres Programmieren, um Schwachstellen wie die OWASP Top 10 zu verstehen und zu beheben, und tauchen Sie in geführte Trainingsmissionen ein, um diese in realistischen Anwendungssimulationen zu üben.