Compartir a través de


Seguridad en ASP.NET

Protección de las aplicaciones ASP.NET

Adam Tuliper

En el último artículo analicé la importancia de incorporar la seguridad en las aplicaciones web y analicé algunos tipos de ataques como la inyección de código SQL y la alteración de parámetros y mostré cómo prevenirlos (msdn.microsoft.com/magazine/hh580736). En esta entrega ahondaré en dos tipos más de ataques comunes para completar un poco el arsenal para proteger las aplicaciones: el scripting entre sitios (XSS) y la falsificación de solicitud entre sitios (CSRF).

Puede que se pregunte: ¿Por qué no usar simplemente un detector de seguridad de producción? Los detectores son unas herramientas excelentes para encontrar los objetivos fáciles de alcanzar, y se prestan especialmente bien para descubrir problemas de configuración en los sistemas y las aplicaciones, pero no pueden conocer la aplicación tan bien como usted lo hace. Es por esto, que resulta imprescindible que se familiarice con los posibles problemas de seguridad, se tome el tiempo de revisar sus aplicaciones e incorpore la seguridad directamente dentro de su ciclo de desarrollo del software.

Scripting entre sitios

¿Qué es? El XSS es un ataque en el que se inyecta un script con malas intenciones en la sesión del explorador del usuario, generalmente sin su conocimiento. Muchas personas se han familiarizado con este tipo de ataques debido a los incidentes en que varios sitios grandes de redes sociales fueron víctima de ataques y a continuación se publicaron muchos mensajes que no habían sido autorizados por los usuarios. Si un atacante publica un script malintencionado y logra que el explorador lo ejecute, entonces ese script se ejecuta en el contexto de la sesión de la víctima. Esto permite al atacante hacer básicamente lo que quiera con el DOM, como mostrar, por ejemplo, cuadros de diálogo de inicio de sesión falsos o hurtar las cookies. Este tipo de ataque incluso podría instalar un registrador de claves en HTML en la página actual para enviar las entradas de esa ventana en forma continua a algún sitio remoto.

¿Cómo se realiza? El XSS se aprovecha con varios métodos, y todos dependen de salidas que no cuentan con secuencias de escape, o donde estas son inadecuadas. Veamos el caso de una aplicación que tiene que mostrar un mensaje sencillo de estado al usuario final. Este mensaje normalmente se pasa en la cadena de consulta, como podemos observar en la Figura 1.

Query String Message
Figura 1 Mensaje en una cadena de consulta

Esta técnica frecuentemente se emplea después de una redirección para mostrarle al usuario algún tipo de estado, como por ejemplo el mensaje Profile Saved de la Figura 1. El mensaje se lee de la cadena de consulta y se escribe directamente en la página. Si la salida no está codificada como HTML, cualquiera puede inyectar fácilmente algún código JavaScript en lugar del mensaje de estado. Este tipo de ataque se considera un ataque XSS reflejado, ya que cualquier cosa que se encuentre en la cadena de consulta vuelve para mostrarse en la página. En un ataque persistente el script malintencionado se almacena, generalmente en una base de datos o una cookie.

En la Figura 1 puede observar que este identificador URI acepta un parámetro llamado msg. La página web para este URI deberá contener algún código similar al siguiente, que simplemente escribe la variable sin codificar en la página:

    <div class="messages"> <%=Request.QueryString["msg"]%></div>

Si reemplaza “Profile Saved” con el script que aparece en la Figura 2 emergerá la función de alerta en el explorador, gracias al script que se encuentra en la cadena de consulta. En este caso, el elemento clave en la vulnerabilidad de seguridad es que los resultados no están codificados como HTML, y por lo tanto el explorador lee la etiqueta <script> como JavaScript válido y la ejecuta. Esto claramente no era lo que el desarrollador tenía en mente.

Injecting Script into the URI
Figura 2 Inyección de un script en el URI

Pues bien, emerge un mensaje de alerta ¿y qué? Desarrollemos el ejemplo un poco más, tal como vemos en la Figura 3. Tenga en cuenta que disminuí el ataque con fines ilustrativos; esta sintaxis no es exactamente correcta, pero con una pequeña modificación se podría convertir en un ataque real.

Creating a Malicious Attack
Figura 3 Creación de un ataque malintencionado

Un ataque como este provocaría que el usuario reciba un cuadro de diálogo de inicio de sesión falso, y escriba despreocupadamente sus credenciales. En este caso, se descarga un script remoto, lo que generalmente está permitido por la configuración de seguridad predeterminada de los exploradores. Este tipo de ataque se puede producir teóricamente en cualquier lugar donde se pueda devolver al usuario una cadena que no se codificó o no se saneó debidamente.

En la Figura 4 se observa un ataque con un script similar en un sitio de desarrollo que permite que los usuarios dejen comentarios sobre los productos. Sin embargo, en vez de dejar una reseña sobre el producto alguien insertó código JavaScript malintencionado en el comentario. Este script ahora muestra un cuadro de diálogo de inicio de sesión a todos los usuarios que llegan a la página, recoge sus credenciales y las envía a un sitio remoto. Este es un ataque XSS persistente; el script se almacena en la base de datos y se repite para todos los visitantes de la página.

A Persistent XSS Attack Showing a Fake Dialog
Figura 4 Un ataque XSS persistente que presenta un cuadro de diálogo falso

Otra forma de aprovechar un XSS es mediante los elementos HTML, por ejemplo cuando se permite texto dinámico en las etiquetas HTML:

    <img onmouseover=alert([user supplied text])>

Si un atacante inyecta un texto parecido a “onmouseout=alert(docu­ment.cookie)” se crea la siguiente etiqueta en el explorador que accede a la cookie:

    <img onmouseover=alert(1) onmouseout=alert(document.cookie) >

No hay ninguna etiqueta “<script>” que permitan filtrar las entradas ni nada que se pueda escapar, pero se trata de JavaScript perfectamente válido que puede leer una cookie; posiblemente una cookie de autenticación. Dependiendo del caso, existen formas de lograr que esto sea más seguro, pero debido al riesgo, lo mejor es impedir que las entradas del usuario alcancen este código en línea.

¿Cómo prevenir el XSS? Si sigue estrictamente las siguientes reglas podrá prevenir la mayoría, si no todos los ataques XSS en sus aplicaciones:

  1. Asegúrese de que todas las salidas estén codificadas como HTML.

  2. No permita que el texto proporcionado por el usuario termine en una cadena de un atributo de un elemento HTML.

  3. Prevenga el uso de Internet Explorer 6 por parte de su aplicación; para esto, revise Request.Browser, tal como se describe en msdn.microsoft.com/library/3yekbd5b.

  4. Conozca el comportamiento de su control y sepa si codifica la salida como HTML. En caso contrario, codifique los datos que van hacia el control.

  5. Use la biblioteca Anti-Cross Site Scripting (AntiXSS) de Microsoft y establézcala como el codificador predeterminado de HTML.

  6. Use el objeto AntiXSS Sanitizer (esta biblioteca se descarga en forma independiente, y la trataré más adelante) para llamar GetSafeHtml o GetSafeHtmlFragment antes de guardar datos HTML en la base de datos; no codifique los datos antes de guardarlos.

  7. Con Web Forms, no establezca EnableRequestValidation=false en las páginas web. Desgraciadamente, en la mayoría de los comentarios en los grupos de usuarios en la web se sugiere deshabilitar este valor en caso de errores. Esta configuración tiene una justificación importante y detendrá la solicitud si por ejemplo se registra que la combinación de caracteres “<X” vuelve al servidor. Si sus controles registran HTML de vuelta en el servidor y reciben el error que aparece en la Figura 5 entonces idealmente debiera codificar los datos antes de registrarlos en el servidor. Esto es una situación común en los controles WYSIWYG, y la mayoría de las versiones modernas codificarán los datos HTML correctamente antes de volver a registrarlos en el servidor.

    Server Error from Unencoded HTML
    Figura 5 Error de servidor debido a HTML sin codificar

  8. En el caso de las aplicaciones ASP.NET MVC 3, cuando tiene que registrar el HTML de vuelta en el modelo, no use ValidateInput(false) para desactivar Request Validation. Simplemente agregue [AllowHtml] a la propiedad de su modelo, del siguiente modo:

