Common Mistakes and Solutions

JSON is a tool that everyone webThe developer uses it daily. At first glance, it seems that JSON is too simple and uncomplicated to read an entire article about it. But if you don’t know what data sanitization is, have never heard of JSON Injection or XSS attacks, if the information that JSON.parse and JSON.stringify take 2 and 3 arguments is news to you, then this article will be useful to you.

General provisions

JSON (JavaScript Object Notation) is a lightweight data format that is used to exchange data between client and server in web applications.

In the early 2000s, there was a need for a data format that was more convenient and simpler than XML. Douglas Crockford proposed using the JavaScript object literal format for data exchange, and JSON was born. The format proposed by Crockford turned out to be more compact than XML, easier to read and write, and easy to parse and generate. As a result, JSON is now the main data exchange format in web development.

JSON is based on two basic data structures: object and array. An object is a set of key-value pairs, an array is an ordered list of values. Keys in JSON objects are always strings, and values ​​can be strings, numbers, objects, arrays, booleans, or null.

{
  "name": "John",
  "age": 30,
  "is_student": false,
  "courses": ["math", "history", "chemistry"],
  "personal_info": {
    "address": "123 Main St",
    "phone_number": "123-456-7890"
  }
}

Although simple and convenient, working with JSON can be associated with a number of security issues if not thoughtfully approached this process.

Safe use of JSON

Main types of attacks

1.JSON Injection

This attack involves an attacker inserting malicious code into JSON, which is then processed by a web application.

Example: If a web application takes user input and directly inserts it into JSON without validation or sanitization (more on that later), an attacker could insert malicious code. This code can be executed while processing JSON.

const userInput = getUserInput();
const data = `{"name": "${userInput}"}`
const jsonData = JSON.parse(data);

In this case, if the user enters something like "", "isAdmin": "true"}the malicious code will be added to the JSON, creating an object { "name": "", "isAdmin": "true"}.

2. Cross-Site Scripting (XSS)

XSS is a type of attack in which an attacker inserts a malicious script into a web page, which is then executed in the user’s browser.

Example: If a web application inserts JSON data directly into HTML without prior sanitization, this could lead to an XSS attack.

const jsonData = getJsonData();
const element = document.getElementById('output');
element.innerHTML = jsonData.content;

If jsonData.content contains something like

<script>maliciousCode()</script>

malicious code will be executed in the user’s browser.

3. SQL Injection and JSON

SQL injection is a type of attack in which an attacker injects or “injects” malicious SQL code into a database query through user input. SQL injections can lead to unauthorized access to the database, theft, modification or destruction of data.

An example would be when data from the client side, such as from a form on a web page, is received in JSON format and then used to build a SQL query.

const userData = JSON.parse(userInput);
const query = `SELECT * FROM users WHERE name="${userData.name}" AND password = '${userData.password}'`;

An attacker can substitute in the field name or password a string that, when inserted into an SQL query, will change its logic. For example, the following JSON:

{
  "name": "' OR '1'='1'; --",
  "password": "any"
}

after substitution in the query will change it:

SELECT * FROM users WHERE name="" OR '1'='1'; --' AND password = 'any'

In this case, since the condition '1'='1' always true, this will select all users and the attacker will be able to bypass password verification and log in with any account.

Disclaimer: I gave a standard SQL injection example, it is intended to demonstrate the concept, not the real attack vector.

It is important to understand that JSON by itself is not related to SQL injection. However, if data received in JSON format is used to form SQL queries without prior validation and sanitization, then this can create vulnerabilities for such attacks.

To prevent SQL injections, it is recommended to use prepared or parameterized queries and validate and sanitize all inputs. Prepared queries use parameters instead of directly inserting user input into the query. Database libraries usually provide functions for creating prepared queries.

An example of using a prepared request in Node.js using MySQL:

const userData = JSON.parse(userInput);
let sql = `SELECT * FROM users WHERE name = ? AND password = ?`;
const inserts = [userData.name, userData.password];
sql = mysql.format(sql, inserts);

In this example, even if an attacker tries to inject malicious code via JSON, it will be safely processed by the MySQL library and no SQL injection will occur.

