вторник, 28 января 2014 г.

stand back, we have PHDays! write-up

PHDays CTF quals.

stand back, we have PHDays! write-up  TRex CTF Team

Отныне буду публиковать райт-апы на блоге. С целью повышения популярности блога. Разумеется не без ссылки на форум.

Итак stand back, we have PHDays!
Задача на две категории - crypto и web.

Когда мы открываем задачу мы видим такое вот окно:




Ни одна ссылка на странице не работает. Ничего вообще хоть сколько нибудь интерактивного.
Единственная зацепка - кука. Сайт каждому зашедшему шлет куку. Если куку изменить - ничего не происходит. Если куку удалить, то сайт шлет нам новую куку, уже другую.
Кука выглядит примерно так:
%2Fbt5GIxaW53eI7rJtbeSoO8TS5B1lc6kqkzTPNXtnHk%3D
Если перевести из base64, получается 32 байта. На взгляд совершенно случайных.
Что делать совершенно не понятно =). Я долго бился в разных попытках, пока не попросил совета у Виктора. Он попросил дать ему несколько кук, чтобы он их рассмотрел повнимательнее.
Я написал скрипт который доставал любое количество кук (для дальнейших исследований у меня был файл с 500 кук)
Куки в хексе выглядят так:
ee8c6d18bf475ca2ba2edfe1e2b993f9a82833d437b5eca8b04e823384cf9e55
e2bb690c930d4f94f232cde8c8bfa7bfad1e38c263e79983c05df545c5cfbc67
bd806679d2706cf4c809caddc68cff9f9b3120ad65a1e797a1758322fccba47d
f8fc7e65b14b5f91ce0bbd95ff84fabdaf2131a567969e839f60d10cc0da8900
d9be0f5d80087081d228e1eae086fb9efa5d2a8e6783c8b5946fda25f2c6874a
fb9470778d526c87de27fcf4e5a6a9bcb61c0db261a6ede6be17f52fe3b9ab5e

Можно подметить закономерность. Первый байт всегда больше 0x80. Последний всегда меньше. Мы предположили, что некоторые случайные данные ксорятся на некоторую постоянную последовательность. Причем данные не совсем случайны. Скорее всего это текст.
Получается, что можно попробовать провести атаку на not-one-time-pad и выявить ключ.
Это и было сделано.
После нескольких экспериментов с алфавитом (оказалось что используется алфавит [a-z][A-Z][0-9]!@#$) получился такой вот код, восстанавливающий ключевую последовательность:
file = open('cookies.txt')

arr = [] while True:     st = file.readline()     if st == '':         break     st = st[:-1]     st = st.decode('hex')     arr.append(st)     keys = [] import string print len(string.printable) print string.printable alph = 'abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 +=-[]\'"*%!$/\\.,<>{}()?:;@~^\r\n\t#' alph = 'abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$' for index in range(32):     key = []     for i in range(256):         good = True         for st in arr:             ch = ord(st[index])             res = (ch ^ i) % 256             if chr(res) in alph:                 continue             else:                 good = False                 break         if good:             key.append(i)     keys.append(key)        print keys print keys key = '' for ch in keys:     key+=chr(ch[0])     print key

Теперь была возможность послать что нибудь на сервер. После нескольких попыток послать что то типо admin и administrator, мы попробовали поставить кавычку иии:
Отлично - инъекция. Казалось бы дальше все понятно.
Однако все не так просто. У нас есть только 32 байтовый ключ. Собственно и инъекция могла содержать только 32 символа. И так получается, что стандартные техники типо select table_name from information_schema.tables limit ... не работают, потому что тупо не влезают. Скрипт также не позволял делать инъекцию с -- в конце. Работало что то типо:
aaa' or '1'='1
На такой строке мы получали новый вид страницы:

Дальше мы придумали такую вот слепую инъекцию, которая позволила восстановить id (надо было сначала угадать, что в таблице есть такое поле):
"a' or id LIKE '%s%%' or 7='" % try
, где try - это пробуемая строка.
Так можно было восстановить первые 5 символов id, на больше не хватало длины строки. Но остальные можно было восстановить также через LIKE с использованием двух символов %:
"a' or id LIKE '%%%s%%' or 7='" % try

Ну ок мы получили id. Что дальше? Это id можно установить в куку (разумеется зашифровав) и нам выдает что мы admin без всякой инъекции. Но флага не дает...
Значит флаг где то в БД. таблице key и flag в БД не было. А узнать список таблиц мы не могли из за длины запроса...

Уже на следующий день с утра Миша предложил проверить пароль админа. До этого я и не смотрел его. Он вроде и не нужен на сайте. Тем не менее поле password было в текущей таблице.
 Восстановив его как и id я получил:
 50m37im35_y0u_d0_n07_n33d_a_k3y_70_d3cryp7_aes
Очень похоже на флаг, но сайт его не принимал.
Ну чтож, пришлось копать дальше.

Следующее предположение было, что  мы открыли не весь ключ. 
Если предположить что так то мы можем вычислить остальной ключ следующим образом:
мы шлем запрос с такой строкой
' or '1'= ____
где ___ столько пробелов, сколько надо чтобы дополнить запрос до 32 байтов. И следующим байтом мы шлем по очереди все 256 символов. Только на одном из этих запросов система не даст ошибку. Причем мы точно знаем, что этот символ с той стороны воспринимается как кавычка. Значит если поксорить символ с которым не выдалась ошибка на 0x27 (кавычку), то мы вычислим следующий байт ключа. Здорово. Таким образом я восстановил еще 120 байт ключа.
Теперь можно было делать обычные запросы.
Но... в базе ничего не было. Других таблиц не было. В таблице users, к которой и был уязвимый запрос, был только один кортеж. И только 3 столбца. Все нам было известно.
Я уже начал просматривать системные таблицы БД, думая что что то лежит в них. Потом я все таки решил считать password из таблицы users и нашел вот что:
 50M37IM35_Y0U_D0_N07_N33D_A_K3Y_70_D3CRYP7_AES

ЁПРСТ. Флаг у меня уже был давным давно. Только я не учел что LIKE пофиг на регистр, и поэтому нижний тоже подошел, а он в моем переборщике стоял первым. Можно было и не восстанавливать остаток ключа!!

Часа 3 наверно было потрачено зря... Но какая разница. Флаг был найден.

После CTF я узнал что вебсайт отдавал еще и исходники по запросу /index.php~
Оттуда я узнал что скрипт позволял делать и не слепую - обычную инъекцию. А я все атаки делал через слепую.
Короче можно было решить эту задачу намного проще. Но, значит соображалка у нас работает хорошо, если мы смогли решить задачу в которой предполагалось знание исходников без исходников. =)

Всем спасибо за прочтение.



 

Комментариев нет:

Отправить комментарий