Diretrizes de segurança para ASP.NET Web API 2 OData

por Mike Wasson

Este tópico descreve alguns dos problemas de segurança que você deve considerar ao expor um conjunto de dados por meio do OData para ASP.NET Web API 2 no ASP.NET 4.x.

Segurança do EDM

A semântica de consulta é baseada no EDM (modelo de dados de entidade), não nos tipos de modelo subjacentes. Você pode excluir uma propriedade do EDM e ela não ficará visível para a consulta. Por exemplo, suponha que seu modelo inclua um tipo de funcionário com uma propriedade Wage. Talvez você queira excluir essa propriedade do EDM para ocultá-la dos clientes.

Há duas maneiras de excluir uma propriedade do EDM. Você pode definir o atributo [IgnoreDataMember] na propriedade na classe de modelo:

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

Você também pode remover a propriedade do EDM programaticamente:

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

Segurança de consulta

Um cliente mal-intencionado ou ingênuo pode ser capaz de construir uma consulta que leva muito tempo para ser executada. Na pior das hipóteses, isso pode interromper o acesso ao seu serviço.

O atributo [Queryable] é um filtro de ação que analisa, valida e aplica a consulta. O filtro converte as opções de consulta em uma expressão LINQ. Quando o controlador OData retorna um tipo IQueryable , o provedor LINQ IQueryable converte a expressão LINQ em uma consulta. Portanto, o desempenho depende do provedor LINQ usado e também das características específicas do seu conjunto de dados ou esquema de banco de dados.

Para obter mais informações sobre como usar opções de consulta OData no ASP.NET Web API, consulte Como dar suporte às opções de consulta OData.

Se você souber que todos os clientes são confiáveis (por exemplo, em um ambiente corporativo) ou se o conjunto de dados for pequeno, o desempenho da consulta poderá não ser um problema. Caso contrário, você deve considerar as recomendações a seguir.

  • Teste seu serviço com várias consultas e crie o perfil do BD.

  • Habilite a paginação controlada pelo servidor para evitar o retorno de um grande conjunto de dados em uma consulta. Para obter mais informações, consulte Paginação controlada por servidor.

    // Enable server-driven paging.
    [Queryable(PageSize=10)]
    
  • Você precisa de $filter e $orderby? Alguns aplicativos podem permitir a paginação do cliente, usando $top e $skip, mas desabilitar as outras opções de consulta.

    // Allow client paging but no other query options.
    [Queryable(AllowedQueryOptions=AllowedQueryOptions.Skip | 
                                   AllowedQueryOptions.Top)]
    
  • Considere restringir $orderby a propriedades em um índice clusterizado. A classificação de dados grandes sem um índice clusterizado é lenta.

    // Set the allowed $orderby properties.
    [Queryable(AllowedOrderByProperties="Id,Name")] // Comma separated list
    
  • Contagem máxima de nós: a propriedade MaxNodeCount em [Queryable] define o número máximo de nós permitidos na árvore de sintaxe $filter. O valor padrão é 100, mas talvez você queira definir um valor mais baixo, pois um grande número de nós pode ser lento para compilar. Isso é particularmente verdadeiro se você estiver usando LINQ to Objects (ou seja, consultas LINQ em uma coleção na memória, sem o uso de um provedor LINQ intermediário).

    // Set the maximum node count.
    [Queryable(MaxNodeCount=20)]
    
  • Considere desabilitar as funções any() e all(), pois elas podem ser lentas.

    // Disable any() and all() functions.
    [Queryable(AllowedFunctions= AllowedFunctions.AllFunctions & 
        ~AllowedFunctions.All & ~AllowedFunctions.Any)]
    
  • Se alguma propriedade de cadeia de caracteres contiver cadeias de caracteres grandes, por exemplo, uma descrição do produto ou uma entrada de blog, considere desabilitar as funções de cadeia de caracteres.

    // Disable string functions.
    [Queryable(AllowedFunctions=AllowedFunctions.AllFunctions & 
        ~AllowedFunctions.AllStringFunctions)]
    
  • Considere não permitir a filtragem nas propriedades de navegação. A filtragem em propriedades de navegação pode resultar em uma junção, o que pode ser lento, dependendo do esquema do banco de dados. O código a seguir mostra um validador de consulta que impede a filtragem em propriedades de navegação. Para obter mais informações sobre validadores de consulta, consulte Validação de consulta.

    // 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");
        }
    }
    
  • Considere restringir $filter consultas escrevendo um validador personalizado para seu banco de dados. Por exemplo, considere estas duas consultas:

    • Todos os filmes com atores cujo sobrenome começa com 'A'.

    • Todos os filmes lançados em 1994.

      A menos que os filmes sejam indexados por atores, a primeira consulta pode exigir que o mecanismo de BD examine toda a lista de filmes. Enquanto a segunda consulta pode ser aceitável, supondo que os filmes sejam indexados por ano de lançamento.

      O código a seguir mostra um validador que permite a filtragem nas propriedades "ReleaseYear" e "Title", mas nenhuma outra propriedade.

      // 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);
          }
      }
      
  • Em geral, considere quais $filter funções necessárias. Se seus clientes não precisarem de expressividade total de $filter, você poderá limitar as funções permitidas.