#204 XSS Protection in Rails 3
- Download:
- source codeProject Files in Zip (156 KB)
- mp4Full Size H.264 Video (11.8 MB)
- m4vSmaller H.264 Video (8.63 MB)
- webmFull Size VP8 Video (21 MB)
- ogvFull Size Theora Video (15.1 MB)
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.
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.
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
.
<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.
<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.
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í:
<div class="comment"> <%= strong link_to(comment.name, comment.url) %> <p><%= raw comment.content %></p> </div>
Crearemos en ApplicationHelper
el método strong
:
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
:
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.
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.