Una aplicación web es vulnerable a los ataques CSRF (Cross Site Request Forgery), también conocidos como XSRF o Session Riding, cuando no establece ningún mecanismo para verificar que una petición de un usuario confiable ha sido efectivamente realizada de forma intencionada por ese usuario, más sencillo: el ataque CSRF aprovecha que el usuario está autenticado en la aplicación vulnerable para hacer una petición en su nombre.
El filtro [ValidateAntiForgeryToken] nos ayudará a defendernos contra la falsificación de entidades entre sitios. Podemos añadir en nuestro formulario el helper AntiForgeryToken, que generará un token dentro de un campo oculto, por otro lado decoramos nuestro método de acción para el post del formulario con el filtro ValidateAntiForgeryToken.
Cuando el usuario rellene el formulario y envie la petición (submit), ASP.NET MVC comprobará que el token enviado es correcto. Esto lo hara comporando una cookie que habrá creado con el token contra el campo de formulario.
Uso del helper AntiForgeryToken en la vista
@using (Html.BeginForm()) { @Html.AntiForgeryToken() ... }Decoramos la acción del post del formulario con el filtro
[ValidateAntiForgeryToken] public ActionResult SubmitPost(ViewModel vm) { ... }
[OutputCache]
Permite guardar información en cache a fin de optimizar el rendimiento de la aplicación, el uso mas básico de este atributo se muestra
[OutputCache(Duration=60,VaryByParam="none")] public ActionResult Index() { var employees = db.Employees; return View(employees.ToList()); }
Sin embargo, algunas veces será necesario deshabilitar la cache para determinadas acciones (generalmente cuando se hace una llamada Ajax ciertos navegadores por defecto almacenan el resultado en cache, pudiendo producir un efecto no esperado en nuestras aplicaciones). Utilizando el mismo atributo, podremos deshabilitar la cache de las siguientes formas:
[OutputCache(NoStore = true, Duration = 0)] [OutputCache(Duration=0, Location = OutputCacheLocation.None, VaryByParam=”None”)]
Por defecto, al usar el atributo [OutputCache], el contenido se cachea en 3 ubicaciones: el servidor web en los servidores proxy intermedios en el navegador del cliente Podemos de todas formas controlar donde exactamente queremos que se cachee el contenido modificando la propiedad Location del atributo [OutputCache] La propiedad Location puede tener los siguientes valores: Any Client Downstream Server None ServerAndClient
Nunca guardes información del usuario en el servidor !!! Por defecto, el valor asignado a Location es Any. Sin embargo hay situaciones donde nos puede interesar cachear sólamente en el navegador o sólamente en el cliente. Por ejemplo, si cacheamos información personalizada por cada usuario, no queremos almacenar esa información en el servidor, pero si queremos mostrar información de una lista de productos desde la base de datos entonces puede servirnos el cachear esta información en el servidor. Como el cache es compartido por todos los usuarios de nuestra aplicación, no debemos cachear nunca contenido personalizado en el servidor. En todo caso, podemos enviar al navegador la directiva para que lo cachee en el cliente, y no se realice la petición al servidor. Por ejemplo:
Atributo CacheProfile En el web.config se debe agregar el perfil de cache con sus respectivos valores
Y así se utilizaría
[OutputCache(CacheProfile="Long")] public ActionResult Index() { var employees = db.Employees; return View(employees.ToList()); }El atributo [OutputCache] viene a ser útil en el área de Login
AllowAnonymous] OutputCache(NoStore = true, Duration = 0)] public ActionResult Login(string returnUrl) { ViewBag.ReturnUrl = returnUrl; return View(); }
[ValidateInput]
Permite indicar cuándo la información recibida debe ser validada para evitar la entrada de contenido potencialmente peligroso, como etiquetas de marcado o scripts, de forma muy similar a la directiva ValidateRequest utilizada en páginas Webforms. Por defecto, ASP.NET MVC framework validará las entradas a no ser que indiquemos lo contrario:
[ValidateInput(false)] public ActionResult Update(Friend friend) { // ... }Este tipo de validación puede ayudar a evitar ataques de inyección de scripts en nuestros sitios web. Hasta ASP.NET MVC 2 esta era la única vía para evitar la recepción de contenido potencialmente peligroso, y realmente era algo limitada, puesto que la validación se realizaba a nivel de acción. Si en un formulario existían unos campos que debían ser validados y otros que no, era necesario desactivarla a nivel de la acción completa. En MVC 3 se introdujo otra posibilidad, bastante más acertada, que permite indicar exactamente qué propiedades son las que queremos que no sean validadas. Así, por defecto, serán validadas todas menos las que incorporen el atributo [AllowHtml] de la siguiente forma:
public class News { [Required] public string Title { get; set; } [Required, AllowHtml] public string Text { get; set; } public DateTine Date { get; set; } }
[HandleError]
El atributo [HandleError] permite especificar el tratamiento que se dará en caso de producirse excepciones en las acciones a las que se aplica, normalmente enviar al usuario una vista describiendo el problema. Por defecto si se produce un error en la aplicación, ASP.NET y al utilizar el atributo [HandleError] sin parámetros sobre una acción provocará que todas las excepciones de tipo System.Exception (o en otras palabras, todas) sean tratadas de la misma forma, esto es, redirigiendo a la vista Error situada en Views/shared de nuestro proyecto. Podemos cambiar este coportamiento predeterminado de este filtro ajustando varias de sus propiedades. ExceptionType: Para indicar los tipos de excepción que controlará el filtro. View: Nombre de vista que utilizará el filtro para mostrar el error. Master: Especifica el nombre de la vista maestra que se va a utilizar, si es que vamos a utilizar alguna vista maestra. Order: Orden en el que se aplican los filtros. Vamos a hablar sobre esto más adelante en este artículo. Para habilitar el uso de los errores personalizados hay que añadir el elemento customErrors a system.web del archivo web.config.
; ;
[HandleError] public ActionResult Test() { throw new Exception(); // Shows the view "Error" }También es posible concretar el tipo de excepción que se desea capturar, ofreciendo la posibilidad de mostrar al usuario vistas distintas en cada caso. Por ejemplo, en el siguiente código se indica al framework que si en el método Divide() se produce un error de división por cero, se mostrará al usuario la vista "MathError", mientras que si la excepción lanzada es de tipo ArgumentException se retornará una vista distinta:
[HandleError(ExceptionType=typeof(DivisionByZeroException), View="MathError")] [HandleError(ExceptionType=typeof(ArgumentException), View="ArgumentError")] public ActionResult Divide(int a, int b) { // ... }
HandleError también puede ser establecido a nivel de controlador, aplicándose a todas las acciones de éste. El manejador de errores que se empleará en una acción será el primero que corresponda al tipo de excepción producida, teniendo en cuenta tanto los filtros HandleError especificados a nivel de clase controlador como a nivel de método, y siempre siguiendo un orden, que es posible modificar utilizando la propiedad Order:
[HandleError(View="GenericError", Order=10)] public class CalculatorController: Controller { [HandleError(ExceptionType=typeof(DivisionByZeroException), Order=1, View="MathError")] public ActionResult Divide(int a, int b) { // ... } }
En este ejemplo, una excepción de división por cero será gestionada como se indica a nivel de acción, puesto que estamos forzando que sea la primera en procesarse. Si no se indica el orden, en este caso se enviaría al usuario el error genérico, puesto que éste es el primero en definirse a nivel de clase. El orden de proceso de los filtros no es determinista cuando no se define un orden específico, es decir, no podemos saber cuál de ellos se va a ejecutar primero. Por tanto, en escenarios en los que las acciones sean afectadas por más de un atributo de este tipo, lo más recomendable es establecer el orden.
Crear atributos personalizados Un filtro no es más que un objeto que tiene la posibilidad de ejecutar código durante cuatro posibles instantes del ciclo de vida de una petición: Antes de ejecutar la acción Después de ejecutar la acción Antes de generar el resultado Después de generar el resultado Para crear nuestro filtro personalizado deberemos dirigirnos a la carpeta Filters de nuestra aplicación, hacer click derecho y seleccionar Add > Class… A continuación asignaremos un nombre a la clase. Como podemos ver en la clase que ya existía en la carpeta Filters, su nomenclatura indica que debe finalizar con el sufijo Attribute para poder ser utilizado como tal en MVC. Por lo tanto, le asignamos un nombre que termine con ese sufijo, una vez creada la clase, dado que se va a tratar de un ActionFilter, haremos que herede de la clase ActionFilterAttribute, añadiendo la cláusula using correspondiente (System.Web.Mvc). Existen un buen número de filtros que el Framework nos proporciona por defecto, pero además, tenemos disponible la capacidad de crear nuestros propios filtros extendiendo una interfaz que se corresponde con uno de los 4 tipos de filtros que el Framework permite implementar. Estos cuatro tipos son los siguientes Filtros de autorización. Son ejecutados antes de las acciones. Implementan la interfaz IAuthorizationFilter(AuthorizeAttribute, RequireHttpsAttribute, ValidateInputAttribute, ValidateAntiForgeryTokenAttribute, ChildActionOnlyAttribute) Filtros de acción: Se ejecutan antes y después de invocar una acción. Implementan la interfaz IActionFilter(OnActionExcecuting, OnActionExecuted) Filtros de resultados: Se ejecutan antes y después de ejecutar un ActionResult que devuelve algo desde una acción. Implementan IResultFilter(OnResultExcecuting, OnResultExecuted). Filtros de error. Se ejecutan cuando se produce una excepción al ejecutar una acción, en el retorno de un resultado o bien en alguno de los filtros de dicha acción. Implementan la interfaz IExceptionFilter Para crear un filtro personalizado podemos hacer dos cosas: 1) Heredar de la clase abstracta System.Web.Mvc.FilterAttribute e implementar uno o varios de los interfaces anteriores dependiendo de en qué momento queremos que nuestro código se ejecute y tomar el control de la acción. 2) Heredar de alguno de los filtros que ya existen en el Framework y sobreescribir sus métodos Aplicando un filtro a toda la aplicación Si en lugar de aplicar la operación de logging a una acción o controlador concreto quisiéramos que se aplicara a todas y cada una de las acciones de nuestra aplicación, tendremos que cambiar el lugar donde asignamos el filtro. Eliminaremos por lo tanto el atributo [Log4Net] de la acción actual y echamos un ojo al fichero que controla las acciones globales de la aplicación: global.asax.cs.
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); AuthConfig.RegisterAuth(); // Iniciamos log4net log4net.Config.XmlConfigurator.Configure(); }
Observamos que una de las líneas de código es la encargada de registrar los filtros globales. Esto se realiza invocando el método FilterConfig.RegisterGlobalFilters. Si nos situamos sobre el método y pulsamos la tecla F12, nos llevará directamente al código de este método, que se encontrará en la carpeta App_Start/FilterConfig y tendrá el siguiente aspecto:
public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); } }
Como podremos imaginar, para añadir nuestro filtro bastará con añadir una línea similar a la existente indicando el filtro que creamos previamente (sin olvidarnos de la cláusula using), dejando el código de la siguiente manera:
public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); filters.Add(new Log4NetAttribute()); } }Con esta operación, cada vez que una acción sea ejecutada, nuestro filtro entrará en acción.