Mass-Assignment-Schwachstellen vermeiden
AI generated
OWASP
0x00
OWASP API Security Top 10 · A06
Mass-Assignment-Schwachstellen vermeiden
Allowlist statt Blocklist bei automatischem Daten-Binding

Automatisches Binden von Request-Daten an Entities oder Models ist bequem, kann aber dazu führen, dass Angreifer Felder wie is_admin einfach mitschicken und überschreiben. Wir zeigen den Unterschied zwischen Allowlist und Blocklist und ein konkretes Symfony-Beispiel mit DTOs statt direktem Entity-Binding.

13 Min. Lesezeit Mass Assignment Allowlist statt Blocklist

1. Was ist Mass Assignment?

Mass Assignment beschreibt eine Schwachstelle, bei der eingehende Request-Daten automatisch und ungefiltert auf die Eigenschaften eines Entity- oder Model-Objekts gemappt werden. Frameworks bieten diese Automatik oft an, um Boilerplate-Code zu sparen: Ein JSON-Body wird direkt in ein PHP-Objekt oder Array-Update übersetzt, ohne dass jedes Feld einzeln zugewiesen werden muss. Diese Bequemlichkeit ist grundsätzlich sinnvoll, wird aber zum Sicherheitsrisiko, sobald niemand mehr bewusst entscheidet, welche Felder tatsächlich vom Client kommen dürfen.

Das Problem entsteht, sobald ein Entity mehr Felder besitzt, als der Client eigentlich setzen dürfen sollte. Enthält ein User-Entity neben Name und E-Mail auch ein Feld isAdmin oder role, kann ein Angreifer dieses Feld einfach im Request mitschicken. Wird es automatisch gebunden, hat sich der Angreifer selbst Admin-Rechte verschafft, ohne dass dafür eine explizite Sicherheitslücke im klassischen Sinn nötig war.

2. Wie ein Mass-Assignment-Angriff funktioniert

Der Ablauf ist denkbar einfach: Ein Angreifer registriert einen normalen Account und beobachtet, welche Felder beim Update des eigenen Profils akzeptiert werden. Oft reicht ein Blick in die Frontend-Quelltexte, API-Dokumentation oder in öffentliche Entity-Klassen eines Open-Source-Projekts, um interne Feldnamen wie isAdmin, role, balance oder verified zu erraten.

Das folgende Beispiel zeigt einen Symfony-Controller, der Profildaten aktualisiert. Die verwundbare Variante bindet den kompletten Request direkt an das Entity. Die abgesicherte Variante nutzt ein DTO mit einer festen Liste erlaubter Felder.


<?php

declare(strict_types=1);

namespace App\Controller\Api;

use App\Dto\UpdateProfileRequest;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\SerializerInterface;

final class ProfileApiController extends AbstractController
{
    public function __construct(
        private readonly EntityManagerInterface $entityManager,
        private readonly SerializerInterface $serializer,
    ) {
    }

    // Verwundbar: Request-JSON wird direkt auf das Entity gemappt
    #[Route('/api/profile', methods: ['PATCH'])]
    public function updateVulnerable(Request $request): JsonResponse
    {
        $user = $this->getUser();
        $data = json_decode($request->getContent(), true);

        // Jedes Feld aus dem Request landet ungeprüft auf dem Entity,
        // inklusive isAdmin oder role, falls der Client sie mitschickt.
        foreach ($data as $property => $value) {
            $setter = 'set' . ucfirst($property);
            if (method_exists($user, $setter)) {
                $user->$setter($value);
            }
        }

        $this->entityManager->flush();

        return $this->json(['status' => 'updated']);
    }

    // Abgesichert: DTO mit fester Allowlist an erlaubten Feldern
    #[Route('/api/profile', methods: ['PATCH'])]
    public function updateSecured(Request $request): JsonResponse
    {
        /** @var UpdateProfileRequest $dto */
        $dto = $this->serializer->deserialize(
            $request->getContent(),
            UpdateProfileRequest::class,
            'json'
        );

        $user = $this->getUser();
        // Nur Felder, die im DTO explizit deklariert sind, können überhaupt ankommen
        $user->setDisplayName($dto->displayName);
        $user->setEmail($dto->email);

        $this->entityManager->flush();

        return $this->json(['status' => 'updated']);
    }
}

