ASP.NET-Fehlkonfiguration: Unsachgemäße Modellvalidierung

Beschreibung

ASP.NET-Fehlkonfiguration: Unsachgemäße Modellvalidierung tritt auf, wenn eine ASP.NET-Anwendung das vom ASP.NET MVC- oder ASP.NET Core-Framework bereitgestellte Modellvalidierungs-Framework nicht verwendet oder falsch verwendet. ASP.NET bietet robuste integrierte Modellvalidierung durch Datenannotationen und den ModelState-Validierungsmechanismus. Wenn Entwickler diese Validierungsfunktionen nicht implementieren oder sie falsch implementieren, werden Benutzereingaben möglicherweise nicht ordnungsgemäß validiert, was zu verschiedenen Sicherheitsschwachstellen einschließlich Injection-Angriffen führt.

Risiko

Unsachgemäße Modellvalidierung in ASP.NET-Anwendungen hat erhebliche Sicherheitsauswirkungen. Ungeprüfte Eingaben können zu SQL-Injection-Angriffen führen. Cross-Site-Scripting (XSS)-Schwachstellen werden wahrscheinlicher. Geschäftslogik kann durch ungültige Eingaben umgangen werden. Prozesssteuerungsangriffe können möglich sein. Mass-Assignment-Schwachstellen können auftreten. Die Datenintegrität wird kompromittiert. Der Anwendungszustand kann beschädigt werden. Autorisierungsprüfungen können umgangen werden.

Lösung

Verwenden Sie immer das ASP.NET-Modellvalidierungs-Framework. Wenden Sie Datenannotations-Attribute auf Modelleigenschaften an ([Required], [StringLength], [Range], etc.). Prüfen Sie ModelState.IsValid vor der Verarbeitung von Eingaben. Implementieren Sie benutzerdefinierte Validierungsattribute für komplexe Regeln. Verwenden Sie [ValidateAntiForgeryToken] für Formularübermittlungen. Konfigurieren Sie Modellbindung, um erlaubte Eigenschaften einzuschränken. Verwenden Sie das [Bind]-Attribut oder View-Models, um Over-Posting zu verhindern. Aktivieren Sie clientseitige Validierung für bessere UX. Implementieren Sie globale Fehlerbehandlung für Validierungsfehler. Halten Sie das ASP.NET-Framework aktuell.

Häufige Auswirkungen

AuswirkungDetails
IntegritätBereich: Integrität

Unerwarteter Zustand - Ungeprüfte Eingaben führen zu Cross-Site-Scripting, Prozesssteuerung, SQL-Injection-Schwachstellen und anderen Injection-Angriffen.

Beispielcode

Verwundbarer Code

// Verwundbar: ASP.NET MVC ohne Modellvalidierung

public class UserController : Controller
{
    // Verwundbar: Keine Modellvalidierung
    [HttpPost]
    public ActionResult Register(string username, string email, string password)
    {
        // Direkte Verwendung von Eingaben ohne Validierung
        // Keine Längenprüfungen, Formatvalidierung oder Pflichtfeldprüfungen

        var user = new User
        {
            Username = username,  // Könnte null, leer oder zu lang sein
            Email = email,        // Könnte ungültiges E-Mail-Format sein
            Password = password   // Könnte schwach oder leer sein
        };

        // SQL-Injection möglich wenn keine parametrisierten Abfragen verwendet
        db.Users.Add(user);
        db.SaveChanges();

        return RedirectToAction("Success");
    }

    // Verwundbar: Modellbindung ohne Validierungsprüfung
    [HttpPost]
    public ActionResult UpdateProfile(UserProfile profile)
    {
        // ModelState.IsValid wird nicht geprüft!
        // Auch wenn das Modell Validierungsattribute hat, werden sie nicht durchgesetzt

        db.UserProfiles.Update(profile);
        db.SaveChanges();

        return RedirectToAction("Profile");
    }

    // Verwundbar: Over-Posting-Angriff möglich
    [HttpPost]
    public ActionResult EditUser(User user)
    {
        // Benutzer kann zusätzliche Felder wie "IsAdmin = true" POSTen
        // die an das Modell gebunden werden

        db.Entry(user).State = EntityState.Modified;
        db.SaveChanges();  // Kann Felder aktualisieren die nicht benutzereditierbar sein sollten

        return RedirectToAction("Index");
    }
}

