![](https://habrastorage.org/getpro/habr/upload_files/7a0/1ba/b38/7a01bab3807d2896c6b5640dd04ef8dd.png)
JSON — это инструмент, которым каждый веб-разработчик пользуется ежедневно. На первый взгляд кажется, что JSON слишком прост и незамысловат, чтобы читать про него целую статью. Но если вы не знаете, что такое санитизация данных, никогда не слышали про JSON Injection или XSS атаки, если информация о том, что JSON.parse и JSON.stringify принимают 2 и 3 аргумента для вас новость, то эта статья будет вам полезна.
Общие положения
JSON (JavaScript Object Notation) — это легковесный формат данных, который используется для обмена данными между клиентом и сервером в веб-приложениях.
В начале 2000-х годов была потребность в формате данных, который был бы удобнее и проще XML. Дуглас Крокфорд предложил использовать для обмена данными формат объектного литерала JavaScript, так и появился JSON. Предложенный Крокфордом формат оказался более компактным по сравнению с XML, проще для чтения и записи и прост в парсинге и генерации. В результате сейчас JSON является основным форматом обмена данными в веб-разработке.
JSON основан на двух основных структурах данных: объекте и массиве. Объект — это набор пар "ключ-значение", массив — это упорядоченный список значений. Ключи в объектах JSON всегда являются строками, а значения могут быть строками, числами, объектами, массивами, булевыми значениями или null.
{
"name": "John",
"age": 30,
"is_student": false,
"courses": ["math", "history", "chemistry"],
"personal_info": {
"address": "123 Main St",
"phone_number": "123-456-7890"
}
}
Несмотря на простоту и удобство, работа с JSON может быть связана с рядом проблем безопасности, если не подходить к этому процессу обдуманно.
Безопасное использование JSON
Основные виды атак
1. JSON Injection
Эта атака заключается в том, что злоумышленник вставляет вредоносный код в JSON, который затем обрабатывается веб-приложением.
Пример: Если веб-приложение берет пользовательский ввод и непосредственно вставляет его в JSON без проверки или санитизации (об этом подробно дальше), злоумышленник может вставить вредоносный код. Этот код может быть выполнен при обработке JSON.
const userInput = getUserInput();
const data = `{"name": "${userInput}"}`
const jsonData = JSON.parse(data);
В этом случае, если пользователь вводит что-то вроде "", "isAdmin": "true"}
, вредоносный код будет добавлен в JSON, создавая объект { "name": "", "isAdmin": "true"}
.
2. Cross-Site Scripting (XSS)
XSS — это тип атаки, при которой злоумышленник вставляет вредоносный скрипт в веб-страницу, который затем выполняется в браузере пользователя.
Пример: Если веб-приложение вставляет данные JSON напрямую в HTML без предварительной санитизации, это может привести к XSS-атаке.
const jsonData = getJsonData();
const element = document.getElementById('output');
element.innerHTML = jsonData.content;
Если jsonData.content
содержит что-то вроде
<script>maliciousCode()</script>
вредоносный код будет выполнен в браузере пользователя.
3. SQL Injection и JSON
SQL-инъекция — это вид атаки, при которой злоумышленник внедряет или "инъецирует" вредоносный SQL код в запрос к базе данных через ввод пользователя. SQL-инъекции могут привести к несанкционированному доступу к базе данных, краже, изменению или уничтожению данных.
Примером может служить ситуация, когда данные с клиентской стороны, например, из формы на веб-странице, принимаются в формате JSON, и затем используются для построения SQL-запроса.
const userData = JSON.parse(userInput);
const query = `SELECT * FROM users WHERE name = '${userData.name}' AND password = '${userData.password}'`;
Злоумышленник может подставить в поле name
или password
строку, которая, будучи вставлена в запрос SQL, изменит его логику. Например, следующий JSON:
{
"name": "' OR '1'='1'; --",
"password": "any"
}
после подстановки в запрос приведет к его изменению:
SELECT * FROM users WHERE name = '' OR '1'='1'; --' AND password = 'any'
В данном случае, поскольку условие '1'='1'
всегда истинно, это приведет к выбору всех пользователей и злоумышленник сможет обойти проверку пароля и войти в систему под любым аккаунтом.
Дисклеймер: я привел стандартный пример SQL-инъекции, он призван демонстрировать концепцию, а не реальный вектор атаки.
Важно понимать, что JSON сам по себе не имеет отношения к SQL-инъекциям. Однако, если данные, полученные в формате JSON, используются для формирования SQL-запросов без предварительной валидации и санитизации, то это может создать уязвимости для таких атак.
Чтобы предотвратить SQL-инъекции, рекомендуется использовать подготовленные или параметризованные запросы, а также проводить валидацию и санитизацию всех входных данных. Подготовленные запросы используют параметры вместо прямой вставки ввода пользователя в запрос. Библиотеки баз данных обычно предоставляют функции для создания подготовленных запросов.
Пример использования подготовленного запроса на Node.js с использованием 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);
В этом примере, даже если злоумышленник попытается внедрить вредоносный код через JSON, он будет безопасно обработан библиотекой MySQL, и SQL-инъекция не произойдет.
Предотвращение атак
Основой безопасного использования JSON являются валидация и санитизация данных. Эти инструменты универсальны и эффективны против всех описанных выше видов атак.
Санитизация данных
Санитизация данных — это процесс очистки данных от вредоносных или нежелательных элементов. В контексте веб-разработки это обычно включает удаление или замену символов, которые могут быть использованы для проведения атак, таких как вставка SQL, XSS или JSON Injection.
Инструменты для санитизации данных:
DOMPurify: Это библиотека для JavaScript, которая позволяет очищать HTML, MathML и SVG от XSS атак. DOMPurify очень прост в использовании. Просто передайте ей строку, которую нужно санитизировать, и она вернет безопасную строку.
const clean = DOMPurify.sanitize(dirty);
express-validator: Это набор middleware функций для Express.js, который предоставляет мощные инструменты валидации и санитизации.
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);
В этом примере ‘express-validator’ проверяет, что имя пользователя не пустое, а вызов escape()
производит санитизацию данных.
Валидация данных
Валидация — это процесс проверки данных на соответствие определенным критериям или правилам. В контексте JSON валидация обычно включает в себя проверку следующего:
Соответствие структуры и типов данных предполагаемым. Например, если ожидается объект с определенными полями, валидация должна убедиться, что все эти поля присутствуют и имеют правильные типы данных.
Отсутствие нежелательных символов или паттернов, которые могут вызвать проблемы при обработке данных (например, вредоносный код).
Соответствие данных бизнес-правилам. Например, валидация может включать проверку того, что значение определенного поля находится в допустимом диапазоне.
JavaScript предоставляет некоторые базовые функции для валидации JSON.
JSON.stringify
JSON.stringify() преобразует объект JavaScript в строку JSON. Этот метод принимает три аргумента: значение для преобразования, функцию-заменитель и пробелы.
Значение: это может быть любой объект JavaScript или значение, которое вы хотите преобразовать в JSON.
Функция-заменитель (replacer): это опциональная функция, которая преобразует значения объекта перед их сериализацией. Она может использоваться для фильтрации или преобразования значений.
Пробелы: опциональный параметр, который контролирует отступы в сериализованном JSON. Это может быть число (количество пробелов) или строка (до 10 символов, используемых в качестве пробела).
const json = JSON.stringify(obj, (key, value) => {
if (key === 'id') return undefined;
return value;
}, 2);
В этом примере значение "id" удаляется из сериализованного JSON и используются два пробела для отступов в JSON.
Сериализация — это процесс преобразования состояния объекта в формат, который может быть сохранен или передан и затем восстановлен. В контексте JSON, сериализация обычно включает преобразование объекта JavaScript в строку JSON.
Десериализация — это обратный процесс. Он преобразует сериализованные данные обратно в их первоначальную форму. В контексте JSON, десериализация обычно включает преобразование строки JSON обратно в объект JavaScript.
JSON.parse
JSON.parse() преобразует строку JSON обратно в объект JavaScript. Этот метод принимает два аргумента: строку для преобразования и функцию-ревайвер.
Строка: это должна быть корректная строка JSON, которую вы хотите преобразовать в объект JavaScript.
Функция-ревайвер (reviver): это опциональная функция, которая преобразует значения объекта после их десериализации. Она может использоваться для преобразования или восстановления определенных значений.
const obj = JSON.parse(str, (key, value) => {
if (key === 'date') return new Date(value);
return value;
});
В примере выше, функция-ревайвер преобразует строку в объект Date в процессе десериализации JSON.
JSON Schema
Для более сложных случаев валидации, таких как проверка структуры объекта JSON и типов данных, может потребоваться использование сторонних библиотек, таких как Ajv (Another JSON Schema Validator).
Ajv использует стандарт, который называется JSON Schema который нужен для описания и валидации структуры JSON-данных.
Пример JSON Schema:
{
"$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"]
}
Обратите внимание на поле:
"$schema": "http://json-schema.org/draft-07/schema#",
"$schema"
является особым полем в JSON Schema. Это URI, который указывает на версию JSON Schema, которая была использована для создания вашей схемы. Это необходимо для корректной интерпретации и валидации схемы.
Вышеуказанная схема описывает объект "Product" с полями "id", "name", "price" и "tags". Поля "id", "name" и "price" являются обязательными. Поле "tags" должно быть массивом уникальных строк.
В данном примере, "$schema": "http://json-schema.org/draft-07/schema#"
указывает, что ваша схема написана с использованием версии "draft-07"
JSON Schema. Это помогает программам, которые обрабатывают вашу схему, правильно интерпретировать и применять правила валидации.
Ajv позволяет определить схему для данных, а затем проверять объекты на соответствие этой схеме.
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)
В этом примере, если данные не соответствуют схеме (например, foo
не является числом, или одно из полей отсутствует), Ajv вернет ошибки, которые затем можно обработать.
Общие ошибки при работе с JSON и их решения
Синтаксические ошибки, ошибки сериализации/десериализации и ошибки передачи данных — это некоторые из наиболее распространенных проблем, с которыми разработчики сталкиваются при работе с JSON.
Синтаксические ошибки: JSON имеет строгий синтаксис. Например, все ключи должны быть заключены в двойные кавычки, а не в одинарные.
{ 'key': "value" } // ошибка { "key": "value" } // правильно
Кроме того, каждый элемент в массиве или пара ключ-значение в объекте должны быть разделены запятыми.
{ "key1": "value1" "key2": "value2" } // ошибка { "key1": "value1", "key2": "value2" } // правильно
Синтаксические ошибки могут быть легко обнаружены с помощью JSON валидаторов, например, JSONLint.
Ошибки сериализации/десериализации: Некоторые объекты JavaScript не могут быть корректно сериализованы в JSON или десериализованы из JSON. Например, функции и объекты Date в JavaScript не могут быть преобразованы в JSON. В этих случаях необходимо преобразовать объекты в поддерживаемые типы данных перед сериализацией или после десериализации. В этом примере, при преобразовании JSON обратно в объект, функция ревайвер преобразует строку даты обратно в объект Date. Без функции ревайвер, дата осталась бы строкой.
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, а не строку
Ошибки при передаче данных: JSON часто используется для передачи данных между клиентом и сервером. Если эти данные не правильно кодируются или декодируются, или если они теряются в процессе передачи, это может привести к ошибкам. Обычно ошибки кодирования и декодирования связаны с ошибками в заголовке ‘Content-Type’, там необходимо указать значение ‘application/json’.
'Content-Type': 'text/plain' // ошибка
'Content-Type': 'application/json' // правильно
Заключение
Правильное использование JSON в веб-разработке критически важно для обеспечения безопасности и эффективности приложения. Валидация и санитизация данных, использование безопасных функций для обработки JSON и избегание распространенных ошибок могут помочь обеспечить надежную и безопасную работу с JSON. Надеюсь теперь вы согласитесь, что даже в таком простом инструменте, как JSON, могут быть неожиданные подводные камни и особенности, о которых нужно знать.
Также напоследок хочу пригласить вас на бесплатный урок, на котором поговорим о том, как работать с многопоточностью в NodeJS. Также на уроке вы познакомитесь с WorkerThreads. Регистрация доступна по ссылке.
Полезные ссылки
Что еще почитать по теме:
Инструменты:
Ajv (Another JSON Schema Validator): Один из самых популярных JSON Schema валидаторов. Он позволяет создавать сложные правила валидации, используя JSON Schema стандарт.
DOMPurify: Библиотека для санитизации HTML, которая помогает предотвращать XSS атаки.
express-validator: Библиотека для валидации и санитизации данных в Express.js приложениях.
Joi: Мощная библиотека для валидации данных в JavaScript. Она поддерживает множество типов данных и позволяет создавать сложные правила валидации.
validator.js: Библиотека строковой валидации и санитизации.
js-xss: Безопасный и мощный модуль санитизации HTML для JavaScript, предназначенный для предотвращения XSS атак.
sanitize-html: Библиотека, которая позволяет обрабатывать HTML, сохраняя только те элементы и атрибуты, которые вы хотите разрешить.