3. Allowlist statt Blocklist

Ein Blocklist-Ansatz versucht, gefährliche Felder explizit auszuschließen, zum Beispiel indem isAdmin vor dem Mapping aus dem Array entfernt wird. Dieser Ansatz ist fehleranfällig, weil jedes neue sensible Feld, das dem Entity später hinzugefügt wird, aktiv in die Blockliste aufgenommen werden muss. Vergisst ein Entwickler diesen Schritt, ist die Lücke sofort wieder da, ohne dass es beim Review unbedingt auffällt.

Ein Allowlist-Ansatz dreht das Prinzip um: Es wird explizit definiert, welche Felder ein Client setzen darf, alles andere ist standardmäßig gesperrt. Neue Felder sind damit automatisch sicher, bis sie bewusst zur Allowlist hinzugefügt werden. Genau dieses Prinzip setzen DTOs, Symfony-Forms mit expliziter Feldliste oder Serializer-Gruppen technisch um.

4. Typische Schwachstellen in der Praxis

Neben klassischen User-Entities mit Rollenfeldern sind auch Bestellungen mit Preis- oder Statusfeldern gefährdet: Ein Client, der beim Checkout ein price-Feld mitschicken kann, das direkt übernommen wird, kann theoretisch den Preis selbst bestimmen. Aehnlich kritisch sind Statusfelder wie isPaid oder isApproved, die ausschließlich serverseitig durch Geschäftslogik gesetzt werden sollten. Auch Zuordnungsfelder wie userId, tenantId oder organizationId sind gefährdet, weil ein Angreifer damit theoretisch Datensätze einem fremden Konto zuordnen oder aus einem fremden Mandanten lesen könnte, wenn diese Felder ungeprüft aus dem Request übernommen werden.

Auch Doctrine-Formulare mit CSRF-Schutz sind nicht automatisch sicher, wenn das zugrunde liegende FormType alle Entity-Felder unreflektiert einbindet, statt bewusst nur die tatsächlich vom Client zu setzenden Felder zu deklarieren. Ein FormType ist ein guter Startpunkt für eine Allowlist, muss aber ebenso sorgfältig gepflegt werden wie ein DTO. Besonders bei generierten CRUD-Oberflächen, die automatisch aus dem Entity abgeleitet werden, schleicht sich diese Nachlässigkeit gerne ein, weil der Komfort der automatischen Generierung gegen die Sicherheit der expliziten Feldliste abgewogen werden muss.

5. DTOs statt direktem Entity-Binding in Symfony

In Symfony lassen sich DTOs elegant mit der Serializer-Komponente und Property-Validierung über den Symfony Validator kombinieren. Ein DTO enthält ausschließlich die Felder, die für einen bestimmten Anwendungsfall wirklich benötigt werden, inklusive eigener Validierungsregeln, unabhängig von den Constraints des Entitys.

Der Mapping-Schritt vom DTO auf das Entity erfolgt anschließend explizit im Controller oder in einem dedizierten Mapper-Service, mit klar benannten Settern für jedes erlaubte Feld. Dieser zusätzliche Schritt wirkt auf den ersten Blick wie mehr Code, reduziert aber die Angriffsfläche erheblich und macht die erlaubten Felder für jeden Entwickler auf einen Blick sichtbar.

6. Serializer-Gruppen als Alternative

Wer aus Aufwandsgründen keine eigenen DTOs für jeden Endpunkt anlegen möchte, kann in Symfony mit Serializer-Gruppen arbeiten. Dabei werden Entity-Properties mit Gruppen wie profile:write annotiert, und nur Felder dieser Gruppe werden beim Deserialisieren berücksichtigt. Sensible Felder wie roles bekommen bewusst keine Write-Gruppe zugewiesen.