// Modell ohne Validierungsattribute
public class User
{
    public int Id { get; set; }
    public string Username { get; set; }  // Keine Validierung
    public string Email { get; set; }     // Keine Validierung
    public string Password { get; set; }  // Keine Validierung
    public bool IsAdmin { get; set; }     // Kann per Mass-Assignment gesetzt werden!
}
// Verwundbar: ASP.NET Core API ohne Validierung

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    // Verwundbar: [ApiController] automatische Validierung nicht verwendet
    // oder ModelState nicht manuell geprüft
    [HttpPost]
    public async Task<IActionResult> Create(ProductDto product)
    {
        // Keine Validierung - product könnte ungültige Daten haben
        var entity = new Product
        {
            Name = product.Name,      // Könnte null sein
            Price = product.Price,    // Könnte negativ sein
            Category = product.Category
        };

        _context.Products.Add(entity);
        await _context.SaveChangesAsync();

        return Ok(entity);
    }

    // Verwundbar: Benutzerdefinierter Model-Binder umgeht Validierung
    [HttpPut("{id}")]
    public async Task<IActionResult> Update(int id, [ModelBinder(typeof(CustomBinder))] Product product)
    {
        // Benutzerdefinierter Binder löst möglicherweise keine Validierung aus

        _context.Entry(product).State = EntityState.Modified;
        await _context.SaveChangesAsync();

        return NoContent();
    }
}

// DTO ohne Validierung
public class ProductDto
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Category { get; set; }
}

Lösung

// Behoben: ASP.NET MVC mit ordnungsgemäßer Modellvalidierung

public class UserController : Controller
{
    // Behoben: Validiertes Modell verwenden
    [HttpPost]
    [ValidateAntiForgeryToken]  // CSRF-Schutz
    public ActionResult Register(RegisterViewModel model)
    {
        // Modellvalidierung prüfen
        if (!ModelState.IsValid)
        {
            // View mit Validierungsfehlern zurückgeben
            return View(model);
        }

        var user = new User
        {
            Username = model.Username,
            Email = model.Email,
            PasswordHash = HashPassword(model.Password)
        };

        db.Users.Add(user);
        db.SaveChanges();

        return RedirectToAction("Success");
    }

    // Behoben: ModelState-Validierung durchgesetzt
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult UpdateProfile(UserProfileViewModel model)
    {
        if (!ModelState.IsValid)
        {
            return View(model);
        }

        var profile = db.UserProfiles.Find(model.Id);
        if (profile == null || profile.UserId != User.GetUserId())
        {
            return NotFound();
        }

        // Nur erlaubte Felder aktualisieren
        profile.DisplayName = model.DisplayName;
        profile.Bio = model.Bio;

        db.SaveChanges();
        return RedirectToAction("Profile");
    }

    // Behoben: [Bind] verwenden um Over-Posting zu verhindern
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult EditUser([Bind("Id,Username,Email")] UserEditViewModel model)
    {
        // Nur Id, Username, Email können gebunden werden - IsAdmin nicht

        if (!ModelState.IsValid)
        {
            return View(model);
        }

        var user = db.Users.Find(model.Id);
        if (user == null)
        {
            return NotFound();
        }

        // Nur bestimmte Felder aktualisieren
        user.Username = model.Username;
        user.Email = model.Email;

        db.SaveChanges();
        return RedirectToAction("Index");
    }
}

// View Model mit Validierungsattributen
public class RegisterViewModel
{
    [Required(ErrorMessage = "Benutzername ist erforderlich")]
    [StringLength(50, MinimumLength = 3,
        ErrorMessage = "Benutzername muss 3-50 Zeichen haben")]
    [RegularExpression(@"^[a-zA-Z0-9_]+$",
        ErrorMessage = "Benutzername kann nur Buchstaben, Zahlen und Unterstriche enthalten")]
    public string Username { get; set; }

    [Required(ErrorMessage = "E-Mail ist erforderlich")]
    [EmailAddress(ErrorMessage = "Ungültiges E-Mail-Format")]
    public string Email { get; set; }

    [Required(ErrorMessage = "Passwort ist erforderlich")]
    [StringLength(128, MinimumLength = 8,
        ErrorMessage = "Passwort muss mindestens 8 Zeichen haben")]
    [DataType(DataType.Password)]
    public string Password { get; set; }

