12 - Reflected DOM XSS (Eval)
#xss #dombased #reflectedxss #javascript #portswigger
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.resultsvar 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:
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:
javascript"searchTerm": ""Esto asigna una cadena vacía a
searchTerm.Operación de resta:
javascript-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:
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