12 - Reflected DOM XSS (Eval)
function search(path) {
function search(path): Define una función llamadasearchque acepta un argumentopath. Este argumento es una cadena que representa la ruta a la que se enviará una solicitud HTTP.
var xhr = new XMLHttpRequest();
var xhr = new XMLHttpRequest();: Crea un nuevo objetoXMLHttpRequest, que se usa para realizar solicitudes HTTP asíncronas que esperan xml a un servidor.
xhr.onreadystatechange = function() {
xhr.onreadystatechange = function() {: Define una función de devolución de llamada (callback) que se ejecutará cada vez que cambie el estado dexhr(por ejemplo, cuando se reciba una respuesta del servidor).
if (this.readyState == 4 && this.status == 200) {
if (this.readyState == 4 && this.status == 200) {: Comprueba si la solicitud ha terminado (readyState == 4) y si se recibió una respuesta exitosa (status == 200).
eval('var searchResultsObj = ' + this.responseText);
eval('var searchResultsObj = ' + this.responseText);: Utilizaevalpara ejecutar el código contenido enthis.responseText, creando un objetosearchResultsObj.this.responseTextcontiene la respuesta del servidor en formato de texto, que aquí se espera sea un JSON.
displaySearchResults(searchResultsObj);
displaySearchResults(searchResultsObj);: Llama a la funcióndisplaySearchResults, pasando el objetosearchResultsObjcomo argumento. Esta función se encarga de mostrar los resultados de la búsqueda en la página.
}
}: Cierra el bloqueif.
};
};: Cierra la función anónima asignada axhr.onreadystatechange.
xhr.open("GET", path + window.location.search);
xhr.open("GET", path + window.location.search);: Abre una solicitud HTTP de tipoGETa la URL que es la combinación depathywindow.location.search(la parte de consulta de la URL actual). Esto configura la solicitud, pero aún no la envía.
xhr.send();
xhr.send();: Envía la solicitud HTTP al servidor.
function displaySearchResults(searchResultsObj) {
function displaySearchResults(searchResultsObj): Define la funcióndisplaySearchResults, que tomasearchResultsObjcomo argumento y se encarga de mostrar los resultados de la búsqueda en la página web.
var blogHeader = document.getElementsByClassName("blog-header")[0];
var blogList = document.getElementsByClassName("blog-list")[0];
var blogHeader = document.getElementsByClassName("blog-header")[0];: Obtiene el primer elemento del DOM con la claseblog-headery lo almacena en la variableblogHeader.var blogList = document.getElementsByClassName("blog-list")[0];: Obtiene el primer elemento del DOM con la claseblog-listy lo almacena en la variableblogList.
var searchTerm = searchResultsObj.searchTerm
var searchResults = searchResultsObj.results
var searchTerm = searchResultsObj.searchTerm;: Extrae el término de búsqueda desdesearchResultsObjy lo guarda en la variablesearchTerm.var searchResults = searchResultsObj.results;: Extrae los resultados de búsqueda desdesearchResultsObjy los guarda en la variablesearchResults.
var h1 = document.createElement("h1");
h1.innerText = searchResults.length + " search results for '" + searchTerm + "'";
blogHeader.appendChild(h1);
var hr = document.createElement("hr");
blogHeader.appendChild(hr)
var h1 = document.createElement("h1");: Crea un nuevo elemento<h1>.h1.innerText = searchResults.length + " search results for '" + searchTerm + "'";: Establece el texto del elemento<h1>para mostrar el número de resultados de búsqueda encontrados y el término de búsqueda.blogHeader.appendChild(h1);: Añade el elemento<h1>al elementoblogHeader.var hr = document.createElement("hr");: Crea un nuevo elemento<hr>(una línea horizontal).blogHeader.appendChild(hr);: Añade el elemento<hr>al elementoblogHeader.
for (var i = 0; i < searchResults.length; ++i)
{
var searchResult = searchResults[i];
for (var i = 0; i < searchResults.length; ++i): Inicia un bucleforque itera a través de cada resultado de búsqueda.var searchResult = searchResults[i];: Obtiene el resultado de búsqueda en la posiciónidel arraysearchResultsy lo guarda en la variablesearchResult.
if (searchResult.id) {
var blogLink = document.createElement("a");
blogLink.setAttribute("href", "/post?postId=" + searchResult.id);
if (searchResult.id) {: Verifica si el resultado de búsqueda tiene unid.var blogLink = document.createElement("a");: Crea un nuevo elemento<a>(enlace).blogLink.setAttribute("href", "/post?postId=" + searchResult.id);: Establece el atributohrefdel enlace para que apunte a la URL del post correspondiente aliddel resultado de búsqueda.
if (searchResult.headerImage) {
var headerImage = document.createElement("img");
headerImage.setAttribute("src", "/image/" + searchResult.headerImage);
blogLink.appendChild(headerImage);
}
if (searchResult.headerImage) {: Verifica si el resultado de búsqueda tiene una imagen de encabezado (headerImage).var headerImage = document.createElement("img");: Crea un nuevo elemento<img>(imagen).headerImage.setAttribute("src", "/image/" + searchResult.headerImage);: Establece el atributosrcde la imagen para que apunte a la URL de la imagen de encabezado.blogLink.appendChild(headerImage);: Añade la imagen como hijo del enlace (<a>).
blogList.appendChild(blogLink);
}
blogList.appendChild(blogLink);: Añade el enlace (<a>) con la imagen de encabezado al elementoblogList.
blogList.innerHTML += "<br/>";
blogList.innerHTML += "<br/>";: Añade un salto de línea (<br/>) al contenido HTML del elementoblogList.
if (searchResult.title) {
var title = document.createElement("h2");
title.innerText = searchResult.title;
blogList.appendChild(title);
}
if (searchResult.title) {: Verifica si el resultado de búsqueda tiene un título (title).var title = document.createElement("h2");: Crea un nuevo elemento<h2>(encabezado).title.innerText = searchResult.title;: Establece el texto del encabezado<h2>con el título del resultado de búsqueda.blogList.appendChild(title);: Añade el encabezado<h2>al elementoblogList.
if (searchResult.summary) {
var summary = document.createElement("p");
summary.innerText = searchResult.summary;
blogList.appendChild(summary);
}
if (searchResult.summary) {: Verifica si el resultado de búsqueda tiene un resumen (summary).var summary = document.createElement("p");: Crea un nuevo elemento<p>(párrafo).summary.innerText = searchResult.summary;: Establece el texto del párrafo<p>con el resumen del resultado de búsqueda.blogList.appendChild(summary);: Añade el párrafo<p>al elementoblogList.
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";
}
if (searchResult.id) {: Verifica nuevamente si el resultado de búsqueda tiene unid.var viewPostButton = document.createElement("a");**: Crea un nuevo elemento<a>(enlace), que se utilizará como botón para ver la publicación completa.viewPostButton.setAttribute("class", "button is-small");: Establece la clase del enlace para que se estilice como un botón pequeño.viewPostButton.setAttribute("href", "/post?postId=" + searchResult.id);: Establece el atributohrefdel botón para que apunte a la URL del post correspondiente.viewPostButton.innerText = "View post";: Establece el texto del botón como "View post".
}
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);
blogList.appendChild(linkback);
}
}
var linkback = document.createElement("div");: Crea un nuevo elemento<div>que se utilizará como contenedor para un enlace de retorno.linkback.setAttribute("class", "is-linkback");: Establece la clase del contenedor<div>comois-linkback.var backToBlog = document.createElement("a");: Crea un nuevo elemento<a>(enlace) que permitirá al usuario volver a la página principal del blog.backToBlog.setAttribute("href", "/");: Establece el atributohrefdel enlace para que apunte a la página principal del blog.backToBlog.innerText = "Back to Blog";: Establece el texto del enlace como "Back to Blog".linkback.appendChild(backToBlog);: Añade el enlace de retorno al contenedor<div>.blogList.appendChild(linkback);: Añade el contenedor<div>con el enlace de retorno al elementoblogList.
El script de abajo llama a una función del script de arriba:
<script src="/resources/js/searchResults.js"></script>
<script>search('search-results')</script>
Si capturamos con el burpsuite se están haciendo dos peticiones, la get del propio servidor y la que realiza de forma asíncrona el código con :
xhr.open("GET", path + window.location.search);
xhr.send();
Payload -> \"-alert()}//
SI hacemos la petición asíncrona con texto normal :
GET /search-results?search=regerg
{"results":[],"searchTerm":"regerg"}
GET /search-results?search=\"-alert()}//
{"results":[],"searchTerm":"\"-alert()}//"}
{"results":[],"searchTerm":""-alert()}//"}
SI le pasamos una doble comilla , esta se escapa con \", podemos escaparla con \\"
La razón por la cual "{"results":[],"searchTerm":""-alert()}//"} funciona y es interpretado correctamente por JavaScript se debe a la manera en que eval procesa el código y cómo la sintaxis de JavaScript trata las cadenas, los operadores y los comentarios.
Análisis del código:
-
Estructura JSON:
{"results":[],"searchTerm":""-alert()}//}Este es un JSON que contiene un array vacío
resultsy una propiedadsearchTerm. -
Parte conflictiva:
"searchTerm": ""-alert()}//"- Aquí,
"searchTerm": ""es una cadena vacía asignada asearchTerm. - Luego,
-alert()es un intento de realizar una operación matemática (resta) con la funciónalert(). - Finalmente,
}//es un comentario de una sola línea en JavaScript.
Cómo lo procesa eval:
-
Primero:
evalevalúa el JSON como un string de código JavaScript. -
Cadena vacía:
"searchTerm": ""Esto asigna una cadena vacía a
searchTerm. -
Operación de resta:
-alert()Aquí, el operador de resta intenta aplicar
-a lo que retornaalert(). En JavaScript,alert()retornaundefined, por lo que el resultado seríaNaN(Not a Number) porque no puedes restarundefined. -
Comentarios en JavaScript:
// Esto es un comentarioDespués de la función
alert(), el resto del código}//es tratado como un comentario, por lo que es ignorado.
Por qué funciona:
-
evalpermite la ejecución dinámica de código JavaScript, lo que significa que cualquier código pasado en forma de string es evaluado como si fuera código JavaScript regular. -
Los comentarios en JSON no son válidos, pero
evallo interpreta como un código de JavaScript, no como JSON puro, y trata//como un comentario, ignorando el resto de la línea. -
El JSON se procesa hasta que llega al comentario, y debido a que JavaScript permite este tipo de sintaxis, el código no lanza un error.
Básicamente hay diferencia entre usar eval() y usar json.parse()
Con eval estamos interpretando objetos javascript, el siguinte bloque es código válido JS :
{"results":[],"searchTerm":""-alert()}
Por eso cuando usamos eval() es ejecuta.
SI usamos json.parse() no se ejecuta y da un error , porque no es json, no podemos meter valores fuera de las comillas