Sicherheitsleitfaden für ASP.NET-Web-API 2 OData

von Mike Wasson

In diesem Thema werden einige der Sicherheitsprobleme beschrieben, die Sie beim Verfügbarmachen eines Datasets über OData für ASP.NET-Web-API 2 auf ASP.NET 4.x berücksichtigen sollten.

EDM-Sicherheit

Die Abfragesemantik basiert auf dem Entitätsdatenmodell (Entity Data Model, EDM), nicht auf den zugrunde liegenden Modelltypen. Sie können eine Eigenschaft aus dem EDM ausschließen, und sie ist für die Abfrage nicht sichtbar. Angenommen, Ihr Modell enthält einen Mitarbeitertyp mit einer Gehaltseigenschaft. Möglicherweise möchten Sie diese Eigenschaft aus dem EDM ausschließen, um sie vor Clients auszublenden.

Es gibt zwei Möglichkeiten, eine Eigenschaft aus dem EDM auszuschließen. Sie können das [IgnoreDataMember] -Attribut für die -Eigenschaft in der Modellklasse festlegen:

public class Employee
{
    public string Name { get; set; }
    public string Title { get; set; }
    [IgnoreDataMember]
    public decimal Salary { get; set; } // Not visible in the EDM
}

Sie können die Eigenschaft auch programmgesteuert aus dem EDM entfernen:

var employees = modelBuilder.EntitySet<Employee>("Employees");
employees.EntityType.Ignore(emp => emp.Salary);

Abfragesicherheit

Ein böswilliger oder naive Client kann möglicherweise eine Abfrage erstellen, deren Ausführung sehr lange dauert. Im schlimmsten Fall kann dies den Zugriff auf Ihren Dienst unterbrechen.

Das [Queryable]- Attribut ist ein Aktionsfilter, der die Abfrage analysiert, überprüft und anwendet. Der Filter konvertiert die Abfrageoptionen in einen LINQ-Ausdruck. Wenn der OData-Controller einen IQueryable-Typ zurückgibt, konvertiert der LINQ-Anbieter IQueryable den LINQ-Ausdruck in eine Abfrage. Daher hängt die Leistung vom verwendeten LINQ-Anbieter und auch von den besonderen Merkmalen Ihres Datasets oder Datenbankschemas ab.

Weitere Informationen zur Verwendung von OData-Abfrageoptionen in ASP.NET-Web-API finden Sie unter Unterstützen von OData-Abfrageoptionen.

Wenn Sie wissen, dass alle Clients vertrauenswürdig sind (z. B. in einer Unternehmensumgebung), oder wenn Ihr Dataset klein ist, stellt die Abfrageleistung möglicherweise kein Problem dar. Andernfalls sollten Sie die folgenden Empfehlungen berücksichtigen.

  • Testen Sie Ihren Dienst mit verschiedenen Abfragen, und erstellen Sie ein Profil für die Datenbank.

  • Aktivieren Sie servergesteuertes Paging, um zu vermeiden, dass ein großes Dataset in einer Abfrage zurückgegeben wird. Weitere Informationen finden Sie unter Servergesteuertes Paging.

    // Enable server-driven paging.
    [Queryable(PageSize=10)]
    
  • Benötigen Sie $filter und $orderby? Einige Anwendungen können clientseitiges Paging mithilfe von $top und $skip zulassen, aber die anderen Abfrageoptionen deaktivieren.

    // Allow client paging but no other query options.
    [Queryable(AllowedQueryOptions=AllowedQueryOptions.Skip | 
                                   AllowedQueryOptions.Top)]
    
  • Erwägen Sie, $orderby auf Eigenschaften in einem gruppierten Index einzuschränken. Das Sortieren großer Daten ohne gruppierten Index ist langsam.

    // Set the allowed $orderby properties.
    [Queryable(AllowedOrderByProperties="Id,Name")] // Comma separated list
    
  • Maximale Knotenanzahl: Die MaxNodeCount-Eigenschaft für [Queryable] legt die maximale Anzahl von Knoten fest, die in der $filter Syntaxstruktur zulässig sind. Der Standardwert ist 100. Möglicherweise möchten Sie jedoch einen niedrigeren Wert festlegen, da die Kompilierung einer großen Anzahl von Knoten möglicherweise langsam ist. Dies gilt insbesondere, wenn Sie LINQ to Objects verwenden (d. h. LINQ-Abfragen für eine Sammlung im Arbeitsspeicher, ohne dass ein LINQ-Zwischenanbieter verwendet wird).

    // Set the maximum node count.
    [Queryable(MaxNodeCount=20)]
    
  • Erwägen Sie, die Funktionen any() und all() zu deaktivieren, da diese langsam sein können.

    // Disable any() and all() functions.
    [Queryable(AllowedFunctions= AllowedFunctions.AllFunctions & 
        ~AllowedFunctions.All & ~AllowedFunctions.Any)]
    
  • Wenn Zeichenfolgeneigenschaften große Zeichenfolgen enthalten , z. B. eine Produktbeschreibung oder einen Blogeintrag, sollten Sie die Zeichenfolgenfunktionen deaktivieren.

    // Disable string functions.
    [Queryable(AllowedFunctions=AllowedFunctions.AllFunctions & 
        ~AllowedFunctions.AllStringFunctions)]
    
  • Erwägen Sie, die Filterung für Navigationseigenschaften nicht mehr zu verwenden. Das Filtern nach Navigationseigenschaften kann zu einer Verknüpfung führen, die je nach Datenbankschema langsam sein kann. Der folgende Code zeigt eine Abfrageüberprüfung, die das Filtern nach Navigationseigenschaften verhindert. Weitere Informationen zu Abfragevalidatoren finden Sie unter Abfragevalidierung.

    // Validator to prevent filtering on navigation properties.
    public class MyFilterQueryValidator : FilterQueryValidator
    {
        public override void ValidateNavigationPropertyNode(
            Microsoft.Data.OData.Query.SemanticAst.QueryNode sourceNode, 
            Microsoft.Data.Edm.IEdmNavigationProperty navigationProperty, 
            ODataValidationSettings settings)
        {
            throw new ODataException("No navigation properties");
        }
    }
    
  • Erwägen Sie, $filter Abfragen einzuschränken, indem Sie ein Validierungssteuerelement schreiben, das für Ihre Datenbank angepasst ist. Betrachten Sie beispielsweise die folgenden beiden Abfragen:

    • Alle Filme mit Schauspielern, deren Nachname mit "A" beginnt.

    • Alle Filme wurden 1994 veröffentlicht.

      Sofern Filme nicht von Schauspielern indiziert werden, erfordert die erste Abfrage möglicherweise, dass die DATENBANK-Engine die gesamte Liste der Filme überprüft. Während die zweite Abfrage akzeptabel sein kann, wird vorausgesetzt, dass Filme nach Releasejahr indiziert werden.

      Der folgende Code zeigt eine Validierung, die das Filtern nach den Eigenschaften "ReleaseYear" und "Title" ermöglicht, aber keine anderen Eigenschaften.

      // Validator to restrict which properties can be used in $filter expressions.
      public class MyFilterQueryValidator : FilterQueryValidator
      {
          static readonly string[] allowedProperties = { "ReleaseYear", "Title" };
      
          public override void ValidateSingleValuePropertyAccessNode(
              SingleValuePropertyAccessNode propertyAccessNode,
              ODataValidationSettings settings)
          {
              string propertyName = null;
              if (propertyAccessNode != null)
              {
                  propertyName = propertyAccessNode.Property.Name;
              }
      
              if (propertyName != null && !allowedProperties.Contains(propertyName))
              {
                  throw new ODataException(
                      String.Format("Filter on {0} not allowed", propertyName));
              }
              base.ValidateSingleValuePropertyAccessNode(propertyAccessNode, settings);
          }
      }
      
  • Im Allgemeinen sollten Sie überlegen, welche $filter Funktionen Sie benötigen. Wenn Ihre Clients nicht die volle Ausdrucksstärke von $filter benötigen, können Sie die zulässigen Funktionen einschränken.