    [DataType(DataType.Password)]
    [Compare("Password", ErrorMessage = "Passwörter stimmen nicht überein")]
    public string ConfirmPassword { get; set; }
}

// Edit View Model - keine sensiblen Felder exponiert
public class UserEditViewModel
{
    public int Id { get; set; }

    [Required]
    [StringLength(50, MinimumLength = 3)]
    public string Username { get; set; }

    [Required]
    [EmailAddress]
    public string Email { get; set; }

    // Hinweis: IsAdmin ist NICHT enthalten - kann nicht per Mass-Assignment gesetzt werden
}
// Behoben: ASP.NET Core API mit Validierung

[ApiController]  // Aktiviert automatische Modellvalidierung
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    // Behoben: [ApiController] validiert ModelState automatisch
    // Gibt 400 Bad Request zurück wenn Validierung fehlschlägt
    [HttpPost]
    public async Task<IActionResult> Create([FromBody] CreateProductDto product)
    {
        // Mit [ApiController] wird ModelState automatisch geprüft
        // Ungültige Anfragen erreichen diesen Punkt nie

        var entity = new Product
        {
            Name = product.Name,
            Price = product.Price,
            Category = product.Category
        };

        _context.Products.Add(entity);
        await _context.SaveChangesAsync();

        return CreatedAtAction(nameof(GetById), new { id = entity.Id }, entity);
    }

    // Behoben: Explizite Validierung für mehr Kontrolle
    [HttpPut("{id}")]
    public async Task<IActionResult> Update(int id, [FromBody] UpdateProductDto product)
    {
        // Zusätzliche benutzerdefinierte Validierung
        if (id != product.Id)
        {
            return BadRequest("ID-Abweichung");
        }

        var entity = await _context.Products.FindAsync(id);
        if (entity == null)
        {
            return NotFound();
        }

        // Nur erlaubte Felder aus DTO aktualisieren
        entity.Name = product.Name;
        entity.Price = product.Price;
        entity.Category = product.Category;
        // Hinweis: andere Felder wie CreatedBy, CreatedDate werden nicht aktualisiert

        await _context.SaveChangesAsync();
        return NoContent();
    }
}

// DTO mit Validierungsattributen
public class CreateProductDto
{
    [Required(ErrorMessage = "Name ist erforderlich")]
    [StringLength(100, MinimumLength = 1,
        ErrorMessage = "Name muss 1-100 Zeichen haben")]
    public string Name { get; set; }

    [Required]
    [Range(0.01, 1000000, ErrorMessage = "Preis muss zwischen 0,01 und 1.000.000 liegen")]
    public decimal Price { get; set; }

    [Required]
    [RegularExpression(@"^(Electronics|Clothing|Food|Other)$",
        ErrorMessage = "Ungültige Kategorie")]
    public string Category { get; set; }
}

// Benutzerdefiniertes Validierungsattribut für komplexe Regeln
public class ValidCategoryAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext context)
    {
        var category = value as string;
        var dto = context.ObjectInstance as CreateProductDto;

        // Beispiel: Lebensmittel müssen Preis unter 100 haben
        if (category == "Food" && dto?.Price > 100)
        {
            return new ValidationResult("Lebensmittel dürfen nicht mehr als 100€ kosten");
        }

        return ValidationResult.Success;
    }
}

CVE-Beispiele

Diese CWE stellt ein Konfigurations-/Implementierungsproblem dar, das eine Grundursache für viele ASP.NET-Schwachstellen ist. Mass-Assignment-Schwachstellen in ASP.NET-Anwendungen resultieren oft aus unsachgemäßer Modellvalidierung.


Verwandte CWEs

  • CWE-1173: Unsachgemäße Verwendung eines Validierungs-Frameworks (übergeordnet)
  • CWE-20: Unsachgemäße Eingabevalidierung (verwandt)
  • CWE-915: Unsachgemäß kontrollierte Modifikation dynamisch bestimmter Objektattribute (Mass-Assignment)

Referenzen

  1. MITRE Corporation. "CWE-1174: ASP.NET Misconfiguration: Improper Model Validation." https://cwe.mitre.org/data/definitions/1174.html
  2. Microsoft Docs - Modellvalidierung in ASP.NET Core MVC
  3. OWASP - Mass Assignment