Attack Prevention

Validation and data sanitization are the foundation of using JSON safely. These tools are universal and effective against all types of attacks described above.

Data sanitization

Data sanitization is the process of cleaning data from malicious or unwanted elements. In the context of web development, this usually involves removing or replacing characters that can be used to launch attacks such as SQL injection, XSS, or JSON Injection.

Data Sanitization Tools:

  1. DOMPurify: This is a JavaScript library that allows you to scrape HTML, MathML and SVG from XSS attacks. DOMPurify is very easy to use. Just pass it the string you want to sanitize and it will return a safe string.

const clean = DOMPurify.sanitize(dirty);
  1. express-validator: This is a set of middleware functions for Express.js that provides powerful validation and sanitization tools.

const express = require('express');
const { query, validationResult } = require('express-validator');

const app = express();
app.use(express.json());
app.get('/hello', query('person').notEmpty().escape(), (req, res) => {
  const result = validationResult(req);
  if (result.isEmpty()) {
    return res.send(`Hello, ${req.query.person}!`);
  }
  res.send({ errors: result.array() });
});
app.listen(3000);

In this example, ‘express-validator’ checks that the username is not empty, and the call escape() performs data sanitization.

Data Validation

Validation is the process of checking data against certain criteria or rules. In the context of JSON, validation typically includes checking for the following:

  • Correspondence of the structure and data types to the intended ones. For example, if an object with certain fields is expected, the validation must ensure that all those fields are present and have the correct data types.

  • The absence of unwanted characters or patterns that can cause processing problems (for example, malicious code).

  • Data compliance with business rules. For example, validation might include checking that the value of a particular field is within the allowed range.

JavaScript provides some basic functionality for JSON validation.

JSON.stringify

JSON.stringify() converts a JavaScript object to a JSON string. This method takes three arguments: the value to convert, the placeholder function, and spaces.

  1. Value: This can be any JavaScript object or value that you want to convert to JSON.

  2. Replacer function: This is an optional function that transforms object values ​​before they are serialized. It can be used to filter or transform values.

  3. Spaces: An optional parameter that controls the indentation in the serialized JSON. This can be a number (the number of spaces) or a string (up to 10 characters used as a space).

const json = JSON.stringify(obj, (key, value) => {
  if (key === 'id') return undefined;
  return value;
}, 2);

This example removes the “id” value from the serialized JSON and uses two spaces to indent the JSON.

Serialization is the process of converting the state of an object into a format that can be stored or transmitted and then restored. In the context of JSON, serialization typically involves converting a JavaScript object to a JSON string.

Deserialization is the reverse process. It converts the serialized data back to its original form. In the context of JSON, deserialization typically involves converting a JSON string back into a JavaScript object.

JSON.parse

JSON.parse() converts the JSON string back to a JavaScript object. This method takes two arguments: a string to convert and a recoverer function.

  1. String: This must be a valid JSON string that you want to convert to a JavaScript object.

  2. The reviver function is an optional function that transforms object values ​​after they have been deserialized. It can be used to convert or restore specific values.

const obj = JSON.parse(str, (key, value) => {
  if (key === 'date') return new Date(value);
  return value;
});

In the example above, the retriever function converts the string to a Date object during JSON deserialization.

json schema

For more complex validation cases, such as checking the structure of a JSON object and data types, it may be necessary to use third-party libraries such as Ajv (Another JSON Schema Validator).

Ajv uses a standard called JSON Schema to describe and validate the structure of JSON data.

JSON schema example:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Product",
  "type": "object",
  "properties": {
    "id": {
      "type": "number"
    },
    "name": {
      "type": "string"
    },
    "price": {
      "type": "number",
      "exclusiveMinimum": 0
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "minItems": 1,
      "uniqueItems": true
    }
  },
  "required": ["id", "name", "price"]
}

Pay attention to the field:

"$schema": "http://json-schema.org/draft-07/schema#",

"$schema" is a special field in JSON Schema. This is a URI that points to the JSON Schema version that was used to create your schema. This is necessary for the correct interpretation and validation of the schema.

