12 - Reflected DOM XSS (Eval)

https://youtu.be/bg_xH4Dp-6E

function search(path) {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) { 
            eval('var searchResultsObj = ' + this.responseText);
            displaySearchResults(searchResultsObj);
        }
    };
    xhr.open("GET", path + window.location.search); 
    xhr.send();
    function displaySearchResults(searchResultsObj) {
        var blogHeader = document.getElementsByClassName("blog-header")[0];
        var blogList = document.getElementsByClassName("blog-list")[0];
        var searchTerm = searchResultsObj.searchTerm
        var searchResults = searchResultsObj.results
        var h1 = document.createElement("h1");
        h1.innerText = searchResults.length + " search results for '" + searchTerm + "'";
        blogHeader.appendChild(h1);
        var hr = document.createElement("hr");
        blogHeader.appendChild(hr)
        for (var i = 0; i < searchResults.length; ++i)
        {
            var searchResult = searchResults[i];
            if (searchResult.id) {
                var blogLink = document.createElement("a");
                blogLink.setAttribute("href", "/post?postId=" + searchResult.id);
                if (searchResult.headerImage) {
                    var headerImage = document.createElement("img");
                    headerImage.setAttribute("src", "/image/" + searchResult.headerImage);
                    blogLink.appendChild(headerImage);
                }
                blogList.appendChild(blogLink);
            }
            blogList.innerHTML += "<br/>";
            if (searchResult.title) {
                var title = document.createElement("h2");
                title.innerText = searchResult.title;
                blogList.appendChild(title);
            }
            if (searchResult.summary) {
                var summary = document.createElement("p");
                summary.innerText = searchResult.summary;
                blogList.appendChild(summary);
            }
            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";
            }
        }

        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);
    }
}

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:

  1. Estructura JSON:

    {"results":[],"searchTerm":""-alert()}//}
    

    Este es un JSON que contiene un array vacío results y una propiedad searchTerm.

  2. Parte conflictiva:

    • "searchTerm": ""-alert()}//"
    • Aquí, "searchTerm": "" es una cadena vacía asignada a searchTerm.
    • Luego, -alert() es un intento de realizar una operación matemática (resta) con la función alert().
    • Finalmente, }// es un comentario de una sola línea en JavaScript.

Cómo lo procesa eval:

  1. Primero: eval evalúa el JSON como un string de código JavaScript.

  2. Cadena vacía:

    "searchTerm": ""
    

    Esto asigna una cadena vacía a searchTerm.

  3. Operación de resta:

    -alert()
    

    Aquí, el operador de resta intenta aplicar - a lo que retorna alert(). En JavaScript, alert() retorna undefined, por lo que el resultado sería NaN (Not a Number) porque no puedes restar undefined.

  4. Comentarios en JavaScript:

    // Esto es un comentario
    

    Después de la función alert(), el resto del código }// es tratado como un comentario, por lo que es ignorado.

Por qué funciona:

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