public class BlogEntry
{
  public int UserId {get;set;}
  [AllowHtml]
  public string BlogText {get;set;}
}

Algunos productos intentan detectar la etiqueta <script> y otras combinaciones de palabras o patrones regulares de expresiones en una cadena para detectar el XSS. Estos productos pueden entregar controles adicionales pero no son completamente confiables, debido a la enorme diversidad creada por los atacantes. Eche una mirada a la guía de referencia rápida sobre XSS en ha.ckers.org/xss.html para ver lo difícil que puede ser la detección.

Para entender las soluciones, supongamos que un atacante inyectó algún script que termina en una variable en nuestra aplicación, proveniente de la cadena de consulta o un campo de un formulario, como vemos aquí:

string message = Request.QueryString["msg"];

o bien:

string message = txtMessage.Text;

Tenga en cuenta que aun cuando el control TextBox codifica la salida como HTML, no codifica la propiedad Text cuando usted lo lee del código. Con una de estas dos líneas de código, quedamos con la siguiente cadena en la variable del mensaje:

message = "<script>alert('bip')</script>"

En una página web que contiene código similar al siguiente, el código JavaScript se ejecutará en el explorador del usuario, simplemente porque se escribió el siguiente texto en la página:

    <%=message %>

Al codificar la salida como HTML el ataque se detiene en seco. En la Figura6 se muestran las principales alternativas para codificar los datos peligrosos.

Estas opciones previenen el tipo de ataques que vimos en el ejemplo, y conviene usarlas en las aplicaciones.

Figura 6 Opciones de codificación como HTML

ASP.NET (ya sea MVC o Web Forms) <%=Server.HtmlEncode(message) %>
Web Forms (sintaxis de ASP.NET 4) <%: message %>
ASP.NET MVC 3 Razor @message
Enlace de datos

Desafortunadamente, la sintaxis para el enlace de datos aún no contiene una sintaxis integrada para la codificación; estará disponible en la siguiente versión de ASP.NET en forma de <%#: %>. Hasta entonces, puede usar:

<%# Server.HtmlEncode(Eval("NombrePropiedad")) %>

Codificación mejor

De la biblioteca AntiXSS en el espacio de nombres Microsoft.Security.Application:

Encoder.HtmlEncode(message)

Es importante que conozca sus controles. ¿Qué controles de HTML codifican los datos y cuáles no lo hacen? Por ejemplo, un control TextBox codifica el resultado representado como HTML, pero no así un LiteralControl. Esta es una diferencia importante. Un cuadro de texto asignado:

yourTextBoxControl.Text = "Test <script>alert('bip')</script>";