The above schema describes a “Product” object with “id”, “name”, “price” and “tags” fields. The fields “id”, “name” and “price” are required. The “tags” field must be an array of unique strings.

In this example, "$schema": "http://json-schema.org/draft-07/schema#" indicates that your schema is written using version "draft-07" json schema. This helps the programs that process your schema correctly interpret and apply the validation rules.

Ajv allows you to define a schema for data and then check objects against that schema.

const Ajv = require("ajv")
const ajv = new Ajv()
const schema = {
  type: "object",
  properties: {
    foo: {type: "integer"},
    bar: {type: "string"}
  },
  required: ["foo","bar"],
  additionalProperties: false
}
const data = {foo: 1, bar: "abc"}
const valid = ajv.validate(schema, data)
if (!valid) console.log(ajv.errors)

In this example, if the data does not match the schema (for example, foo is not a number, or one of the fields is missing), Ajv will return errors that can then be handled.

Common errors when working with JSON and their solutions

Syntax errors, serialization/deserialization errors, and data transfer errors are some of the most common problems that developers encounter when working with JSON.

  1. Syntax errors: JSON has a strict syntax. For example, all keys must be enclosed in double quotes, not single quotes.

    { 'key': "value" } // ошибка
    { "key": "value" } // правильно

    Also, each element in an array or key-value pair in an object must be separated by commas.

    { "key1": "value1" "key2": "value2" } // ошибка
    { "key1": "value1", "key2": "value2" } // правильно

    Syntax errors can be easily caught using JSON validators, for example, JSONLint.

  2. Serialization/deserialization errors: Some JavaScript objects cannot be correctly serialized to JSON or deserialized from JSON. For example, functions and Date objects in JavaScript cannot be converted to JSON. In these cases, you must convert the objects to supported data types before serialization or after deserialization. In this example, when converting the JSON back to an object, the retriever function will convert the date string back to a Date object. Without the restore function, the date would remain a string.

    const user = {
      name: 'Tom',
      registered: new Date(),
    };
    const json = JSON.stringify(user);
    // Преобразуем JSON обратно в объект, восстанавливая дату с помощью функции ревайвер
    const parsedUser = JSON.parse(json, (key, value) => {
      if (key == 'registered') return new Date(value);
      return value;
    });
    console.log(parsedUser.registered); // Выведет объект Date, а не строку
  3. Data transfer errors: JSON is often used to transfer data between client and server. If this data is not properly encoded or decoded, or if it is lost during transmission, errors can result. Typically, encoding and decoding errors are related to errors in the ‘Content-Type’ header, which must be set to ‘application/json’.

'Content-Type': 'text/plain' // ошибка 
'Content-Type': 'application/json' // правильно

Conclusion

Proper use of JSON in web development is critical to the security and performance of an application. Validating and sanitizing data, using safe functions for handling JSON, and avoiding common pitfalls can help ensure a safe and secure JSON experience. I hope you now agree that even in a tool as simple as JSON, there can be unexpected pitfalls and quirks to be aware of.

Finally, I would like to invite you to free lesson, where we will talk about how to work with multithreading in NodeJS. Also in the lesson you will get acquainted with WorkerThreads. Registration available via link.

useful links

What else to read on the topic:

  1. MDN Documentation on JSON

  2. OWASP Guide to SQL Injection Prevention.

  3. You can read more about the history and importance of JSON here. Here And Here.

  4. Learn more about JSON Schema here And here.

Tools:

  1. Ajv (Another JSON Schema Validator): One of the most popular JSON Schema validators. It allows you to create complex validation rules using the JSON Schema standard.

  2. DOMPurify: An HTML sanitization library that helps prevent XSS attacks.

  3. express-validator: A library for data validation and sanitization in Express.js applications.

  4. Joi: A powerful library for data validation in JavaScript. It supports many data types and allows you to create complex validation rules.

  5. validator.js: String validation and sanitization library.

  6. js-xss: A secure and powerful HTML sanitizer for JavaScript designed to prevent XSS attacks.

  7. sanitize-html: A library that allows you to process HTML while keeping only the elements and attributes you want to allow.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *