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

Что делает запрос уязвимым

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

Почему ручное экранирование недостаточно

Распространённая реакция на этот риск — попытка вручную очистить или экранировать специальные символы перед вставкой в запрос, например экранировать апострофы. Такой подход остаётся хрупким: он зависит от памяти и аккуратности разработчика на каждой точке входа сайта, и одной упущенной точки достаточно, чтобы снова открыть уязвимость. Он также может оказаться неполным в зависимости от контекста использования данных внутри запроса, что делает его ненадёжной защитой, а не устойчивым решением.

Подготовленные выражения: строгое разделение кода и данных

Подготовленные выражения, доступные в PHP нативно через расширения PDO или MySQLi, решают эту проблему в корне, полностью отделяя структуру SQL-запроса от вставляемых в него значений. Запрос сначала отправляется в базу данных в фиксированном виде с зарезервированными местами для переменных значений, а затем эти значения передаются отдельно и ни при каких обстоятельствах не могут быть интерпретированы как SQL-код, независимо от их содержимого. Злоумышленник может ввести любую строку символов, включая полноценные SQL-инструкции, но эти символы всё равно будут восприняты как обычные текстовые данные без какого-либо влияния на структуру запроса.

Конкретный пример различия

Запрос, построенный прямой конкатенацией, обычно выглядит как строка, собранная с пользовательской переменной, вставленной посреди SQL-текста. Подготовленное выражение с PDO разделяет эту логику: запрос содержит placeholder, обычно представленный знаком вопроса или именем с двоеточием впереди, а реальное значение передаётся отдельно во время выполнения через специальный метод объекта соединения. Это структурное различие, будучи понятым, не требует больше строк кода, чем ручная конкатенация, вопреки иногда распространённому мнению.

Что не покрывают подготовленные выражения

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

Случай ORM и современных фреймворков

Распространённые PHP-фреймворки и ORM-библиотеки по умолчанию используют подготовленные выражения внутри себя, что автоматически защищает разработчика, пока он использует методы, предоставляемые этими инструментами, а не строит сырой запрос вручную внутри тех же фреймворков. При полностью индивидуальной разработке без фреймворка ответственность за систематическое использование подготовленных выражений полностью лежит на привычках кодирования, принятых с самого начала проекта, что делает ещё более важным сделать это неоспоримым правилом, а не разовой опцией.

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