presenta correctamente el texto en la página como:

    Test &lt;script&gt;alert(&#39;bip&#39;)&lt;/script&gt;

En cambio:

yourLiteralControl.Text = "Test <script>alert('bip')</script>";

provoca que se muestre una alerta de JavaScript en la página, lo que confirma la vulnerabilidad de XSS. La solución aquí es sencilla:

yourLiteralControl.Text = Server.HtmlEncode(
    "Test <script>alert('bip')</script>");

Esto es un poco más complicado al usar el enlace de datos en Web Forms. Por ejemplo las siguientes líneas:

    <asp:Repeater ID="Repeater1" runat="server">
        <ItemTemplate>
          <asp:TextBox ID="txtYourField" Text='<%# Bind("YourField") %>'
            runat="server"></asp:TextBox>
        </ItemTemplate>
      </asp:Repeater>

¿Esto es vulnerable? No, no lo es. Aun cuando pareciera que el código puede escribir el script o puede salirse de las comillas de control, realmente está codificado. 

¿Y qué hay de estas líneas?

    <asp:Repeater ID="Repeater2" runat="server">
      <ItemTemplate>
        <%# Eval("YourField") %>
      </ItemTemplate>
    </asp:Repeater>

¿Es vulnerable? Sí, lo es. La sintaxis de enlace de datos <%# %> no se codifica como HTML. Aquí tenemos la solución:

    <asp:Repeater ID="Repeater2" runat="server">
      <ItemTemplate>
        <%#Server.HtmlEncode((string)Eval("YourText"))%>
      </ItemTemplate>
    </asp:Repeater>

Debe tener presente que si usa Bind en esta situación, no puede usar un Server.HtmlEncode para encapsularlo, debido a la forma en que Bind se compila entre bastidores en dos llamadas independientes. Esto generará un error:

    <asp:Repeater ID="Repeater2" runat="server">
      <ItemTemplate>
        <%#Server.HtmlEncode((string)Bind("YourText"))%>
      </ItemTemplate>
    </asp:Repeater>

Si usa Bind y no asigna el texto a un control que codifica como HTML (como por ejemplo el control TextBox), considere la posibilidad de usar Eval en su lugar y así podrá encapsular la llamada a Server.HtmlEncode como en el ejemplo anterior.

Como en ASP.NET MVC no existe el mismo concepto de enlace de datos, tiene que saber si los métodos auxiliares de HTML se van a codificar o no. Los métodos auxiliares para las etiquetas y los cuadros de texto sí se codifican como HTML. Este código, por ejemplo:

@Html.TextBox("customerName", "<script>alert('bip')</script>")
@Html.Label("<script>alert('bip')</script>")

se representa como:

    <input id="customerName" name="customerName" type="text"
      value="&lt;script>alert(&#39;bip&#39;)&lt;/script>" />
    <label for="">&lt;script&gt;alert(&#39;bip&#39;)&lt;/script&gt;</label>

Ya mencioné AntiXSS previamente. La versión actual de la biblioteca AntiXSS (4.1 beta 1) fue reescrita, y en cuanto a la seguridad, entrega un codificador HTML mejor que el que entrega ASP.NET. No es que Server.HtmlEncode tenga algo malo, pero está enfocado en la compatibilidad y no en la seguridad. AntiXSS tiene una postura diferente frente a la codificación. Puede obtener más información en msdn.microsoft.com/security/aa973814.

La versión beta está disponible en bit.ly/gMcB5K. Esté atento para ver si AntiXSS ya salió de beta. Si no, tendrá que descargar el código y compilarlo. Jon Galloway escribió una artículo excelente sobre esto en bit.ly/lGpKWX.

Para usar el codificador AntiXSS basta simplemente con hacer la siguiente llamada:

    <%@ Import Namespace="Microsoft.Security.Application" %>
    ...
    ...
    <%= Encoder.HtmlEncode(plainText)%>

ASP.NET MVC 4 agregó una nueva característica excelente que le permite reemplazar el codificador HTML predeterminado de ASP, y puede usar el codificador AntiXSS en su lugar. A la hora de escribir esto, necesita la versión 4.1. Como actualmente está en beta, debe descargar el código, compilarlo y agregar la biblioteca como referencia a su aplicación; en total no debiera tomarle más de cinco minutos. Luego, en su web.config, agregue la siguiente línea en la sección <system.web>:

<httpRuntime encoderType=
  "Microsoft.Security.Application.AntiXssEncoder, AntiXssLibrary"/>

Ahora cualquier llamada que codifique como HTML que se haga mediante alguna de las sintaxis enumeradas en la Figura 6, incluida la sintaxis Razor de ASP.NET MVC 3, se codificará con la biblioteca AntiXSS. ¡Esta sí que es una característica acoplable!

Esta biblioteca también incluye un objeto Sanitizer que se puede usar para limpiar el HTML antes de almacenarlo en una base de datos, lo que resulta muy útil cuando el usuario tiene un editor WYSIWYG para editar HTML. Esta llamada intenta eliminar el script de la cadena:

using Microsoft.Security.Application;
...
...
string wysiwygData = "before <script>alert('bip ')</script> after ";
string cleanData = Sanitizer.GetSafeHtmlFragment(wysiwygData);
This results in the following cleaned string that can then be saved to the database:
cleanData = "before  after ";

Falsificación de solicitud entre sitios (CSRF)

¿Qué es? La falsificación de solicitud entre sitios o CSRF (pronunciado en inglés como “sea-surf”), es un ataque que se produce cuando alguien se aprovecha de la confianza entre el explorador y un sitio web para ejecutar un comando mediante la sesión del inocente usuario. Resulta un poco más difícil de imaginar este ataque sin ver los detalles, así que entremos inmediatamente en materia.

¿Cómo se realiza? Supongamos que John está autenticado como administrador en el sitio PureShoppingHeaven. PureShoppingHeaven tiene una dirección URL que tiene un acceso administrativo restringido y permite pasar información a la URL para ejecutar acciones, como por ejemplo crear un usuario nuevo, tal como se muestra en la Figura 7.

Passing Information on the URL
Figura 7 Paso de información a la URL

Si un atacante logra mediante alguno de varios métodos que John solicite esta dirección URL, su explorador la solicitará del servidor y entregará toda la información de autenticación que esté almacenada en caché o esté usando el explorador de John, como por ejemplo las cookies de autenticación y otros tokens de autenticación, incluida la autenticación de Windows Authentication.

Este es un ejemplo sencillo, pero los ataques CSRF pueden ser mucho más sofisticados e incorporar solicitudes POST de formularios además de las GET y al mismo tiempo pueden aprovechar otros tipos de ataques como por ejemplo los XSS.

Supongamos por ejemplo que John visita un sitio de redes sociales que ha sido víctima de un ataque. Quizás un atacante dejó un trozo de JavaScript en la página mediante una vulnerabilidad XSS y este ahora solicita la dirección URL de .aspx en AddUser.aspx en la sesión de John. Este volcado de Fiddler (fiddler2.com) después de la visita de John en la página web muestra que el explorador también envía una cookie personalizada de autenticación en el sitio:

GET http://pureshoppingheaven/AddUser.aspx?userName=hacked&pwd=secret HTTP/1.1
Host: pureshoppingheaven
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)
Cookie: CUSTOMERAUTHCOOKIE=a465bc0b-e1e2-4052-8292-484d884229ab

Todo eso ocurre sin el conocimiento de John. Lo importante es entender que el explorador intencionadamente envía cualquier cookie o información de autenticación válida. ¿Se ha percatado alguna vez de que el cliente de correo electrónico generalmente no carga las imágenes en forma predeterminada? Una de las razones de esto es prevenir la CSRF. Si recibe un mensaje de correo electrónico formateado como HTML con una etiqueta con una imagen incrustada como la siguiente, entonces se solicitaría la dirección URL y el servidor ejecutaría esa acción si está autenticado en ese sitio web:

    <img src='yoursite/createuser.aspx?id=hacked&pwd=hacked' />

Si fuera un administrador en “yoursite” y ya está autenticado, entonces el explorador enviaría esa solicitud GET junto con todas las credenciales y se quedaría tan contento. El servidor la ve como una solicitud válida de un usuario autenticado, y la ejecutará sin que usted jamás se entere, ya que no hay ninguna respuesta válida en forma de imagen que se muestre en el cliente de correo electrónico.

¿Cómo prevenir la CSRF? Para prevenir la CSRF debe comenzar por seguir algunas reglas:

  1. Asegúrese de que las solicitudes no se puedan reemplazar al hacer clic en un vínculo para una solicitud GET. La especificación de HTTP para las solicitudes GET da a entender que las solicitudes GET sólo se deben usar para recuperar datos y no para modificar el estado.
  2. Asegúrese de que las solicitudes no se puedan reproducir si un atacante usó JavaScript para imitar una solicitud POST de un formulario.
  3. Prevenga cualquier tipo de acciones mediante GET. Por ejemplo, no permita la creación ni la eliminación a través de una dirección URL. Idealmente, estas operaciones debieran requerir algún tipo de interacción con el usuario. Si bien esto no previene los ataques basados en formularios más inteligentes, impide un sinnúmero de ataques más fáciles, como el que se describió en el ejemplo del mensaje de correo electrónico que contiene una imagen, además de los vínculos básicos incrustados en los sitios comprometidos por XSS.

En Web Forms la prevención de los ataques es un poco diferente de ASP.NET MVC. Con Web Forms, se puede firmar el atributo ViewState MAC, lo que entrega protección contra la falsificación, siempre y cuando no se establezca EnableViewStateMac=false. También tendrá que firmar ViewState con la sesión de usuario actual y prevenir que ViewState se pase a la cadena de consulta para bloquear lo que algunos llaman los ataques de un solo clic (consulte la Figura 8).

Figura 8 Prevención de un ataque de un solo clic

void Page_Init(object sender, EventArgs e)
{
  if (Session.IsNewSession)
  {
    // Force session to be created;
    // otherwise the session ID changes on every request.
    Session["ForceSession"] = DateTime.Now;
  }
  // 'Sign' the viewstate with the current session.
  this.ViewStateUserKey = Session.SessionID;
  if (Page.EnableViewState)
  {
    // Make sure ViewState wasn't passed on the querystring.
    // This helps prevent one-click attacks.
    if (!string.IsNullOrEmpty(Request.Params["__VIEWSTATE"]) &&
      string.IsNullOrEmpty(Request.Form["__VIEWSTATE"]))
    {
      throw new Exception("Viewstate existed, but not on the form.");
    }
  }
}

La razón por la que asigno un valor aleatorio a la sesión es para garantizar que se establezca la sesión. Puede usar cualquier identificador temporal para la sesión, pero el identificador de la sesión ASP.NET cambiará con cada solicitud hasta que usted realmente cree una sesión. Como aquí no nos sirve un identificador de sesión que cambia con cada solicitud, tenemos que crear una sesión nueva para fijarlo.

ASP.NET MVC contiene su propio conjunto de métodos auxiliares integrados que brindan protección contra los ataques CSRF que usan tokens únicos que se pasan con la solicitud. Estos métodos no solo usan un campo de formulario oculto requerido, sino que también el valor de una cookie, lo que dificulta la falsificación de las solicitudes. Estas medidas de protección se pueden implementar fácilmente y es absolutamente imprescindible que las incorpore en sus aplicaciones. Para agregar @Html.AntiForgery­Token() dentro de <form> en su vista, haga lo siguiente:

@using (Html.BeginForm())
{
  @Html.AntiForgeryToken();
  @Html.EditorForModel();
  <input type="submit" value="Submit" />
}
Decorate any controllers that accept post data with the [Validate­AntiForgeryToken], like so:
[HttpPost]
[ValidateAntiForgeryToken()]
public ActionResult Index(User user)
{
  ...
}

Entender las vulnerabilidades

En este artículo examinamos el scripting entre sitios y la falsificación de solicitud entre sitios, dos tipos comunes de ataques informáticos en las aplicaciones web. Sumados a los dos que vimos el mes pasado, la inyección de código SQL y la alteración de parámetros, ahora cuenta con una buena comprensión de las formas en que las aplicaciones pueden ser vulnerables.

También puede ver lo fácil que resulta incorporar la seguridad en las aplicaciones para protegerse contra algunos de los ataques más comunes. Si ya ha estado integrando la seguridad en su ciclo de desarrollo de software, ¡excelente! Si no, ahora es el mejor momento para hacerlo. Puede auditar las aplicaciones existentes por página o por módulo, y en la mayoría de los casos las podrá refactorizar fácilmente. Y proteja sus aplicaciones con SSL para prevenir la captura de las credenciales. Recuerde pensar en la seguridad antes, durante y después del desarrollo.

Adam Tuliper es arquitecto de software en Cegedim y ha desarrollado software durante más de 20 años. Es ponente de la comunidad INETA y periódicamente expone en congresos y grupos de usuarios .NET. Sígalo en Twitter en twitter.com/AdamTuliper, en su blog en completedevelopment.blogspot.com o el nuevo sitio web secure-coding.com. Para obtener información más detallada acerca de cómo proteger las aplicaciones ASP.NET contra los hackers, consulte su serie prevista de vídeos Pluralsight.

Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo: Barry Dorrans