homeASCIIcasts

204: Protección contra XSS en Rails 3 

(view original Railscast)

Other translations: En Cn It

Other formats:

Written by Juan Lupión

En el episodio 27 [verlo, leerlo] tratábamos acerca del cross-site scripting (XSS), que es una importante cuestión para cualquier desarrollador web. Uno de los momentos en los que las aplicaciones web son vulnerables a ataques XSS es cuando muestran información introducida por los usuarios. Es muy importante, por tanto, escapar toda esta información, lo que en Rails se hace por lo general con el método h.

<%= h comment.content %>

Uso del método h para escapar la salida.

Sin embargo en Rails 3 la salida de nuestra aplicación es escapada automáticamente de forma que no hay que poner el método h en nuestras vistas. En este episodio veremos como gestiona Rails este escapado automático.

Para ver como funciona todo esto vamos a utilizar una aplicación sencilla de blog escrita con Rails 3. En esta aplicación tendremos artículos y cada artículo puede tener un número de comentarios asociados. Vamos a comprobar la reacción del sistema de comentarios ante un intento de XSS introduciendo <script>alert('I steal cookies!')</script> en cada campo del formulario de comentarios y enviando este malvado comentario.

Introduciendo un comentario malvado.

Cuando enviamos este comentario y se visualiza en la página veremos que Rails 3 automáticamente ha escapado las etiquetas HTML en los campos que hemos enviado. Veamos cómo lo ha hecho.

Los comentarios en HTML se escapan automáticamente.

El código que muestra cada comentario se encuentra en un parcial, y si examinamos dicho código veremos que la salida no está siendo protegida utilizando h.

/app/views/comments/_comment.html.erb

<div class="comment">
  <strong><%= link_to comment.name, comment.url %></strong>
  <p><%= comment.content %></p>
</div>

Esto quiere decir que con este código de vista en Rails 2 los mensajes de alerta se habrían mostrado. En Rails 3 toda la salida del parcial es escapada incluso a través de helpers como link_to así que ya no hay que utilizar el método h.

¿Qué ocurre si, por ejemplo, estamos migrando una aplicación de Rails 2 y nuestras vistas sí que están utilizando el método h? Podemos averiguarlo haciendo ese cambio en el parcial anterior y recargando la página.

/app/views/comments/_comment.html.erb

<div class="comment">
  <strong><%= link_to h(comment.name), comment.url %></strong>
  <p><%= h comment.content %></p>
</div>

Si recargamos la página veremos que tiene el mismo aspecto y que la salida no ha sido escapada por partida doble. Rails es inteligente aquí, aún si usamos el método h.

Podríamos pensar a la vista de esto que el método h es inocuo en Rails 3, pero no es así; más adelante veremos cuál es su propósito pero de momento veamos una funcionalidad relacionada. Si bien es muy interesante tener la salida escapada automáticamente puede ser que a veces queramos mostrar el contenido en bruto: si confiamos en el contenido que ha introducido el usuario (por ejemplo se trata de un usuario administrador) y queremos mostrar exactamente lo que ha introducido podemos utilizar el nuevo método raw.

<div class="comment">
  <strong><%= link_to comment.name, comment.url %></strong>
  <p><%= raw comment.content %></p>
</div>

Si recargamos la página esta vez sí que se ejecutará el código Javascript que hemos introducido en el comentario.

Cuando se usa el método raw se muestra la alerta.

Por tanto en Rails podemos usar el método raw cuando no queramos escapar el contenido en HTML. ¿Pero cómo funciona esto, cómo es Rails tan inteligente como para saber cuándo tiene que escapar y cuándo no?.

Esto lo veremos en la consola, que en Rails 3 se invoca con el comando rails c.

$ rails c
Loading development environment (Rails 3.0.0.beta)
ruby-1.9.1-p378 >

Rails 3 introduce el concepto de cadenas con HTML seguro. Esto significa que podemos comprobar si es seguro mostrar una cadena como HTML invocando el método html_safe? sobre ella.

> "foo".html_safe?
 => false 

Con el método html_safe se puede marcar una cadena como segura.

> safe = "safe".html_safe
 => "safe" 
> safe.html_safe?
 => true 

Aún no hemos efectuado ninguna modificación al contenido. Lo único que hacemos es cambiar una propiedad booleana de la cadena que se usará para determinar si debe ser escapada antes de ser visualizada.

A la hora de aplicar esto a nuestra vista, Rails examinará cada cadena y mirará si está marcada como HTML seguro. Si no lo es, será automáticamente convertida mientras que si lo es se mostrará sin procesar. Si se usa el método h en una cadena, realizará la conversión y la marcará como segura, lo que significa que Rails 3 considerará que la cadena es segura y no la volverá a escapar.

Cuando se usa el método raw en una cadena, se marcará como segura pero su contenido seguirá intacto de forma que la cadena pasará sin alteración.

Es importante entender esto cuando usamos helpers. Como ejemplo crearemos un método llamado strong que rodea lo que se le pase con un par de etiquetas <strong>. En nuestra vista lo utilizaremos así:

/app/views/comments/_comment.html.erb

<div class="comment">
  <%= strong link_to(comment.name, comment.url) %>
  <p><%= raw comment.content %></p>
</div>

Crearemos en ApplicationHelper el método strong:

/app/helpers/application_helper.rb

Pero veremos que este método no ha funcionad como esperábamos si recargamos la página.

La etiqueta strong en nuestro método ha sido escapada.

El escapado automático de Rails 3 ha modificado la etiqueta <strong> porque nuestro helper no ha creado una cadena HTML segura.

Sólo hay que seguir dos reglas muy sencillas cuando definamos helpers que devuelvan HTML. Primero tenemos que asegurarnos de que las cadenas que devolvamos estén marcadas como seguras.

/app/helpers/application_helper.rb

module ApplicationHelper
  def strong(content)
    "<strong>#{content}</strong>".html_safe
  end
end

Esto corrige el problema de que la etiqueta <strong> aparezca escapada pero ahora resulta que el contenido entre las etiquetas no será escapado. Para esto podemos escapar el contenido con nuestro viejo amigo el método h:

/app/helpers/application_helper.rb

module ApplicationHelper
  def strong(content)
    "<strong>#{h(content)}</strong>".html_safe
  end
end

Así que para que todo se muestre correctamente tenemos que escapar cualquier contenido introducido por el usuario con el método h y marcar la cadena resultante como segura con html_safe. Si recargamos la página, ahora veremos que la etiqueta <strong> no ha sido escapada pero el contenido del segundo comentario, que incluye el Javascript peligroso, sí que ha sido escapado.

El helper se escapa correctamente.

Y con esto termina nuestro episodio. El escapado automático es una nueva funcionalidad de Rails 3 que elimina la necesidad de tener que acordarse de escapar cualquier fragmento de salida con h, reduciéndose de esta forma las posibilidades de que seamos víctimas de un ataque XSS.