Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool

Preventing JSON Hijacking with Secure Formatter Design

JSON (JavaScript Object Notation) is the de facto standard for data interchange on the web. While seemingly simple, improperly formatted JSON responses can expose web applications to a vulnerability known as JSON Hijacking or JSON Array Hijacking. This page explains the attack vector and, more importantly, how designing your backend to format JSON securely can prevent it.

What is JSON Hijacking?

JSON Hijacking is a client-side attack where a malicious website loads sensitive JSON data from a vulnerable origin and reads it, typically by exploiting how browsers handle script execution and array/object literals.

The core idea is that if a sensitive JSON response is returned as a valid JavaScript array or object literal, a malicious page loaded in the victim's browser might be able to execute the JSON response as a script and gain access to the data.

The Attack Vector Explained:

Imagine a banking application at bank.example.com that has an endpoint like /api/transactionswhich returns a user's transaction history as a JSON array:

Vulnerable JSON Output:

[
  {
    "id": 123,
    "date": "2023-10-27",
    "description": "Salary Deposit",
    "amount": 5000.00
  },
  {
    "id": 124,
    "date": "2023-10-28",
    "description": "Groceries",
    "amount": -150.50
  }
  // ... potentially sensitive data ...
]

If a user visits a malicious site (malicious.com) in the same browser where they are logged into bank.example.com, the malicious site could attempt to load the /api/transactions endpoint using a <script> tag:

Malicious Script Tag on malicious.com:

&lt;script src="https://bank.example.com/api/transactions"&gt;&lt;/script&gt;

Since the user is logged in, the browser sends the authentication cookies with the request to bank.example.com. If the /api/transactions response is just a plain JSON array ([...]), the browser might interpret this response as JavaScript code trying to define an array literal.

In older browsers or specific JavaScript contexts (like overriding the Array constructor or using getter properties), the malicious script could potentially intercept or read the data as it's being "defined" by the loaded <script>. Similarly, a plain JSON object literal ({...}) might be assignable to a global variable if wrapped in parentheses (({...})).

The Solution: Secure JSON Formatter Design

The key to preventing JSON Hijacking is to ensure that the JSON response, when loaded as a script, is *not* valid or executable JavaScript that could reveal sensitive data. This is achieved by modifying the format of the JSON response slightly.

Secure JSON formatter design focuses on making the JSON response syntaxically valid JSON, but invalid or inert when interpreted directly as a standalone JavaScript script by the browser's script parser.

Method 1: Wrapping the JSON

One common method is to wrap the actual JSON payload inside an outer structure that makes it non-executable as a simple literal.

Wrap in an Object with a Key:

Instead of returning a plain array or object, wrap it inside an object with a specific key.

Secure JSON Output (Wrapped):

&#x7b;
  "data": [
    &#x7b;
      "id": 123,
      "date": "2023-10-27",
      "description": "Salary Deposit",
      "amount": 5000.00
    &#x7d;,
    &#x7b;
      "id": 124,
      "date": "2023-10-28",
      "description": "Groceries",
      "amount": -150.50
    &#x7d;
  ]
&#x7d;

When loaded via a <script> tag, a plain object literal ({...}) is usually parsed as a JavaScript block statementcontaining labeled statements. This parsing behavior prevents direct access to the object's contents as a simple value. The malicious script cannot easily assign this "block" to a variable to read the "data" property.

Wrap in a JSON string:

Although less common for standard API responses, you could theoretically return the entire JSON as a string value within an object.

Secure JSON Output (String Wrapped):

&#x7b;
  "data": "[{\"id\": 123, \"date\": \"2023-10-27\", \"description\": \"Salary Deposit\", \"amount\": 5000.00}, {\"id\": 124, \"date\": \"2023-10-28\", \"description\": \"Groceries\", \"amount\": -150.50}]"
&#x7d;

The client would then need to parse the string value of the data property. Loading a script that defines an object with a string property doesn't execute arbitrary code or expose data easily.

Method 2: JSON Prefixing

This method adds a specific, non-executable prefix to the JSON response. The most common prefixes are an infinite loop statement.

Secure JSON Output (with Prefix):

while(1);[
  &#x7b;
    "id": 123,
    "date": "2023-10-27",
    "description": "Salary Deposit",
    "amount": 5000.00
  &#x7d;,
  &#x7b;
    "id": 124,
    "date": "2023-10-28",
    "description": "Groceries",
    "amount": -150.50
  &#x7b;
]

When loaded via a <script> tag, the browser's JavaScript engine will encounter the while(1); (or for(;;);) statement first. This creates an infinite loop, effectively hanging the script execution before the JSON data (the array literal) is processed. The malicious script is trapped in the loop and cannot access the data.

When your legitimate frontend application fetches this data using standard methods like fetch or XMLHttpRequest, it receives the full text response, including the prefix. The frontend code then needs to parse this response: first, it should remove the known prefix (e.g., check if the response starts with while(1); and slice it off), and *then* parse the remaining string as JSON using JSON.parse().

Conceptual Frontend Parsing with Prefix Removal:

const expectedPrefix = 'while(1);';

async function fetchSecureJson(url) {
  const response = await fetch(url);
  const text = await response.text();

  if (text.startsWith(expectedPrefix)) {
    const jsonString = text.substring(expectedPrefix.length);
    try {
      const data = JSON.parse(jsonString);
      return data; // The actual JSON data
    } catch (e) {
      console.error('Failed to parse JSON after removing prefix:', e);
      throw new Error('Invalid JSON response');
    }
  } else {
    console.error('Response did not start with expected prefix.');
    // Handle error or unexpected response format
    throw new Error('Unexpected response format');
  }
}

Implementation Considerations

  • Backend Responsibility: The secure formatting must be implemented on the backend server side for any endpoint returning sensitive JSON data.
  • Consistency: Choose one method (wrapping or prefixing) and apply it consistently to all sensitive JSON endpoints.
  • Frontend Adaptation: If using the prefix method, the frontend code consuming the API must be updated to handle the prefix before parsing the JSON.
  • Modern Browsers: While modern browsers have mitigations that make the original JSON Array Hijacking attack less prevalent (e.g., stricter script parsing rules, Content Security Policy), implementing secure formatting is still considered a robust defense-in-depth measure, especially for sensitive data and for compatibility with older clients or specific browser configurations.
  • Content-Type: Ensure the response uses the correct Content-Type header, typically application/json. While this doesn't prevent the script tag attack (browsers often sniff content), it's correct practice.

Conclusion

JSON Hijacking is a historical vulnerability that leveraged how browsers interpreted JSON arrays or objects when loaded as scripts. By implementing a secure JSON formatter on the backend – either by wrapping the data in a non-executable structure or by adding a non-terminating prefix – you can effectively mitigate this risk. While browser security has evolved, employing secure formatting remains a valuable practice for protecting sensitive data transmitted via JSON APIs, contributing to a more secure application architecture.

Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool