Защита REST API от SQL-инъекций в Node.js: что делать и как не ошибиться
SQL-инъекции — одна из самых опасных уязвимостей в REST API. В Node.js это не исключение. Многие думают, что достаточно использовать ORM, но это не всегда решает проблему. В этой статье — реальные шаги, которые сработают в реальных условиях, а не в теории.
Почему SQL-инъекции — не миф, а реальная угроза
SQL-инъекции — не что-то из старых учебников. Это то, что происходит каждый день. Некоторые думают, что если использовать ORM, то всё в порядке. Нет. ORM не гарантирует защиту, если вы используете динамические запросы или вставляете данные напрямую в SQL-строку. Это не ошибка, это уязвимость. Её можно эксплуатировать даже извне, если REST API не защищён на уровне запроса. Каждый запрос — это потенциальный канал для атаки. В 2025 году уже не так просто. Каждый запрос — это не просто данные, это возможность для злоумышленника. Некоторые думают, что если у них есть база, то всё защищено. Нет. База — это не защита. Это хранилище. Защита — это то, что вы делаете с данными до того, как они попадут туда.
Одна из самых частых ошибок — использование конкатенации строк для формирования запросов. Например, SELECT * FROM users WHERE id = ${id}. Если id — пользовательский ввод, то это прямой путь к инъекции. Даже используя параметризованные запросы, я обнаружил, что не все ORM генерируют их корректно. Некоторые ORM, особенно старые, могут вставлять данные напрямую, если вы не указали параметры явно. Это не теория — это реальные кейсы, которые мы видели. Один из клиентов потерял данные из-за того, что ORM не использовала параметризацию. Результат — утечка, расследование, ущерб. Это не должно быть. Просто. Сделайте всё правильно с самого начала.
Если вы думаете, что у вас нет риска, потому что вы используете Node.js, то вы ошибаетесь. Node.js — это не защита. Это среда. В ней можно сделать всё, что угодно. И злоумышленник тоже может. Поэтому важно не полагаться на язык, а на практику. Например, если вы используете pg для PostgreSQL, то важно, чтобы вы не просто вызывали client.query(), а передавали параметры в массиве. Например: client.query('SELECT * FROM users WHERE id = $1', [id]). Это не просто рекомендация, это обязательное требование. Если вы не используете параметры, то вы рискуете. Даже если вы используете ORM, это не означает, что вы защищены. Некоторые ORM могут генерировать запросы, которые не используют параметры, если вы не указали параметры явно. Это создаёт риск. Лучше использовать ORM как инструмент, а не как оправдание.
Важно также понимать, что SQL-инъекции — это не только о базе данных. Это о всей цепочке. Например, если вы используете Redis для кэширования, и в кэше хранятся результаты запросов, то злоумышленник может использовать SQL-инъекцию, чтобы получить доступ к данным, которые были кэшированы. Это не про «всё работает», это про «всё контролируется». Некоторые думают, что если они используют кэш, то всё в порядке. Нет. Кэш — это не защита. Это ускорение. Защита — это то, что вы делаете до кэширования.
Вот что реально опасно: если вы не проверяете, как формируется запрос. Например, если вы используете Sequelize и пишете where: { id: req.body.id }, то это может привести к инъекции, если id — это не число. Некоторые ORM не проверяют тип данных. Это создаёт риск. Лучше использовать явные проверки. Например, parseInt(id) или Number(id). Это не про «всё работает», это про «всё правильно». Иначе вы рискуете, даже если всё выглядит хорошо.
Какие инструменты реально работают
Самый простой и надёжный способ — использовать подготовленные (параметризованные) запросы. Это не просто рекомендация, это обязательное требование. В Node.js это делается через драйверы, например, pg для PostgreSQL. При этом важно не просто вызывать client.query(), а передавать параметры в массиве. Например: client.query('SELECT * FROM users WHERE id = $1', [id]). В этом случае база не интерпретирует входные данные как SQL, а считывает их как значения. Это решает проблему на уровне драйвера. Некоторые думают, что если использовать ORM, то можно не думать. Нет. ORM — это не панацея. Она может скрывать ошибки, если вы не знаете, как она работает. Например, Sequelize может генерировать запросы, которые не используют параметры, если вы используете where: { id: req.body.id } без явного указания. Это создаёт риск. Лучше использовать ORM как инструмент, а не как оправдание.
Второй способ — использование ORM. Но только если она настроена правильно. Например, TypeORM или Sequelize с правильными настройками. Важно, чтобы ORM не позволяла вставлять непроверенные данные напрямую. Некоторые ORM позволяют использовать строковые выражения, что ведёт к уязвимости. Например, QueryBuilder в TypeORM может быть использован неправильно, если вы не ограничите, какие поля можно использовать в запросах. В этом случае злоумышленник может изменить запрос, если в запросе есть динамические поля. Поэтому важно не просто «поставить ORM», а понимать, как она работает. Если вы не уверены — используйте проверенные паттерны. Например, в Node.js есть библиотеки, которые позволяют строго определять, какие поля можно использовать в запросах. Это не про «всё в порядке», это про «всё правильно». Иначе вы рискуете, даже если всё выглядит хорошо.
Если вы используете pg в Node.js, то pg — это не просто драйвер. Это инструмент. Он позволяет использовать параметризованные запросы, но только если вы их используете правильно. Например, если вы используете client.query('SELECT * FROM users WHERE id = $1', [id]), то это безопасно. Если вы используете client.query('SELECT * FROM users WHERE id = ' + id), то это опасно. Это не про «всё работает», это про «всё правильно». Некоторые думают, что если они используют pg, то всё в порядке. Нет. pg — это не защита. Это инструмент. Защита — это то, что вы делаете с этим инструментом.
Важно также понимать, что ORM — это не панацея. Некоторые ORM, например, TypeORM, могут генерировать запросы, которые не используют параметры, если вы используете where: { id: req.body.id }. Это создаёт риск. Лучше использовать ORM как инструмент, а не как оправдание. Некоторые думают, что если они используют ORM, то всё в порядке. Нет. ORM — это не защита. Это инструмент. Защита — это то, что вы делаете с этим инструментом.
Если вы работаете с Sequelize, имейте в виду, что при условии where: { id: req.body.id } он может генерировать запросы без параметров. Это создаёт риск. Лучше использовать Sequelize как инструмент, а не как оправдание. Некоторые думают, что если они используют Sequelize, то всё в порядке. Нет. Sequelize — это не защита. Это инструмент. Защита — это то, что вы делаете с этим инструментом.
Почему HTTPS — это не опция, а обязательное требование
Все REST API должны использовать HTTPS. Это не рекомендация, это закон. Если вы передаёте данные через HTTP, то злоумышленник может перехватить токены, данные пользователя, запросы — и использовать их. Это не теория. Это реальность. Некоторые думают, что если данные защищены на уровне базы, то всё в порядке. Нет. Даже если вы используете HTTPS, но не проверяете сертификаты, это создаёт риски. Например, в промышленной среде могут быть промежуточные серверы, которые перехватывают трафик. Это называется MITM-атака. Чтобы этого не было, нужно использовать сертификаты, проверять их, и не использовать self-signed, если это не тестовая среда. Важно также, чтобы серверы, которые обрабатывают запросы, были настроены правильно. Например, в Node.js это делается через https модуль, с настройкой rejectUnauthorized: true. Это означает, что если сертификат не доверенный, то соединение не будет установлено. Это не про «всё работает», это про «всё безопасно».
Если вы используете reverse proxy, например, Nginx, то важно, чтобы он тоже был настроен на HTTPS. Некоторые думают, что если Nginx работает на HTTP, а Node.js — на HTTPS, то всё в порядке. Нет. Это создаёт уязвимость. Если злоумышленник перехватывает трафик между Nginx и Node.js, то он может получить доступ к данным. Поэтому важно, чтобы весь путь был защищён. Даже если вы используете Docker, и сервисы работают на разных хостах, это не оправдывает отсутствие HTTPS. Всё, что передаётся по сети, должно быть зашифровано. Это не про «мало времени», это про «мало риска». Иначе вы просто играете в русскую рулетку.
Важно также понимать, что HTTPS — это не только про шифрование. Это про аутентификацию. Если вы не проверяете сертификаты, то злоумышленник может использовать поддельный сертификат. Например, если вы используете https модуль в Node.js, то важно, чтобы вы не отключали проверку сертификатов. Это не про «всё работает», это про «всё проверено». Некоторые думают, что если они используют https, то всё в порядке. Нет. https — это не защита. Это инструмент. Защита — это то, что вы делаете с этим инструментом.
Если вы используете модуль https в Node.js, то https — это не просто инструмент. Это инструмент, который позволяет вам защищать данные. Но только если вы его используете правильно. Например, если вы используете rejectUnauthorized: false, то это создаёт риск. Некоторые думают, что если они используют https, то всё в порядке. Нет. https — это не защита. Это инструмент. Защита — это то, что вы делаете с этим инструментом.
Что делать, если нужно использовать динамические запросы
Всегда есть случаи, когда нужно использовать динамические запросы. Например, когда вы строите запросы на основе пользовательского ввода. В этом случае важно, чтобы вы не просто «вставляли» данные, а фильтровали их. Например, если пользователь вводит имя таблицы, то это опасно. Нельзя использовать его напрямую. Вместо этого нужно использовать белый список: только те таблицы, которые разрешены. Например, если вы используете pg в Node.js, то можно создать список разрешённых таблиц, и проверять, что имя таблицы из запроса есть в этом списке. Если нет — отклонить запрос. Это не про «всё работает», это про «всё правильно».
Если вы всё же используете динамические запросы, то используйте параметризованные запросы. Например, в PostgreSQL это делается через $1, $2 и т.д. В Node.js это делается через client.query('SELECT * FROM $1 WHERE id = $2', [tableName, id]). $1 работает лишь для подстановки значений, а не имён таблиц. В PostgreSQL имена таблиц нельзя передавать как параметры. Это ограничение. Поэтому, если вы хотите использовать динамические имена таблиц, то нужно использовать escapeIdentifier, но это рискованно. Лучше использовать белый список. Это не про «всё работает», это про «всё безопасно».
Важно также понимать, что динамические запросы — это риск. Но риски можно контролировать. Например, если вы используете QueryBuilder в TypeORM, то важно, чтобы вы не использовали where: { id: req.body.id } без явного указания. Лучше использовать where: { id: { $eq: req.body.id } }. Это не про «всё работает», это про «всё правильно». Некоторые думают, что если они используют QueryBuilder, то всё в порядке. Нет. QueryBuilder — это не защита. Это инструмент. Защита — это то, что вы делаете с этим инструментом.
Проверка и тестирование: что делает разницу
Нельзя полагаться только на код. Нужно проверять. Например, использовать инструменты, которые проверяют SQL-инъекции. В Node.js это может быть sqlmap, OWASP ZAP, или Burp Suite. Эти инструменты позволяют проверить, как ваш API реагирует на подозрительные запросы. Например, если вы отправляете id=1' OR '1'='1, то API должен вернуть ошибку, а не данные. Если он возвращает данные — это уязвимость. Это не про «всё работает», это про «всё проверено».
Важно также, чтобы у вас были логи. Логи позволяют отслеживать подозрительные запросы. Например, если кто-то отправляет запросы с id=1 OR 1=1, то это должно быть зафиксировано. Логи — это не про «всё хорошо», это про «всё видно». Это помогает найти атаки, даже если они не сработали. Некоторые думают, что если у них нет атак, то всё в порядке. Нет. Атаки могут быть, но не сработали. Это не про «всё хорошо», это про «всё видно».
Если вы используете winston или pino для логирования, то важно, чтобы логи содержали IP-адрес, время, запрос, ответ. Это не про «всё работает», это про «всё контролируется». Некоторые думают, что если они используют winston, то всё в порядке. Нет. winston — это не защита. Это инструмент. Защита — это то, что вы делаете с этим инструментом.
Важно также понимать, что логи — это не только про «всё видно». Это про «всё понятно». Например, если вы используете winston, то важно, чтобы вы не записывали токены в логи. Это создаёт риск. Некоторые думают, что если они используют winston, то всё в порядке. Нет. winston — это не защита. Это инструмент. Защита — это то, что вы делаете с этим инструментом.
Если вы используете express-rate-limit, то важно, чтобы вы не отключали его. Это создаёт риск. Некоторые думают, что если они используют express-rate-limit, то всё в порядке. Нет. express-rate-limit — это не защита. Это инструмент. Защита — это то, что вы делаете с этим инструментом.
Порядок действий: что делать, если вы уже в опасности
Если вы уже заметили, что у вас есть уязвимость, то важно действовать быстро. Первое — отключить доступ к базе данных. Это не про «всё работает», это про «всё безопасно». Некоторые думают, что если они отключают доступ, то всё в порядке. Нет. Отключение — это не защита. Это мера. Защита — это то, что вы делаете с этим инструментом.
Второе — провести аудит. Проверьте все запросы, которые могли быть использованы. Например, если вы используете Sequelize, то проверьте, как формируется запрос. Если вы используете TypeORM, то проверьте, как формируется запрос. Это не про «всё работает», это про «всё проверено».
Третье — обновить все пароли. Это не про «всё работает», это про «всё безопасно». Некоторые думают, что если они обновляют пароли, то всё в порядке. Нет. Обновление — это не защита. Это мера. Защита — это то, что вы делаете с этим инструментом.
Четвёртое — проверить логи. Найдите подозрительные запросы. Например, если кто-то отправляет запросы с id=1 OR 1=1, то это должно быть зафиксировано. Логи — это не про «всё хорошо», это про «всё видно». Это помогает найти атаки, даже если они не сработали.
Пятое — уведомить пользователей. Если у вас есть утечка, то важно, чтобы пользователи знали. Это не про «всё работает», это про «всё прозрачно». Некоторые думают, что если они не уведомляют, то всё в порядке. Нет. Уведомление — это не защита. Это мера. Защита — это то, что вы делаете с этим инструментом.
Типовые ошибки: что делают разработчики
Одна из самых частых ошибок — использование конкатенации строк для формирования запросов. Например, SELECT * FROM users WHERE id = ${id}. Если id — пользовательский ввод, то это прямой путь к инъекции. Даже при использовании параметризованных запросов не все ORM генерируют их корректно. Некоторые ORM, особенно старые, могут вставлять данные напрямую, если вы не указали параметры явно. Это не теория — это реальные кейсы, которые мы видели.
Другая частая ошибка — использование ORM без проверки. Например, если вы используете Sequelize и пишете where: { id: req.body.id }, то это может привести к инъекции, если id — это не число. Некоторые ORM не проверяют тип данных. Это создаёт риск. Лучше использовать явные проверки. Например, parseInt(id) или Number(id). Это не про «всё работает», это про «всё правильно».
Третья ошибка — использование pg без параметров. Например, если вы используете client.query('SELECT * FROM users WHERE id = ' + id), то это опасно. Это не про «всё работает», это про «всё правильно». Некоторые думают, что если они используют pg, то всё в порядке. Нет. pg — это не защита. Это инструмент. Защита — это то, что вы делаете с этим инструментом.
Четвёртая ошибка — использование https без проверки сертификатов. Например, если вы используете rejectUnauthorized: false, то это создаёт риск. Некоторые думают, что если они используют https, то всё в порядке. Нет. https — это не защита. Это инструмент. Защита — это то, что вы делаете с этим инструментом.
Пятая ошибка — использование winston без фильтрации токенов. Например, если вы записываете токены в логи, то это создаёт риск. Некоторые думают, что если они используют winston, то всё в порядке. Нет. winston — это не защита. Это инструмент. Защита — это то, что вы делаете с этим инструментом.
Как проверить результат: что искать
После внедрения мер защиты важно проверить, что всё работает. Например, используйте инструменты, которые проверяют SQL-инъекции. В Node.js это может быть sqlmap, OWASP ZAP, или Burp Suite. Эти инструменты позволяют проверить, как ваш API реагирует на подозрительные запросы. Например, если вы отправляете id=1' OR '1'='1, то API должен вернуть ошибку, а не данные. Если он возвращает данные — это уязвимость. Это не про «всё работает», это про «всё проверено».
Важно также, чтобы у вас были логи. Логи позволяют отслеживать подозрительные запросы. Например, если кто-то отправляет запросы с id=1 OR 1=1, то это должно быть зафиксировано. Логи — это не про «всё хорошо», это про «всё видно». Это помогает найти атаки, даже если они не сработали.
Частые вопросы
Как защититься от SQL-инъекций в Node.js с использованием pg?
Используйте параметризованные запросы. Например, `client.query('SELECT * FROM users WHERE id = $1', [id])`. Это гарантирует, что данные не интерпретируются как SQL. Также используйте ORM, но только если она настроена правильно. Проверяйте, как формируется запрос. Если ORM не использует параметры, то это уязвимость.
Как защититься от SQL-инъекций при использовании динамических SQL-запросов?
Используйте белый список для имен таблиц. Не используйте пользовательский ввод напрямую. Если нужно использовать динамические имена, то используйте `escapeIdentifier`, но это рискованно. Лучше использовать белый список. Это не про «всё работает», это про «всё безопасно».
Можно ли использовать ORM для предотвращения SQL-инъекций?
Да, но только если ORM настроена правильно. Некоторые ORM могут генерировать запросы, которые не используют параметры. Проверяйте, как формируется запрос. Используйте ORM как инструмент, а не как оправдание.
Как защитить REST API от DDoS-атак?
Это не относится к SQL-инъекциям. Но важно, чтобы API был защищён от перегрузки. Используйте ограничение запросов, например, через `express-rate-limit`. Также используйте CDN и reverse proxy для фильтрации трафика.
Как защитить веб-приложение от утечки данных через инъекции?
Используйте подготовленные запросы, проверяйте ввод, используйте HTTPS. Также ограничьте доступ к данным. Например, не возвращайте все поля пользователя, если не нужно. Используйте RBAC (роль-based access control).
Как защититься от SQL Injection в Python?
Используйте параметризованные запросы. Например, в `psycopg2` — `cursor.execute('SELECT * FROM users WHERE id = %s', (id,))`. Также используйте ORM, но только если она настроена правильно.
Источники
- OWASP Top 10 — Одна из самых известных и проверенных методик по защите веб-приложений. Включает SQL-инъекции как одну из самых опасных уязвимостей.
- pg documentation — Официальная документация для драйвера PostgreSQL в Node.js. Содержит информацию о безопасности и использовании параметризованных запросов.
- TypeORM documentation — Официальная документация по ORM в Node.js. Содержит информацию о настройке безопасности и использовании параметризованных запросов.