Diese Lösung ist schneller umzusetzen als vollständige DTOs, birgt aber das Risiko, dass ein neues Feld versehentlich mit einer zu weiten Gruppe versehen wird. DTOs bleiben deshalb die robustere Lösung für sicherheitskritische Entities wie User oder Payment. Eine sinnvolle Faustregel ist, Serializer-Gruppen für unkritische, intern genutzte Entities zu verwenden und für alles, was Rollen, Zahlungen oder Berechtigungen betrifft, konsequent auf DTOs zu setzen.

7. Mass Assignment gezielt testen

Ein einfacher, aber wirkungsvoller Test schickt bei jedem schreibenden Endpunkt zusätzlich zu den erwarteten Feldern ein bekanntes sensibles Feld wie isAdmin oder role mit einem abweichenden Wert mit. Nach dem Request wird geprüft, ob sich der Wert des sensiblen Feldes tatsächlich verändert hat. Bleibt er unverändert, funktioniert die Allowlist wie erwartet.

Dieser Test lässt sich gut automatisieren und als Pflichtbestandteil für jeden neuen PATCH- oder PUT-Endpunkt in die CI-Pipeline aufnehmen, ähnlich wie der Zwei-Nutzer-Test bei BOLA-Schwachstellen. Sinnvoll ist zudem, den Test mit mehreren sensiblen Feldern gleichzeitig laufen zu lassen, um auch Kombinationen aus mehreren, jeweils für sich harmlos wirkenden Feldern abzudecken.

8. Häufige Fehler bei der Umsetzung

Ein häufiger Fehler ist, DTOs oder FormTypes zwar für neue Endpunkte konsequent einzusetzen, bei bestehenden, älteren Endpunkten aber weiterhin direktes Entity-Binding zu verwenden, weil eine Migration als zu aufwendig empfunden wird. Genau diese Altlasten sind in der Praxis die häufigste Quelle für Mass-Assignment-Vorfälle, da neue Sicherheitsstandards oft nur für neuen Code gelten und bestehender Code unangetastet bleibt, bis ein konkreter Vorfall die Nachbesserung erzwingt.

Ein weiterer Fehler ist, Admin-Bereiche für sicherer zu halten als öffentliche APIs und dort auf eine Allowlist zu verzichten. Admin-Oberflächen werden aber ebenso über HTTP-Requests angesprochen und sind damit genauso anfällig, sobald ein Angreifer ein kompromittiertes oder falsch berechtigtes Konto nutzt. Auch interne Tools, die nur von Kollegen genutzt werden, sind kein Freibrief für direktes Entity-Binding, da ein kompromittierter Mitarbeiter-Account dieselben Folgen haben kann wie ein externer Angriff.

9. Best Practices und Checkliste

Jeder schreibende Endpunkt sollte mit einer expliziten Allowlist arbeiten, sei es über ein DTO, ein FormType mit definierten Feldern oder Serializer-Gruppen. Sensible Felder wie Rollen, Preise oder Statuswerte dürfen ausschließlich durch dedizierte, serverseitige Endpunkte oder interne Services gesetzt werden, niemals über denselben Endpunkt wie unkritische Profildaten. Diese Trennung sollte auch architektonisch sichtbar sein, etwa durch getrennte Controller oder Namensräume für administrative und selbstverwaltete Operationen.

Ergänzend hilft ein automatisierter Test pro Endpunkt, der versucht, ein bekanntes sensibles Feld zu überschreiben, sowie ein Code-Review-Standard, der direktes Entity-Binding aus Request-Daten grundsätzlich ablehnt.

Feld Risiko bei Mass Assignment Blocklist-Ansatz Allowlist-Ansatz
isAdmin / role Selbst-Ernennung zum Administrator Muss aktiv gesperrt werden Existiert im DTO gar nicht
price Client bestimmt Verkaufspreis selbst Leicht vergessen Nur serverseitig berechenbar
isPaid / isApproved Status ohne Geschäftslogik gesetzt Fehleranfällig bei neuen Feldern Kein Setter im DTO vorhanden
createdAt / userId Fremde Zuordnung von Datensätzen Oft übersehen Wird serverseitig gesetzt, nie vom Client

