Guía de seguridad para ASP.NET Web API 2 OData

por Mike Wasson

En este tema se describen algunos de los problemas de seguridad que se deben tener en cuenta al exponer un conjunto de DataSet a través de OData para ASP.NET Web API 2 en ASP.NET 4. x.

Seguridad de EDM

La semántica de la consulta se basa en el Entity Data Model (EDM), no en los tipos de modelo subyacentes. Puede excluir una propiedad del EDM y no será visible para la consulta. Por ejemplo, supongamos que el modelo incluye un tipo de empleado con una propiedad salary. Es posible que desee excluir esta propiedad del EDM para ocultarla de los clientes.

Hay dos maneras de excluir una propiedad del EDM. Puede establecer el atributo [IgnoreDataMember] en la propiedad en la clase 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
}

También puede quitar la propiedad del EDM mediante programación:

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

Seguridad de las consultas

Un cliente malintencionado o Naive puede crear una consulta que tarda mucho tiempo en ejecutarse. En el peor de los casos, esto puede interrumpir el acceso a su servicio.

El atributo [Queryable] es un filtro de acción que analiza, valida y aplica la consulta. El filtro convierte las opciones de consulta en una expresión LINQ. Cuando el controlador OData devuelve un tipo IQueryable , el proveedor LINQ de IQueryable convierte la expresión LINQ en una consulta. Por lo tanto, el rendimiento depende del proveedor LINQ que se utiliza, y también de las características concretas del conjunto de datos o del esquema de la base de datos.

Para obtener más información sobre el uso de las opciones de consulta de OData en ASP.NET Web API, consulte compatibilidad con las opciones de consulta de oData.

Si sabe que todos los clientes son de confianza (por ejemplo, en un entorno empresarial) o si el conjunto de información es pequeño, el rendimiento de las consultas podría no ser un problema. De lo contrario, debe tener en cuenta las siguientes recomendaciones.

  • Pruebe el servicio con varias consultas y realice el perfil de la base de BD.

  • Habilitar la paginación controlada por el servidor, para evitar que se devuelva un conjunto de datos grande en una consulta. Para obtener más información, consulte paginación controlada por el servidor.

    // Enable server-driven paging.
    [Queryable(PageSize=10)]
    
  • ¿Necesita $filter y $orderby? Algunas aplicaciones pueden permitir la paginación del cliente mediante $top y $skip, pero deshabilitar las demás opciones de consulta.

    // Allow client paging but no other query options.
    [Queryable(AllowedQueryOptions=AllowedQueryOptions.Skip | 
                                   AllowedQueryOptions.Top)]
    
  • Considere la posibilidad de restringir $orderby a las propiedades de un índice clúster. La ordenación de datos grandes sin un índice clúster es lenta.

    // Set the allowed $orderby properties.
    [Queryable(AllowedOrderByProperties="Id,Name")] // Comma separated list
    
  • Número máximo de nodos: la propiedad MaxNodeCount en [Queryable] establece el número máximo de nodos permitido en el árbol de sintaxis $Filter. El valor predeterminado es 100, pero puede que desee establecer un valor inferior, ya que un gran número de nodos puede ser lento de la compilación. Esto es especialmente cierto si usa LINQ to Objects (es decir, consultas LINQ en una colección en memoria, sin usar un proveedor LINQ intermedio).

    // Set the maximum node count.
    [Queryable(MaxNodeCount=20)]
    
  • Considere la posibilidad de deshabilitar las funciones any () y All (), ya que pueden ser lentas.

    // Disable any() and all() functions.
    [Queryable(AllowedFunctions= AllowedFunctions.AllFunctions & 
        ~AllowedFunctions.All & ~AllowedFunctions.Any)]
    
  • Si alguna de las propiedades de cadena—contiene cadenas grandes, por ejemplo, una descripción del—producto o una entrada del blog considere la posibilidad de deshabilitar las funciones de cadena.

    // Disable string functions.
    [Queryable(AllowedFunctions=AllowedFunctions.AllFunctions & 
        ~AllowedFunctions.AllStringFunctions)]
    
  • Considere la posibilidad de no permitir el filtrado en las propiedades de navegación. El filtrado de las propiedades de navegación puede dar lugar a una combinación, que podría ser lenta, dependiendo del esquema de la base de datos. En el código siguiente se muestra un validador de consulta que impide el filtrado en las propiedades de navegación. Para obtener más información sobre los validadores de consultas, vea validación de consultas.

    // 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 la posibilidad de restringir las consultas de $filter escribiendo un validador personalizado para la base de datos. Por ejemplo, considere estas dos consultas:

    • Todas las películas con actores cuyo apellido empieza por ' A '.

    • Todas las películas publicadas en 1994.

      A menos que los actores indexen las películas, es posible que la primera consulta requiera que el motor de base de BD examine toda la lista de películas. Mientras que la segunda consulta podría ser aceptable, suponiendo que las películas se indexan por año de la versión.

      En el código siguiente se muestra un validador que permite filtrar en las propiedades "ReleaseYear" y "title", pero no en otras propiedades.

      // 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);
          }
      }
      
  • En general, tenga en cuenta qué $filter funciones necesita. Si los clientes no necesitan la expresividad total de $filter, puede limitar las funciones permitidas.