Что такое SQL-инъекции и как им противостоять

Андрей Коваленко

SQL-инъекции (SQL injections, SQLi) — самый хорошо изученный и простой для понимания тип атаки на веб-сайт или веб-приложение. Тем не менее, он странным образом остается весьма распространенным и в наши дни. Организация OWASP (Open Web Application Security Project) упоминает SQL-инъекции в своем документе OWASP Top 10 2017 как угрозу номер один для безопасности веб-приложений, и вряд ли положение сильно изменилось за четыре года.

Попасться на SQL-инъекцию — это все равно что в преферансе вистовать «стоя» на девятерной и в первый ход зайти с «голой» семерки или нарваться на «детский мат» в шахматах. Первое случилось с автором этих строк на первом курсе КПИ и стоило ему стипендии за месяц, а второе как минимум дважды происходило на официальных турнирах, согласно онлайн-базе данных. Итак, что же делает этот классический хакерский трюк таким живучим? Давайте разбираться.

Источник: reddit.com

Содержание:
Источник уязвимости — язык SQL
Атака
Реальные примеры
Защита от SQL-инъекций
Заключение

Источник уязвимости — язык SQL

Почему вообще возможны SQL-инъекции?

Когда пользователь вводит и отправляет данные на веб-сайте, эти данные попадают в веб-приложение, которое в свою очередь использует эти данные при доступе к БД.

Допустим, у нас есть веб-сайт онлайн-магазина, и пользователь вводит название продукта в строке поиска. Для обращения к данным в реляционных БД используется SQL (structured query language) — специальный язык, похожий на естественный (английский) язык. Этот язык стандартизирован (самый последний стандарт — SQL:2008), и основные его команды одинаковы для разных производителей СУБД — Microsoft, Oracle, MySQL, PostgreSQL и других.

Например:

CREATE TABLE Users (
  Id int PRIMARY KEY,
  Name varchar(100) NOT NULL,
  CreationDate datetime NOT NULL)

Этот запрос (команды в SQL называют запросами, англ. Queries) создает в базе данных таблицу Users (пользователи) с тремя полями — целочисленным идентификатором (Id), именем (Name) и датой создания записи о пользователе (CreationDate).

DROP TABLE Users

Этот запрос удаляет таблицу с пользователями из базы данных (не данные, т.е. строки из таблицы, а саму таблицу; для удаления строк используется запрос DELETE — смотрите ниже):

SELECT
    Id,
    Name
FROM
    Users
WHERE
  CreationDate > '2021-01-01'

Читает всех пользователей, созданных после 1 января 2021 года (в СУБД MS SQL Server).
DELETE FROM Users WHERE Id > 10
Удаляет всех пользователей с идентификатором большим 10
UPDATE Users SET CreationDate = '2020-12-01'
Устанавливает для всех (поскольку нет условия WHERE) пользователей дату создания в 1 декабря 2020 года.

Итак, веб-приложение (веб-сайт) для доступа к своим данным использует параметры, введенные пользователем на сайте.

Например, если пользователь ищет на нашем сайте веб-магазина ноутбуки и вводит слово «ноутбук» в строке поиска, то соответствующий запрос может иметь (упрощенный) вид:

SELECT
    Id,
    Name,
    Price
FROM
    Products
WHERE
    Name LIKE 'ноутбук%'

Оператор LIKE и символ подстановки “%” (wildcard character) используются для задания условия по подстроке.

Соответственно, фрагмент программы, который формирует этот запрос для выполнения, в простейшем случае формируется из шаблона команды SELECT с подставленным значением пользовательского ввода (C#):

var selectProductQuery = 
@"
SELECT
    Id,
    Name,
    Price
FROM
    Products
WHERE
    Name LIKE '" + productName + "%'";

Атака

Теперь предположим, что пользователь — злоумышленник, и вводит в строке поиска не название продукта, а вредоносный фрагмент SQL-кода, например:

a'; DROP TABLE Products; --

Тогда результирующий запрос будет иметь вид двух последовательных команд и одного комментария:

SELECT
    Id,
    Name,
    Price
FROM
    Products
WHERE
    Name LIKE '%a';

DROP TABLE Products;

-- %'

Комментарии в SQL имеют вид:

  1. /* это многострочный
  2. */ комментарий
  3. -- однострочный комментарий

Итак, что мы видим? Введенный пользователем текст в форме поиска превратил SQL-команду выбора продуктов из таблицы в два запроса: первый — бессмысленный, а второй — вредоносный, удаляющий таблицу продуктов из базы и делающий наше приложение (веб-сайт) нежизнеспособным.

Разумеется, это упрощенный пример, который предполагает, что команда, состоящая из нескольких запросов, будет выполнена как последовательность этих запросов, что данные пользователя не валидируются веб-фреймворком и так далее, но суть любой SQL-инъекции заключается именно в этом: злоумышленник внедряет («впрыскивает» — отсюда и название атаки «инъекция») вредоносный код в обычную форму ввода или в строку адреса, вынуждая веб-приложение произвести саморазрушительные действия или предоставить доступ внешнему атакующему к несанкционированным данным.

Реальные примеры

Наиболее часто применяются два вида атак SQL injection: Boolean-атака (Boolean Based SQLi) и UNION-атака (UNION Based SQLi).

Boolean-атака

В адресной строке браузера вводится запрос вида

https://example.com/showItem?item=1%20or%201=1

Это может заставить бэкенд приложения выполнить SELECT-запрос с всегда истинным условием, что может привести к раскрытию несанкционированных данных.

UNION-атака

Ключевое слово UNION используется для объединения результатов двух и более запросов в один результат.

Например, у нас есть таблица продуктов:

Id Name Price
1 Ноутбук 29000
2 Карандаш 2

Выполнив запрос:

SELECT
    Id,
    Name,
    Price
FROM
    Products
UNION
SELECT
    NULL,
    CURRENT_USER,
    NULL

Мы получим такой результат (для БД MS SQL Server):Используя оператор UNION, злоумышленник может попытаться атаковать страницу поиска продуктов:

https://example.com/showProduct?id=1′ union select NULL,CURRENT_USER,NULL —

Что при определенном везении может раскрыть ему секретную информацию, такую как имя базы данных, имя пользователя, от которого происходит подключение к БД, и так далее. Понятно, что результат такой атаки может иметь катастрофические последствия для веб-приложения: раскрытие данных пользователей, полное разрушение приложения и его данных.

Источник: xkcd.com

Защита от SQL-инъекций

Параметризуйте это

Самое главное правило — данные, присылаемые пользователем, не должны участвовать в формировании текста SQL-запроса, во всяком случае напрямую. Достигается это использованием параметризированных (подготовленных) запросов.

Так, вместо подстановки (конкатенации) пользовательского ввода, надо использовать параметры, например, в случае C# вместо фрагмента, рассмотренного ранее:

var selectProductQuery = 
@"
SELECT
    Id,
    Name,
    Price
FROM
    Products
WHERE
    Name LIKE '" + productName + "%'";
command.CommandText = selectProductQuery;
var reader = command.ExecuteReader();

Надо использовать следующий код:

command.CommandText =
@"
SELECT
    Id,
    Name,
    Price
FROM
    Products
WHERE
    Name LIKE @p_productName";

command.Parameters.AddWithValue("p_productName", productName);
var reader = command.ExecuteReader();

В этом случае, какой бы текст пользователь не ввел в поле поиска продукта, приложение будет искать этот текст в качестве имени продукта, и если в тексте содержится нерелевантный фрагмент (например, с SQL-выражениями), никакой инъекции не произойдет, и продукт просто-напросто не будет найден.

Используйте хранимые процедуры

С технической точки зрения, это правило идентично предыдущему: пользовательский ввод не используется при динамической генерации SQL-запроса. Код хранимой процедуры неизменный и хранится в самой СУБД, а не в коде приложения.

Используйте белый список валидации

В некоторых случаях невозможно использовать параметризацию запросов.

Например, имя таблицы, из которой происходит выборка (SELECT), не может быть параметром, и в этом случае сам текст запроса формируется в зависимости от ввода пользователя.

В таком случае необходимо ограничить список допустимых значений, которые могут прийти от пользователя («белый список»).

Например, если из выпадающего списка веб-формы приходит значение Customers, то мы производим выборку из таблицы Customers, а если значение Supplier» — то из таблицы Suppliers.

Но если придет значение System_Users, которого приходить не должно, то это, скорее всего, значит, что мы имеем дело с злоумышленником, который с помощью Swagger или подобной программы пытается проверить наше приложение на прочность:

switch (param)
{
    case "Customers":
        tableName = "Customers";
        break;
    case "Suppliers":
        tableName = "Suppliers";
        break;
    default:
        throw new InputValidationException("Unexpected value.");
}

Валидируйте пользовательский ввод

Вообще в работе с пользовательским вводом руководствуйтесь принципом «пользователь — всегда потенциальный злоумышленник». Бэкенд не имеет права слепо доверять ничему, что приходит с клиента, даже если клиентское приложение валидирует пользовательский ввод. Злоумышленник может использовать Swagger, автоматические скрипты и другие средства преодоления клиентской валидации.

Поменьше привилегий

Системный пользователь (системная учетная запись), которая осуществляет доступ к данным, должна иметь как можно меньше привилегий на сервере.

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

Проверяйтесь

Всегда полезно проверять свое приложение на устойчивость, в том числе по отношению к SQLi-атакам. Одна из мощнейших и старейших утилит, предназначенных для поиска и устранения SQLi-уязвимостей — https://sqlmap.org/.

Заключение

В статье мы рассмотрели самый простой и самый распространенный тип атаки на веб-сайт — SQL-инъекцию. Давайте отметим основные тезисы:

  • Суть атаки заключается в попытке злоумышленника внедрить вредоносный SQL-код через легальный канал ввода (веб-форма, адресная строка браузера).
  • Наиболее эффективный способ защиты от SQL-инъекции — не использовать пользовательский ввод при построении SQL-запроса, а только в качестве значения параметров.
  • Всегда полезно валидировать (проверять) пользовательский ввод на стороне бэкенда. Пользователь — всегда злоумышленник.
  • Sqlmap — проверенное средство выявления SQLi-уязвимостей, и регулярное «обследование» им своего веб-приложения можно только приветствовать.

Ссылки

Качественное видео с дополнительной информаций по теме:

Примеры уязвимостей и противодействия им на разных языках программирования, основанные на чудесном комиксе xkcd: bobby-tables.com.

Останні статті

Обучение Power BI – какие онлайн курсы аналитики выбрать

Сегодня мы поговорим о том, как выбрать лучшие курсы Power BI в Украине, особенно для…

13.01.2024

Work.ua назвал самые конкурентные вакансии в IТ за 2023 год

В 2023 году во всех крупнейших регионах конкуренция за вакансию выросла на 5–12%. Не исключением…

08.12.2023

Украинская IT-рекрутерка создала бесплатный трекер поиска работы

Unicorn Hunter/Talent Manager Лина Калиш создала бесплатный трекер поиска работы в Notion, систематизирующий все этапы…

07.12.2023

Mate academy отправит работников в 10-дневный оплачиваемый отпуск

Edtech-стартап Mate academy принял решение отправить своих работников в десятидневный отпуск – с 25 декабря…

07.12.2023

Переписки, фото, история браузера: киевский программист зарабатывал на шпионаже

Служба безопасности Украины задержала в Киеве 46-летнего программиста, который за деньги устанавливал шпионские программы и…

07.12.2023

Как вырасти до сеньйора? Девелопер создал популярную подборку на Github

IT-специалист Джордан Катлер создал и выложил на Github подборку разнообразных ресурсов, которые помогут достичь уровня…

07.12.2023