← Все статьи

Что такое SQL-инъекция и как от неё защититься?

Burncode Team 4 минуты чтения

SQL-инъекция известна и хорошо задокументирована уже более двадцати лет, но каждый год всё равно фигурирует в реальных утечках — не потому, что от неё трудно защититься, а потому, что её легко допустить по случайности, особенно в старом коде или в коде, написанном в условиях жёстких сроков.

Что такое SQL-инъекция на самом деле

Большинство веб-приложений хранят данные в базе и читают и записывают их с помощью языка запросов SQL. SQL-инъекция возникает, когда приложение берёт введённый пользователем текст — из поисковой строки, формы входа, параметра URL — и вставляет его прямо в запрос к базе, не обработав предварительно как недоверенные входные данные. Если злоумышленник введёт что-то, что выглядит как код на SQL, а не как обычное значение, и приложение вслепую включит это в запрос, база данных в итоге выполнит команды, написанные злоумышленником, а не только те, что задумало приложение.

Простой пример

Представьте проверку при входе, устроенную примерно так: «найти пользователя, у которого логин равен [тому, что ввёл пользователь], а пароль равен [тому, что ввёл пользователь]». Если приложение строит этот запрос, напрямую вклеивая ввод пользователя в текст SQL, злоумышленник может ввести в поле логина хитрую строку, которая меняет сам смысл запроса — например, делает условие всегда истинным независимо от пароля, что позволяет войти под любым пользователем, вообще не зная его пароля.

Это простой случай. Более продвинутые SQL-инъекции позволяют читать данные, которые пользователь никогда не должен был видеть, — персональные данные других пользователей, платёжные реквизиты, хеши паролей, — а в худших случаях и вовсе изменять или удалять данные.

Почему она до сих пор так распространена

  • Её не видно, пока не начнёшь тестировать. Уязвимая форма прекрасно работает для любого обычного пользователя. Проблема проявляется только тогда, когда кто-то намеренно проверяет её вредоносным вводом, а при обычной разработке такое редко происходит случайно.
  • Она прячется в коде «только на этот раз». Быстрый админский скрипт, разовый запрос для отчёта, функция, сделанная впопыхах, — именно здесь разработчики чаще всего собирают запрос простым склеиванием строк вместо безопасного подхода, потому что в моменте так кажется быстрее.
  • К старому коду не возвращаются. Паттерн, появившийся много лет назад в редко затрагиваемой части кодовой базы, может тихо лежать там, пока кто-нибудь наконец не начнёт его искать.

Как её действительно предотвратить

Решение — не расплывчатый принцип «очищайте пользовательский ввод», а конкретная, давно отработанная техника, которая при последовательном применении практически устраняет проблему.

  • Всегда используйте параметризованные запросы (prepared statements). Вместо того чтобы собирать запрос склеиванием строк, вы пишете его с плейсхолдерами и передаёте значения пользователя отдельно. Тогда база данных воспринимает эти значения строго как данные и никогда — как часть команды, какие бы символы в них ни содержались. Это поддерживает практически любая современная библиотека для работы с базами данных, и это должно быть нормой, а не исключением, о котором вспоминают от случая к случаю.
  • Используйте ORM или конструктор запросов — и используйте их правильно. Такие инструменты, как Eloquent в Laravel, по умолчанию строят параметризованные запросы. Риск возвращается именно тогда, когда разработчик переходит на «сырую» строку запроса, чтобы сделать то, что инструмент не поддерживает напрямую, — вот где стоит притормозить и всё перепроверить.
  • Применяйте принцип минимальных привилегий к учётным записям базы данных. У пользователя базы, под которым подключается приложение, должны быть только те права, которые ему действительно нужны. Если какой-то запрос всё же удастся проэксплуатировать, это ограничит, что злоумышленник сможет с ним сделать.
  • Проверяйте типы входных данных как второй рубеж, а не как основную защиту. Если поле должно быть числом, действительно убедитесь, что это число, до того как оно попадёт в запрос. Само по себе это не поймает всё, но сузит пространство возможных атак и рано выявит очевидные ошибки.

Почему здесь нужен настоящий аудит, а не только хорошие привычки

Самое неприятное в SQL-инъекциях то, что кодовая база может быть безупречной в 999 местах, а один «сырой» запрос в одном редко используемом админском инструменте сведёт всё на нет. Автоматические сканеры ловят часть таких случаев, но действительно важные находит только настоящий, внимательный ручной анализ того, как на самом деле строятся запросы, — а не просто проверка, используется ли ORM.

Такой ручной аудит безопасности приложений — когда мы действительно читаем, как устроены ваши запросы и логика контроля доступа, а не просто запускаем сканер и считаем дело сделанным, — основа работы Burncode в области безопасности наряду с усилением защиты инфраструктуры и подготовкой команд к настоящему аудиту на соответствие требованиям.