Mironsoft

Security-Audits, OWASP-konforme Härtung und sichere Architektur

Anwendungen, die einem echten Angriffsversuch tatsächlich standhalten?

Wir prüfen bestehende Anwendungen auf klassische OWASP-Schwachstellen, unsichere Authentifizierung und fehlende Input-Validierung und bauen daraus eine Architektur, die Angriffsflächen strukturell reduziert statt nur einzelne Symptome zu flicken.

Security-Audit

OWASP Top 10, Auth-Flows und Input-Validierung systematisch auf Schwachstellen prüfen.

Sichere Architektur

Rate-Limiting, Verschlüsselung und Zugriffskontrollen von Grund auf richtig aufbauen.

Incident-Vorbereitung

Logging, Monitoring und Reaktionsprozesse für den Ernstfall etablieren.

10. Zusammenfassung

Mass Assignment

Ursache

Automatisches Binden aller Request-Felder an ein Entity.

Erkennung

Test mit bekanntem sensiblen Feld und abweichendem Wert.

Fix

DTOs oder Serializer-Gruppen als explizite Allowlist.

Prävention

Kein direktes Entity-Binding, konsequentes Code-Review.

11. FAQ: Mass Assignment

1Was bedeutet Mass Assignment genau?
Mass Assignment bezeichnet das automatische Uebernehmen aller Felder eines Requests in ein Entity oder Model, ohne dass geprüft wird, ob der Client dieses Feld überhaupt setzen darf.
2Warum ist ein Blocklist-Ansatz riskant?
Weil jedes neue sensible Feld aktiv zur Blockliste hinzugefügt werden muss. Wird das vergessen, ist die Lücke sofort wieder vorhanden, oft unbemerkt bis zu einem tatsächlichen Vorfall.
3Was ist der Vorteil von DTOs gegenüber direktem Entity-Binding?
Ein DTO kennt von vornherein nur die tatsächlich erlaubten Felder. Zusätzliche oder sensible Felder im Request können das Entity gar nicht erreichen, weil kein passendes Property existiert.
4Reichen Serializer-Gruppen als Schutz aus?
Serializer-Gruppen sind eine praktikable Alternative zu DTOs, erfordern aber sorgfältige Pflege, damit neue Felder nicht versehentlich einer zu weiten Schreibgruppe zugeordnet werden.
5Sind Admin-Bereiche von Mass Assignment ausgenommen?
Nein. Admin-Oberflächen werden über dieselben HTTP-Mechanismen angesprochen wie öffentliche APIs und sind bei fehlender Allowlist genauso verwundbar.
6Wie testet man gezielt auf Mass Assignment?
Indem bei einem Request zusätzlich zu den erwarteten Feldern ein bekanntes sensibles Feld mit abweichendem Wert mitgeschickt und anschließend geprüft wird, ob sich dieser Wert tatsächlich verändert hat.
7Welche Felder sind besonders gefährdet?
Rollenfelder, Preisfelder, Statusfelder wie isPaid oder isApproved sowie Zuordnungsfelder wie userId oder createdAt, die ausschließlich serverseitig gesetzt werden sollten.
8Muss man alte Endpunkte nachträglich migrieren?
Ja, bestehende Endpunkte mit direktem Entity-Binding sind in der Praxis die häufigste Ursache für Mass-Assignment-Vorfälle und sollten priorisiert auf DTOs oder Serializer-Gruppen umgestellt werden.
9Ist Mass Assignment nur bei PHP und Symfony relevant?
Nein, das Problem betrifft jedes Framework mit automatischem Objekt-Mapping aus Request-Daten, unabhängig von der verwendeten Programmiersprache.
10Wie hängt Mass Assignment mit Excessive Data Exposure zusammen?
Beide Schwachstellen entstehen durch zu großzügiges automatisches Mapping zwischen Entity und Request beziehungsweise Response, einmal beim Schreiben und einmal beim Lesen von Daten.