Condividi tramite


Linee guida sulla sicurezza per API Web ASP.NET 2 OData

di Mike Wasson

Questo argomento descrive alcuni dei problemi di sicurezza da considerare quando si espone un set di dati tramite OData per API Web ASP.NET 2 in ASP.NET 4.x.

Sicurezza EDM

La semantica della query si basa sul modello di dati dell'entità (EDM), non sui tipi di modello sottostanti. È possibile escludere una proprietà da EDM e non sarà visibile alla query. Si supponga, ad esempio, che il modello includa un tipo employee con una proprietà Salary. È possibile escludere questa proprietà da EDM per nasconderla dai client.

Esistono due modi per escludere una proprietà da EDM. È possibile impostare l'attributo [IgnoreDataMember] sulla proprietà nella classe model:

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

È anche possibile rimuovere la proprietà da EDM a livello di codice:

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

Sicurezza delle query

Un client dannoso o ingenuo può essere in grado di costruire una query che richiede molto tempo per l'esecuzione. Nel peggiore dei casi questo può interrompere l'accesso al servizio.

L'attributo [Queryable] è un filtro azione che analizza, convalida e applica la query. Il filtro converte le opzioni di query in un'espressione LINQ. Quando il controller OData restituisce un tipo IQueryable , il provider LINQ IQueryable converte l'espressione LINQ in una query. Di conseguenza, le prestazioni dipendono dal provider LINQ usato e anche dalle caratteristiche specifiche del set di dati o dello schema del database.

Per altre informazioni sull'uso delle opzioni di query OData in API Web ASP.NET, vedere Supporto delle opzioni di query OData.

Se si è certi che tutti i client sono attendibili (ad esempio, in un ambiente aziendale) o se il set di dati è ridotto, le prestazioni delle query potrebbero non essere un problema. In caso contrario, è consigliabile prendere in considerazione le raccomandazioni seguenti.

  • Testare il servizio con diverse query e profilarlo nel database.

  • Abilitare il paging basato su server per evitare di restituire un set di dati di grandi dimensioni in una query. Per altre informazioni, vedere Paging basato su server.

    // Enable server-driven paging.
    [Queryable(PageSize=10)]
    
  • Sono necessari $filter e $orderby? Alcune applicazioni potrebbero consentire il paging client, usando $top e $skip, ma disabilitare le altre opzioni di query.

    // Allow client paging but no other query options.
    [Queryable(AllowedQueryOptions=AllowedQueryOptions.Skip | 
                                   AllowedQueryOptions.Top)]
    
  • Valutare la possibilità di limitare $orderby alle proprietà in un indice cluster. L'ordinamento di dati di grandi dimensioni senza un indice cluster è lento.

    // Set the allowed $orderby properties.
    [Queryable(AllowedOrderByProperties="Id,Name")] // Comma separated list
    
  • Numero massimo di nodi: la proprietà MaxNodeCount in [Queryable] imposta i nodi numero massimo consentiti nell'albero della sintassi $filter. Il valore predefinito è 100, ma può essere necessario impostare un valore inferiore, perché un numero elevato di nodi può essere lento da compilare. Ciò è particolarmente vero se si usa LINQ to Objects (ad esempio, le query LINQ su una raccolta in memoria, senza l'uso di un provider LINQ intermedio).

    // Set the maximum node count.
    [Queryable(MaxNodeCount=20)]
    
  • È consigliabile disabilitare le funzioni any() e all(), in quanto possono essere lente.

    // Disable any() and all() functions.
    [Queryable(AllowedFunctions= AllowedFunctions.AllFunctions & 
        ~AllowedFunctions.All & ~AllowedFunctions.Any)]
    
  • Se le proprietà stringa contengono stringhe di grandi dimensioni, ad esempio una descrizione del prodotto o una voce di blog, valutare la possibilità di disabilitare le funzioni stringa.

    // Disable string functions.
    [Queryable(AllowedFunctions=AllowedFunctions.AllFunctions & 
        ~AllowedFunctions.AllStringFunctions)]
    
  • Valutare la possibilità di impedire il filtro sulle proprietà di navigazione. Il filtro delle proprietà di navigazione può comportare un join, che potrebbe essere lento, a seconda dello schema del database. Il codice seguente illustra un validator di query che impedisce di filtrare le proprietà di navigazione. Per altre informazioni sui validator di query, vedere Convalida delle query.

    // 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");
        }
    }
    
  • È consigliabile limitare $filter query scrivendo un validator personalizzato per il database. Si considerino ad esempio queste due query:

    • Tutti i film con attori il cui cognome inizia con 'A'.

    • Tutti i film sono stati pubblicati nel 1994.

      A meno che i film non vengano indicizzati dagli attori, la prima query potrebbe richiedere al motore di database di analizzare l'intero elenco di film. Mentre la seconda query potrebbe essere accettabile, supponendo che i film vengano indicizzati per anno di rilascio.

      Il codice seguente mostra un validator che consente di filtrare le proprietà "ReleaseYear" e "Title", ma non altre proprietà.

      // 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);
          }
      }
      
  • In generale, considerare quali $filter funzioni necessarie. Se i client non necessitano dell'espressività completa di $filter, è possibile limitare le funzioni consentite.