Compartir a través de


Prevenir ataques por inyección de código de JavaScript (C#)

por Stephen Walther

Evite ataques de inyección de JavaScript y ataques de scripting entre sitios. En este tutorial Stephen Walther explica cómo puede derrotar fácilmente estos tipos de ataques mediante la codificación HTML de su contenido.

El objetivo de este tutorial es explicar cómo puede evitar ataques por inyección de JavaScript en las aplicaciones ASP.NET MVC. En este tutorial se describen dos enfoques para defender su sitio web frente a un ataque por inyección de JavaScript. Aprenderá a evitar ataques por inyección de JavaScript mediante la codificación de los datos que se muestran. También aprenderá a evitar ataques por inyección de JavaScript mediante la codificación de los datos que acepta.

¿Qué es un ataque por inyección de JavaScript?

Cada vez que acepta la entrada del usuario y vuelve a mostrarla, abre el sitio web a ataques por inyección de JavaScript. Vamos a examinar una aplicación concreta que está abierta a ataques por inyección de JavaScript.

Imagine que ha creado un sitio web de comentarios de clientes (vea la figura 1). Los clientes pueden visitar el sitio web y escribir comentarios sobre su experiencia con sus productos. Cuando un cliente envía sus comentarios, estos se vuelven a mostrar en la página de comentarios.

Customer Feedback Website

Figura 01: Sitio web de comentarios de clientes (Haga clic para ver la imagen a tamaño completo)

El sitio web de comentarios del cliente usa el elemento controller de la lista 1. Este elemento controller contiene dos acciones denominadas Index() y Create().

Lista 1: HomeController.cs

using System;
using System.Web.Mvc;
using CustomerFeedback.Models;

namespace CustomerFeedback.Controllers
{
     [HandleError]
     public class HomeController : Controller
     {
          private FeedbackDataContext db = new FeedbackDataContext();

          public ActionResult Index()
          {
               return View(db.Feedbacks);
          }

          public ActionResult Create(string message)

          {
               // Add feedback
               var newFeedback = new Feedback();
               newFeedback.Message = message;
               newFeedback.EntryDate = DateTime.Now;
               db.Feedbacks.InsertOnSubmit(newFeedback);
               db.SubmitChanges();

               // Redirect
               return RedirectToAction("Index");
          }
     }
}

El método Index() muestra la vista Index. Este método pasa todos los comentarios anteriores del cliente a la vista Index recuperándolos de la base de datos (mediante una consulta LINQ to SQL).

El método Create() crea un nuevo elemento de comentarios y lo agrega a la base de datos. El mensaje que escribe el cliente en el formulario se pasa al método Create() en el parámetro de mensaje. Se crea un elemento de comentarios y el mensaje se asigna a la propiedad Message del elemento de comentarios. El elemento de comentarios se envía a la base de datos con la llamada al método DataContext.SubmitChanges(). Por último, se redirige de nuevo al visitante a la vista Index, donde se muestran todos los comentarios.

La vista Index se encuentra en la lista 2.

Lista 2: Index.aspx

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="CustomerFeedback.Views.Home.Index"%>

<%@ Import Namespace="CustomerFeedback.Models" %>

<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
     <h1>Customer Feedback</h1>
     <p>
          Please use the following form to enter feedback about our product.
     </p>

     <form method="post" action="/Home/Create">

          <label for="message">Message:</label>
          <br />
          <textarea name="message" cols="50" rows="2"></textarea>
          <br /><br />
          <input type="submit" value="Submit Feedback" />
     </form>

     <% foreach (Feedback feedback in ViewData.Model)
     {%>
          <p>
          <%=feedback.EntryDate.ToShortTimeString()%>
          --
          <%=feedback.Message%>
          </p>
     <% }%>

</asp:Content>

La vista Index tiene dos secciones. La sección superior contiene el formulario de comentarios reales del cliente. La sección inferior contiene un bucle ForEach que recorre en bucle todos los elementos anteriores de comentarios del cliente y muestra las propiedades EntryDate y Message de cada elemento de comentarios.

El sitio web de comentarios del cliente es un sitio web sencillo. Desafortunadamente, el sitio web está abierto a ataques por inyección de JavaScript.

Imagine que escribe el texto siguiente en el formulario de comentarios del cliente:

<script>alert("Boo!")</script>

Este texto representa un script de JavaScript que muestra un cuadro de mensaje de alerta. Después de que alguien envíe este script al formulario de comentarios, el mensaje Boo!aparecerá cada vez que alguien visite el sitio web de comentarios del cliente en el futuro (vea la figura 2).

JavaScript Injection

Figura 02: Inyección de JavaScript (Haga clic para ver la imagen a tamaño completo)

Ahora, la respuesta inicial a los ataques por inyección de JavaScript podría ser apatía. Es posible que piense que los ataques por inyección de JavaScript son simplemente un tipo de ataque de desfiguración. Puede creer que nadie puede hacer nada verdaderamente malo cuando se comete un ataque por inyección de JavaScript.

Por desgracia, un hacker puede hacer cosas realmente malas cuando inyecta código JavaScript en un sitio web. Puede usar un ataque por inyección de JavaScript para realizar un ataque de scripting entre sitios (XSS). En un ataque de scripting entre sitios, se roba información confidencial del usuario y se envía la información a otro sitio web.

Por ejemplo, un hacker puede usar un ataque por inyección de JavaScript para robar los valores de las cookies del explorador de otros usuarios. Si se almacena información confidencial (como contraseñas, números de tarjeta de crédito o números de la seguridad social) en las cookies del explorador, un hacker puede usar un ataque por inyección de JavaScript para robar esta información. O bien, si un usuario escribe información confidencial en un campo de formulario contenido en una página que se ha puesto en peligro con un ataque de JavaScript, el hacker puede usar el JavaScript insertado para capturar los datos del formulario y enviarlos a otro sitio web.

Debe tener miedo. Tómese en serio los ataques por inyección de JavaScript y proteja la información confidencial del usuario. En las dos secciones siguientes, se describen dos técnicas que puede usar para defender las aplicaciones ASP.NET MVC frente a ataques por inyección de JavaScript.

Enfoque 1: Codificación HTML de la vista

Un método sencillo de evitar ataques por inyección de JavaScript consiste en codificar en HTML los datos introducidos por los usuarios del sitio web al volver a mostrar los datos en una vista. La vista Index actualizada de la lista 3 sigue este enfoque.

Lista 3: Index.aspx (codificados en HTML)

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="CustomerFeedback.Views.Home.Index"%>

<%@ Import Namespace="CustomerFeedback.Models" %>

<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
     <h1>Customer Feedback</h1>
     <p>
          Please use the following form to enter feedback about our product.
     </p>

     <form method="post" action="/Home/Create">
          <label for="message">Message:</label>
          <br />
          <textarea name="message" cols="50" rows="2"></textarea>
          <br /><br />
          <input type="submit" value="Submit Feedback" />

     </form>

     <% foreach (Feedback feedback in ViewData.Model)
     {%>
          <p>
          <%=feedback.EntryDate.ToShortTimeString()%>
          --
          <%=Html.Encode(feedback.Message)%>
          </p>
     <% }%>

</asp:Content>

Observe que el valor de feedback.Message está codificado en HTML antes de que se muestre el valor con el código siguiente:

<%=Html.Encode(feedback.Message)%>

¿Qué significa codificar en HTML una cadena? Al codificar en HTML una cadena, los caracteres peligrosos, como < y >, se reemplazan por referencias a entidades HTML, como &lt; y &gt;. Por lo tanto, cuando la cadena <script>alert("Boo!")</script> está codificada en HTML, se convierte en &lt;script&gt;alert(&quot;Boo!&quot;)&lt;/script&gt;. La cadena codificada ya no se ejecuta como un script de JavaScript cuando la interpreta un explorador. En su lugar, recibe la página inofensiva de la figura 3.

Defeated JavaScript Attack

Figura 03: Ataque de JavaScript frustrado (Haga clic para ver la imagen a tamaño completo)

Observe que en la vista Index de la lista 3 solo está codificado el valor de feedback.Message. El valor de feedback.EntryDate no está codificado. Solo necesita codificar los datos introducidos por un usuario. Dado que el valor de EntryDate se generó en el controlador, no es necesario codificar este valor en HTML.

Enfoque 2: Codificación HTML en el controlador

En lugar de codificar datos en HTML al mostrar los datos en una vista, puede hacerlo justo antes de enviarlos a la base de datos. Este segundo enfoque se adopta en caso del elemento controller de la lista 4.

Lista 4: HomeController.cs (codificados en HTML)

using System;
using System.Web.Mvc;
using CustomerFeedback.Models;
namespace CustomerFeedback.Controllers
{
     [HandleError]
     public class HomeController : Controller
     {
          private FeedbackDataContext db = new FeedbackDataContext();

          public ActionResult Index()
          {
               return View(db.Feedbacks);
          }

          public ActionResult Create(string message)
          {
               // Add feedback
               var newFeedback = new Feedback();
               newFeedback.Message = Server.HtmlEncode(message);
               newFeedback.EntryDate = DateTime.Now;
               db.Feedbacks.InsertOnSubmit(newFeedback);

               db.SubmitChanges();

               // Redirect
               return RedirectToAction("Index");
          }
     }
}

Observe que el valor de Message está codificado en HTML antes de enviarlo a la base de datos dentro de la acción Create(). Cuando el mensaje se vuelve a mostrar en la vista, está codificado en HTML y no se ejecuta ningún JavaScript inyectado en el mensaje.

Normalmente, es preferible el primer enfoque descrito en este tutorial por encima de este segundo enfoque. El problema con este segundo enfoque es que termina con datos codificados en HTML en la base de datos. En otras palabras, los datos de su base de datos se ensucian con caracteres de aspecto raro.

¿Por qué esto es malo? Si alguna vez necesita mostrar los datos de la base de datos en algo distinto de una página web, tendrá problemas. Por ejemplo, ya no podrá mostrar fácilmente los datos en una aplicación de Windows Forms.

Resumen

El propósito de este tutorial era asustarle sobre la perspectiva de un ataque por inyección de JavaScript. En este tutorial se describen dos enfoques para defender las aplicaciones ASP.NET MVC frente a ataques por inyección de JavaScript: codificar en HTML los datos enviados por el usuario en la vista o codificar en HTML los datos enviados por el usuario en el controlador.