Reflected DOM XSS
Laboratorio de Portswigger sobre XSS
Certificaciones
- eWPT
- eWPTXv2
- OSWE
- BSCP
Descripción
Este laboratorio tiene una vulnerabilidad Reflected DOM XSS. Estas vulnerabilidades ocurren cuando la aplicación del lado del servidor procesa datos de una solicitud y refleja esos datos en la respuesta. Luego, un script en la página procesa los datos reflejados de una manera insegura, escribiéndolos finalmente en un sink o función peligrosa. Para resolver este laboratorio, debemos explotar un XSS y llamar a la función alert()
Guía de XSS
Antes de completar este laboratorio es recomendable leerse esta guía de XSS https://justice-reaper.github.io/posts/XSS-Guide/
Resolución
Al acceder a la web nos sale esto
Mediante el DOM Invader de Burpsuite vamos a analizar posibles vulnerabilidades del DOM
Nos abrimos el inspector de Chrome y nos dirigimos al DOM Invader
Copiamos el payload yc5diz6f y lo pegamos en el cuadro de búsqueda, como ha encontrado un sink (función peligrosa) nos lo muestra
Si pulsamos sobre Exploit intentará explotar el sink, pero en este caso no ha logrado explotarlo
Para ver donde ha encontrado el sink debemos pulsar sobre Stack Trace y dirigirnos a Console
Si pinchamos sobre el enlace nos llevará a un archivo JavaScript, pero yo prefiero verlo directamente en el navegador, así que accedo a https://0ad20009048ecacd82f44c1e00fd0025.web-security-academy.net/resources/js/searchResults.js
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// Definimos la función 'search' que recibe un parámetro 'path' (que sería la URL para realizar la búsqueda)
function search(path) {
// Creamos una nueva solicitud XMLHttpRequest para realizar la solicitud HTTP
var xhr = new XMLHttpRequest();
// Definimos el manejador del evento 'onreadystatechange' para procesar la respuesta de la solicitud
xhr.onreadystatechange = function() {
// Verificamos si la solicitud ha finalizado (readyState == 4) y si el código de estado es 200 (OK)
if (this.readyState == 4 && this.status == 200) {
// Usamos 'eval' para convertir la respuesta JSON en un objeto de JavaScript
eval('var searchResultsObj = ' + this.responseText);
// Llamamos a la función para mostrar los resultados de la búsqueda
displaySearchResults(searchResultsObj);
}
};
// Abrimos la solicitud GET, añadiendo los parámetros de búsqueda de la URL (window.location.search)
xhr.open("GET", path + window.location.search);
// Enviamos la solicitud
xhr.send();
// Función interna que maneja la visualización de los resultados de la búsqueda
function displaySearchResults(searchResultsObj) {
// Accedemos a los elementos del DOM donde se mostrarán los resultados
var blogHeader = document.getElementsByClassName("blog-header")[0];
var blogList = document.getElementsByClassName("blog-list")[0];
// Obtenemos el término de búsqueda y los resultados del objeto
var searchTerm = searchResultsObj.searchTerm;
var searchResults = searchResultsObj.results;
// Creamos un encabezado con el número de resultados encontrados
var h1 = document.createElement("h1");
h1.innerText = searchResults.length + " search results for '" + searchTerm + "'";
blogHeader.appendChild(h1);
// Agregamos una línea horizontal para separar el encabezado de los resultados
var hr = document.createElement("hr");
blogHeader.appendChild(hr);
// Iteramos sobre los resultados de la búsqueda y los mostramos
for (var i = 0; i < searchResults.length; ++i) {
var searchResult = searchResults[i];
// Si el resultado tiene un 'id', creamos un enlace a la publicación
if (searchResult.id) {
var blogLink = document.createElement("a");
blogLink.setAttribute("href", "/post?postId=" + searchResult.id);
// Si el resultado tiene una imagen de encabezado, la añadimos al enlace
if (searchResult.headerImage) {
var headerImage = document.createElement("img");
headerImage.setAttribute("src", "/image/" + searchResult.headerImage);
blogLink.appendChild(headerImage);
}
// Añadimos el enlace al listado de blogs
blogList.appendChild(blogLink);
}
// Agregamos un salto de línea entre los resultados
blogList.innerHTML += "<br/>";
// Si el resultado tiene un título, lo mostramos
if (searchResult.title) {
var title = document.createElement("h2");
title.innerText = searchResult.title;
blogList.appendChild(title);
}
// Si el resultado tiene un resumen, lo mostramos
if (searchResult.summary) {
var summary = document.createElement("p");
summary.innerText = searchResult.summary;
blogList.appendChild(summary);
}
// Si el resultado tiene un 'id', creamos un botón para ver la publicación
if (searchResult.id) {
var viewPostButton = document.createElement("a");
viewPostButton.setAttribute("class", "button is-small");
viewPostButton.setAttribute("href", "/post?postId=" + searchResult.id);
viewPostButton.innerText = "View post";
// Pero el botón no se agrega a ningún lado, falta un appendChild aquí.
}
}
// Creamos un enlace para volver al blog principal
var linkback = document.createElement("div");
linkback.setAttribute("class", "is-linkback");
var backToBlog = document.createElement("a");
backToBlog.setAttribute("href", "/");
backToBlog.innerText = "Back to Blog";
linkback.appendChild(backToBlog);
// Añadimos el enlace de vuelta al blog al final de la lista de blogs
blogList.appendChild(linkback);
}
}
El DOM Invader está señalando a la función eval() https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/eval. En esta web https://js.do/ podemos probar como funciona la función eval(). Esta función evalúa el contenido de izquierda a derecha, por lo tanto si se produce un error antes del payload que queramos, no se ejecutará, pero si se produce después, nuestro payload seguirá pudiéndose ejecutar independientemente de si hay errores
Vamos a ver como se está enviando la petición, para ello ponemos lo que sea en el cuadro de búsqueda y capturamos la petición con Burpsuite
Si pulsamos sobre Forward recibiremos esta otra petición
Si la enviamos al repeater y vemos que la respuesta que nos devuelve es un JSON
Vamos a intentar escapar del JSON, si ponemos una comilla nos añade una \ para escapar nuestra comilla he invalidar nuestro input
Hemos conseguido cerrar la comilla, añadiendo la \, la cual escapa a la \ que nos añade la web
Vamos a intentar llamar a alert() aprovechando el funcionamiento de eval() mediante este payload \"-alert(3)}//. El símbolo - sirve como concatenación al string de JSON donde nosotros somos capaces de introducir los datos, es igual que +, ambos son operadores aritméticos que se utilizan para separar la función alert() de las expresiones anteriores y comentamos el resto del JSON con la //
Para explotar esta vulnerabilidad debemos introducir este payload \"-alert(3)}// en el cuadro de búsqueda
Es posible explotar esta vulnerabilidad debido a que a la función eval('var searchResultsObj = ' + this.responseText); le estamos pasando un string y no un objeto JSON, incluso aunque le pasáramos un objeto de JavaScript, seguiría siendo vulnerable debido a que podríamos seguir pasándole una función. Para que esto no pase debemos usar JSON.parse() para convertir el string a un objeto JSON https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse. Aunque un objeto de JavaScript y un objeto JSON son parecidos existen diferencias entre ambos, estas diferencias las podemos consultar en w3schools https://www.w3schools.com/js/js_json_syntax.asp

















