Richtlinien

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)
Slogan

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.

Weitere Richtlinien
Demo buchen