CTF ringzer0team - SQL Injection 26: stackoverflow.com lied to me... again

В честь юбилейного подписчика под номером 3 (да их всего 3) я продемонстрирую решение задачки за 9 очков, решивших его пока всего 40 (включая меня) и надеюсь меня никто за это не осудит, ведь сами задания довольно старые, хотя и не теряют актуальности. Возможно решение не самое блестящее и может содержать часть кода которую можно отбросить, но я покажу ту инъекцию с помощью которой сам все получил. Поехали!

это не форма которую предстоит атаковать, а кнопка для перехода.
это не форма которую предстоит атаковать, а кнопка для перехода.

Нажав на кнопку мы попадаем на страницу на которой автор (по всей видимости) задания заявляет что StackOverflow неправ, в том что по информации с их сайта, нельзя сделать SQL statement (в нашем случае - нельзя сделать инъекцию) не зная названия столбцов, и дает три ссылки на пруфы (не кликайте по третьей если не хотите быть "отрикролленными" (be rick rolled), мем такой).

Действительно, если не использовать в SQL Select запросе *, то я не знаю других путей чтобы обратиться к столбцу, например по порядку.

То есть: (по крайней мере в Mysql) в SELECT запросе мы обращаемся либо ко всем столбцам через *, либо поименно к каждому.

И сразу маленький спойлер, это не очередная история про information_schema. Кликаем по "Start Here" и получаем:

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

1'+or+1=1+--+

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

Тут мы видим сообщение о том что мы введи какие то недопустимые значения, и я было переживал о том что это select или union, я проверил это с помощью комментариев. Сперва взял в комментарий весь добавленный кусок инъекции:

-1'+or+1=1+/*union+select+1,2,3*/+--+
сообщение никак не изменилось, тогда я убрал слово union
-1'+or+1=1+/*select+1,2,3*/+--+
сообщение осталось тем же, и тогда я убрал select
-1'+or+1=1+/*1,2,3*/+--+
но на удивление сообщение осталось, я убрал 1,2,3 и вернул union и select на место
-1'+or+1=1+/*union+select*/+--+
и получил снова список как и при инъекции 1'+or+1=1+--+

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

можно сделать выборку в под запросе по нескольким таблицам(количество таблиц равно количеству требуемых полей) и использовать ключевое слово join, для объединения таблиц, вместо запятой, а выборку основного запроса производить по всем полям, например:
-1'+or+1=1+union+select+*+from+(select+1)a+join+(select+2)b+join+(select+3)c+--+

получаем:

Как видим все удалось, НО: нам нужно сделать выборку из таблицы с 4 столбцами в запрос где в выборке всего три столбца, очевидно что если использовать * это вызовет ошибку. Для этого нам нужно перечислить необходимые поля, и в этом и есть весь подвох задания - названия полей не известны. Логический подбор типа id, name, username, flag, password, key, column не помог. Не помог даже наспех написанный скрипт на PHP генерирующий все названия с набором символов A-Z0-9 и количеством символов 6 (я его остановил через какое то время, поскольку очевидно что решение не брутфорс). Есть вариант попробовать узнать в information_schema, для этого составим инъекцию:

-1'+or+1=1+union+select+*+from+(select+1)a+join+(select+2)b+join+(select+column_name+from+information_schema.columns+where+table_name='flag')c+--+

Но получим снова сообщение о том что в запросе есть опасные символы и ключевые слова. Опытным путем было установлено что фильтруется именно information_schema и если в любом месте эту фразу(??) разделить комментарием /**/ то сообщение о недопустимом вводе исчезает, но пользы от этого знания мало.

Я перепробовал массу вариантов, и заново перерыл весь мануал по mysql, но никаких ухищрений не нашлось, даже идею с JSON_ARRAYAGG(*) не удалось воспроизвести, ведь он был добавлен в mysql 5.7.22, а версия mysql с которой мы работаем 5.7.21. Но не буду томить и приведу сразу ту инъекцию (я ее сохранил) с которой у меня все получилось, а потом объясню в чем тут смысл.

-1'+or+name+not+like+'M'/*!50000union+all+select+*+from+(select+database()+from+flag)a+join+(select+JSON_ARRAY(version())+from+flag)b+join+(select+d.1+from+(select+*+from+(select+1)e+join+(select+2)f+join+(select+3)g+join+(select+4)h+union+select+*+from+flag)d)c;*/+--+i

Сразу скажу что в запросе наверняка много лишнего, в конце статьи будет упрощенный запрос который покажет только то что нужно. Вся суть тут заключается в том что нам не нужно знать названия столбцов, мы можем исхитриться и дать им сами нужные имена. Для этого в очередном подзапросе (в моем случае в последнем), мы получаем данные не напрямую из таблицы flag, а из псевдо-таблицы к которой мы через union добавляем все поля из таблицы flag. Главное здесь чтобы количество полей в flag (а оно нам известно) совпадало с нашей самодельной таблицей, то есть количество надо подобрать. Далее мы используем выдуманный нами alias (в моем случае d) и порядковый номер столбца в запросе и поочередно выводим поля. Если убрать лишнее и немного облагородить получим:

-1'+union+select+*+from+(select+null)a+join+(select+d.2+from+(select+*+from+(select+1)e+join+(select+2)f+join+(select+3)g+join+(select+4)h+union+select+*+from+flag)d)c +join+(select+null)b +--+

Флаг наш, как и 9 очков! Нельзя сказать что это простое задание, и ход мыслей тут существенно урезан, потому что пробовались разные варианты, прежде чем нашелся правильный подход, но в очередной раз стоит задуматься о WAF и фильтрации запросов, поскольку даже без названий полей злоумышленник может получить информацию из БД!

That's all, folks!