Тестовый стенд доступен по адресу sql.kalovik.ru
Внедрение SQL-кода — это атака, в которой вредоносный код вставляется в строки, которые позже передаются ядру СУБД для синтаксического анализа и выполнения.
Суть атаки в том, что атакующий может вставить специфичные для языка SQL символы, чтобы вмешаться в логику запроса и выполнить произвольный SQL-код.
SQL: язык общения с базой данных
SQL (Structured Query Language) — это язык, с помощью которого программы разговаривают с базами данных. Простыми словами, SQL — это как заказ в ресторане:
SELECT
— "Принесите мне меню."
INSERT
— "Добавьте новое блюдо в меню."
UPDATE
— "Поменяйте цену у пиццы."
DELETE
— "Уберите это блюдо из меню."
Когда вы заполняете форму на сайте (регистрируетесь, заказываете товар, оставляете комментарий), ваш ввод превращается в SQL-запрос. Сервер отправляет этот запрос в базу данных, получает ответ и показывает вам результат.
Но что будет, если подать базе данных не тот запрос, который она ждёт?
Всё упирается в доверие. Если разработчик без проверки передаёт пользовательские данные в SQL-запрос, этим может воспользоваться злоумышленник.
Пример:
SELECT * FROM users WHERE username = '$username' AND password = '$password';
Обычный пользователь введёт логин и пароль, и запрос выполнится корректно.
Но если в поле логина ввести:
' OR '1'='1
Запрос превратится в:
SELECT * FROM users WHERE username = '' OR '1'='1' -- AND password = '';
Что происходит:
Разберем этот запрос подробнее:
username = ''
Проверка имени пользователя на пустое значение.
OR '1'='1'
Это условие всегда истинно.
--
Это комментарий в SQL. Он отбрасывает остаток запроса, включая AND password = ''
.
Таким образом, запрос сводится к:
SELECT * FROM users WHERE username = '' OR TRUE;
Поскольку любое условие OR
с TRUE
всегда истинно, запрос вернет всех пользователей из таблицы users
, игнорируя проверку пароля. Злоумышленник получает доступ без знания пароля.
В зависимости от способа получения доступа к данным бэкенд-сервера и потенциальных масштабов ущерба SQL-инъекции можно разделить на три категории:
Внутриполосная атака (In-band SQLi)
Это самый простой вид атаки для злоумышленников, так как для реализации атаки и сбора результатов используется один и тот же канал связи. Этот тип SQLi-атак разделяют на два подвида:
Инференциальная атака (Inferential SQLi, также известна как «слепая SQL-инъекция»)
При таких атаках злоумышленники изучают ответы и поведение сервера после отправки наборов данных, чтобы узнать больше о структуре базы данных. При этом никакие записи из базы данных веб-сайта не передаются злоумышленнику, и он не видит их в том же канале связи, как в случае внутриполосной атаки (этим и объясняется название «слепая SQL-инъекция»). Такие атаки разделяют на два подвида:
Внеполосная атака (Out-of-band SQLi)
Такая атака происходит в двух случаях:
SQL-инъекция — это не магия, а банальное недопонимание того, как работают базы данных. Хакеру не нужно ломать сервер или подбирать сложные пароли. Всё, что ему нужно — это возможность отправить базе данных свой запрос.
Чтобы понять, как это работает, разберёмся в самых популярных видах атак и почему они срабатывают.
Это один из самых простых, но, к сожалению, до сих пор распространенных видов SQL-инъекций. Суть атаки заключается в том, что пользовательский ввод напрямую, без какой-либо обработки или проверки, вставляется в SQL-запрос.
Представьте себе веб-страницу с формой для входа. Пользователь вводит свой логин и пароль, нажимает кнопку "Войти", и эти данные отправляются на сервер. Сервер, в свою очередь, формирует SQL-запрос для проверки наличия такого пользователя в базе данных.
Пример уязвимого кода (PHP):
$username = $_POST['username'];$password = $_POST['password'];$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";$result = mysql_query($query); // УЯЗВИМО!
В этом коде значения, введенные пользователем в поля username и password, напрямую подставляются в SQL-запрос. Это создает серьезную уязвимость.
Кейс для начинающих:
Представьте, что в базе данных есть таблица users со столбцами id, username и password. Разработчик написал код, который должен проверять, есть ли в базе пользователь с введенными логином и паролем.
Обычный пользователь вводит:
username: ivan
password: mypassword
Запрос к базе данных будет выглядеть так:
SELECT * FROM users WHERE username = 'ivan' AND password = 'mypassword';
Все работает корректно, база данных ищет пользователя с логином ivan и паролем mypassword.
Теперь представим злоумышленника, который вводит в поле username: admin' --
Что происходит:
Введенное значение подставляется в SQL-запрос, и он превращается в:
SELECT * FROM users WHERE username = 'admin' -- ' AND password = '';
Почему это работает:
--
в SQL означает начало однострочного комментария. Все, что находится после --
до конца строки, игнорируется базой данных. В результате часть запроса AND password = ''
просто отбрасывается. Запрос фактически превращается в:
SELECT * FROM users WHERE username = 'admin';
Таким образом, если в базе данных есть пользователь с логином admin, запрос вернет его данные, независимо от введенного пароля. Злоумышленник получает доступ к учетной записи администратора.
Как защититься:
Самый эффективный способ защиты от прямого встраивания кода — использование параметризованных запросов (prepared statements) или связанных параметров (bound parameters).
Пример безопасного кода (PHP с PDO):
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");$stmt->execute(['username' => $username, 'password' => $password]);
В этом случае значения
username
иpassword
передаются в запрос отдельно от SQL-кода, как параметры. База данных обрабатывает их как обычные данные, а не как часть SQL-кода, что предотвращает инъекцию.
Почему параметризованные запросы безопасны:При использовании параметризованных запросов база данных сначала компилирует SQL-запрос, а затем подставляет в него значения параметров. Это гарантирует, что пользовательский ввод никогда не будет интерпретирован как часть SQL-кода.
Примеры на других языках:
Python (psycopg2 для PostgreSQL)
cursor.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password))
Java (JDBC)
PreparedStatement statement = connection.prepareStatement("SELECT * FROM users WHERE username = ? AND password = ?");statement.setString(1, username);statement.setString(2, password);ResultSet result = statement.executeQuery();
Node.js (mysql2)
const [rows] = await connection.execute('SELECT * FROM users WHERE username = ? AND password = ?', [username, password]);
Чтобы обнаружить уязвимости SQL-инъекции, рассмотрите следующее:
--
, ;
, '
, или "
чтобы увидеть, вызывают ли они ошибки или непреднамеренное поведение.