Diretrizes de segurança para o 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 DataSet por meio do OData para ASP.NET Web API 2 no ASP.NET 4. x.

Segurança do EDM

A semântica de consulta se baseia 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 será visível para a consulta. Por exemplo, suponha que seu modelo inclua um tipo de funcionário com uma propriedade salário. 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ênua 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 [passível de consulta] é 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 de LINQ do IQueryable converte a expressão LINQ em uma consulta. Portanto, o desempenho depende do provedor LINQ que é 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 as opções de consulta OData no ASP.NET Web API, consulte Opções de consulta OData de suporte.

Se você souber que todos os clientes são confiáveis (por exemplo, em um ambiente corporativo), ou se o conjunto de seus conjuntos de seus 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 perfil do BD.

  • Habilite a paginação baseada em servidor para evitar retornar um conjunto de dados grande em uma consulta. Para obter mais informações, consulte paginação baseada em servidor.

    // Enable server-driven paging.
    [Queryable(PageSize=10)]
    
  • Você precisa de $filter e $orderby? Alguns aplicativos podem permitir a paginação de cliente, usando $top e $skip, mas desabilite 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 [passível de consulta] define o número máximo de nós permitidos na árvore de sintaxe de $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 quaisquer propriedades de cadeia de caracteres—contiverem cadeias grandes, por exemplo,—uma descrição de 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 a despermissão de filtragem nas propriedades de navegação. A filtragem nas propriedades de navegação pode resultar em uma junção, que pode ser lenta, dependendo do seu esquema de banco de dados. O código a seguir mostra um validador de consulta que impede a filtragem nas propriedades de navegação. Para obter mais informações sobre os 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 as consultas de $filter escrevendo um validador que é personalizado para seu banco de dados. Por exemplo, considere estas duas consultas:

    • Todos os filmes com atores cujo último nome 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 banco de BD examine toda a lista de filmes. Enquanto a segunda consulta pode ser aceitável, pressupondo 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 de que você precisa. Se os clientes não precisarem da expressividade completa do $filter, você poderá limitar as funções permitidas.