17 ноября 2011 в Санкт-Петербурге прошло соревнование по информационной безопасности rfCTF 2011.
Тема соревнования — безопасность систем, использующих технологии RFID. Участникам был представлен набор заданий и 6 часов на их выполнение (стиль task-based CTF).
Место проведения — ВК Ленэкспо: Санкт-Петербург, Васильевский остров, Большой пр., 103 корп. 7
Время проведения — с 11:00 до 17:00.
Участие приняли команды петербургских вузов:
- RDOT.ORG
- PeterPen (СПбГУ, матмех)
- KillDozer (СПбГУ, матмех)
- noob technology (ИНЖЭКОН)
- Cлоупок (ИНЖЭКОН)
На соревновании для решения тасков участникам были предоставлены карточки Mifare разных типов, а также ридер IronLogic Z-2 USB MF.
Полезные ссылки:
- Mifare (Википедия)
- Документация, даташиты и SDK. Внимание! SDK ридера только для Win32.
- Репозиторий для участников (задания и подсказки)
rfCTF 2011 прошел в рамках II международной специализированной выставки «Информация: техника и технологии защиты».
Организаторы соревнования — команда Leet More. ;)
UPD: соревнования закончились! финальный скорборд:
UPD2: фотографии с закрытия
Разбор заданий
1. PYFI console (100) [задание] [хинт 1]
Это задание было самое простое из всех. Оно состояло из двух частей:
– проверка данных с карточки:
xx = [0, 1] for i in xrange(14*12-1): xx.append(xx[i] + xx[i+1]) |
Легко видеть, что массив заполняется числами Фиббоначи.
Затем выбирается случайная ячейка на карточке, и число из неё сравнивается с соответствующим числом последовательности.
То есть, необходимо заполнить всю карточку числами из этой последовательности.
– выполнение кода:
code = str(card.read(15, 0)) + str(card.read(15, 1)) + str(card.read(15, 2)) code = code.rstrip("\x00") try: __builtins__ = __import__ = None os = sys = quit = locals = globals = None eval(code) |
Так как было известно, что флаг лежит в c:\flag.txt, то можно было использовать следующий код для записи его на карточку:
card.write(0,1,open("c:/flag.txt").read()) |
После выполнения кода флаг лежит во втором блоке первого сектора.
Исходники сервиса
Решение – скрипт, записывающий на карточку правильный код.
2. LeetSec (300) [задание] [хинт 1] [хинт 6] [хинт 7] [хинт 9]
Этот квест состоит из двух этапов проверки, после прохождения которых флаг записывается на карточку.
– код MAGIC в третьем блоке второй секции:
MAGIC = "\xde\xad\xfa\xce" + "\xa9" * 8 + "\xf1\xf2\xf3\xf4" |
Здесь всё просто – записываем на карточку строчку (в хексах) DEADFACEA9A9A9A9A9A9A9A9F1F2F3F4.
– основная проверка
N = 308437044275067938131653453561001047357000826062737759785845990336 C = 111111111111111111111111111111111111111111111111111111111111112000 E = 31337 key = (s2n(card.read(1, 0)) << 128) | s2n(card.read(1, 1)) if pow(key + pow(serial, E, N), 2, N) == C: return grant(card) else: return deny("Bad key") |
Если внимательно изучить этот код, то можно понять, что нам необходимо найти такое число X, что
мы можем вычислить, поэтому
Также для упрощения мы можем заменить X + K на M:
То есть нам просто нужно извлечь квадратный корень по модулю N.
Для извлечения квадратных корней по простому модулю существуют эффективные алгоритмы (например). В данном случае N составное (можно факторизовать здесь или здесь):
2^6 · 3^3 · 761364314999 · 891083343019 · 263094610336089343590963140117371337377
Поэтому, необходимо извлечь корень по каждому простому модулю, а потом получить полный корень с помощью Китайской Теоремы об Остатках. Единственная проблема – большинство реализаций алгоритмов извлечения корня по простым модулям не справляются со степенями простых – например 2^6 или 3^3. Но, так как эти числа достаточно малы, можно перебрать все остатки (найти корень перебором).
UPD: еще одно решение, без кодинга и матана — vos
Зачем нам все эти теоремы, если есть Вольфрам =)
Нам надо решить уравнение:
1. Посчитаем часть, которая нам известна, т.е. S^E mod N. Допустим, у нашей карточки серийник 0xAABBCCDD12345678AABBCCDD12345678. Wolfram »
Результат:
2. Решим квадратное уравнение по модулю (x^2 mod N = C). Wolfram »
Результат:
3. Вычтем из второго первое. Wolfram »
Результат:
Итак, наш ключ: 0x00000000015d1c3d3f84b44b6929bd2c41650d711f16d00df87b32cf7f802d68 (нули спереди добавлены, чтобы было ровно 32 байта)
4. Проверяем.
c:\>\Python26\python.exe Python 2.6.6 (r266:84297, Aug 24 2010, 18:46:32) [MSC v.1500 32 bit (Intel)] Type "help", "copyright", "credits" or "license" for more information. >>> N = 308437044275067938131653453561001047357000826062737759785845990336 >>> C = 111111111111111111111111111111111111111111111111111111111111112000 >>> E = 31337 >>> serial = 0xAABBCCDD12345678AABBCCDD12345678 >>> key = 0x00000000015d1c3d3f84b44b6929bd2c41650d711f16d00df87b32cf7f802d68 >>> pow(key + pow(serial, E, N), 2, N) == C True |
3. Arigato (250) [задание] [хинт 3]
Программа запакована UPX, однако, помимо этого имеет одну особенность. Между 2 и 3 секциями запакованного файла расположен оверлей (overlay) – область, которая не будет загружена в память при запуске файла и как следствие – будет отсутствовать в дампе памяти.
Конец второй секции: 0x01AA00 + 0x400 = 0x01AE00
Начало третей секции: 0x01BE00
Размер оверлея: 0x01BE00 – 0x01AE00 = 0x1000
Оверлей запакован:
Так же, если распаковать семпл UPX.exe, то оверлей так же станет не валидным.
Распаковав (с потерей оверлея или без) и немного поанализировав семпл, можно понять, что основная функция находится по адресу 0x0402420
В этой функции сначала происходит чтение карточки (одного из 4 секторов, выбранных случайно):
После этого подряд вызываются 2 функции 0x0401550 и 0x0401630.
В функции 0x0401550 происходит чтение оверлея из файла:
В функции 0x0401630 происходит расшифровка и выполнение оверлея. После чего оверлей заново шифруется. Алгоритм, используемый для шифрования TEA, ключи для расшифровки генерируются правильно только в случае, если присутствие дебаггера скрыто (чего легко можно достигнуть множеством плагинов). В случае успешной расшифровки, код из оверлея выполняется, а в качестве параметра ему передается считанная с карты информация. Далее, если код из оверлея возвращает true – генерируется секретный флаг и записывается на карту.
Все, что нужно сделать для получения доступа к «секретному» коду, включить скрытие дебаггера и поставить брекпоинт по адресу 0x04016DB. После остановки под дебаггером можно было проанализировать достаточно простой код проверки:
В этом коде первая часть входных данных сравнивается со строкой:
“The obstacle is “
Если в гугле вбить данную строчку (а в коде есть подсказка выводимая как
OutputDebugStringA (“You should google a little for full answer :)”)), то первой же строчкой выпадает:
“The obstacle is the path”
Это и есть ответ, если его записать на первые 4 секции карточки, то на один из 8 секторов с 32 по 39 запишется флаг.
Прикладываю полный код программы, а так же распакованный оверлей.
4. Gosuslugi (250) [задание] [хинт 2] [хинт 5] [хинт 10]
Сервис Gosuslugi представлял собой оболочку терминала госуслуг России 2050 года =)
Код задания написан на PHP, БД — SQLite.
Команды могли авторизоваться на терминале под своими аккаунтами. Чтобы решить задание, нужно было войти в терминал под именем другого пользователя.
Авторизация под легитимным пользователем:
Обход аутентификации возможен благодаря двум уязвимостям, одна из которых находится в коде оболочки терминала, а другая содержится в штатном SDK rfid-ридера.
Разбираемся по порядку.
1) SQL-инъекция в коде авторизации
Файл ui_pages/01_login.php:
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | update_device_status("Чтение карты..."); sleep_with_client_ping(0.2); $cardSn = mifare_read_card($cardId, 0, 8); if ($cardSn === false) { update_device_status("Ошибка чтения карты!"); sleep_with_client_ping(2); continue; } update_device_status("Вход в систему..."); sleep_with_client_ping(0.2); $result = sqlite_query($GLOBALS['SQLITE_DB'], "SELECT id FROM people WHERE auth_rfid_sn = '$cardSn'"); if ($result === false) { update_device_status("Ошибка работы с БД!"); sleep_with_client_ping(2); continue; } if (!sqlite_num_rows($result)) { update_device_status("Карта не опознана!"); sleep_with_client_ping(2); continue; } $row = sqlite_fetch_array($result); @session_start(); $_SESSION['auth_person_id'] = (int)$row['id']; update_device_status("Вход произведен!", 'success'); js_timeout_redirect("?page=account", 3); |
127 | $cardSn = mifare_read_card($cardId, 0, 8); |
В переменную $cardSn считываются первые 8 байт с карточки Mifare Ultralight.
В первых 8 байтах содержится серийный номер карточки; этот блок данных защищен от записи.
137 | $result = sqlite_query($GLOBALS['SQLITE_DB'], "SELECT id FROM people WHERE auth_rfid_sn = '$cardSn'"); |
Серийный номер подставляется в авторизационный SQL-запрос в бинарном виде.
Возможна SQL-инъекция длиной 8 байт при условии, что мы контролируем первые 8 байт карточки.
Чтобы использовать SQL-инъекцию, необходимо иметь возможность влиять на первые 8 байт, прочитанные ридером с карты. На самой карте этот блок перезаписать невозможно, из-за этого остается два варианта:
- 1. Использовать дорогой эмулятор Mifare-карточек
- 2. Использовать вторую уязвимость в SDK ридера ;)
Эмулятора карточек у нас нет…
2) Ошибка наложения секторов в SDK ридера
Уязвимость существует из-за того, что библиотека SDK неверно обрабатывает ситуации прерывания контакта карточки со считывателем.
Вместо того, чтобы заполнить неверно считанный блок данных нулями или вернуть ошибку чтения, библиотека заполняет блок, который не удалось считать, данными из ранее успешно считанного блока.
Кроме того, функция чтения карт UltraLight читает блоки от конца к началу, таким образом, при прерывании контакта в нужный момент первый блок, который содержит серийник, может быть перезатёрт данными из последующих блоков карты, куда можно записать любые данные.
Видео этого эффекта:
С помощью этой уязвимости мы можем контролировать 8 байт, которые попадут в SQL-запрос в неизменном виде.
3) Эксплоит
Для того, чтобы войти в систему от имени другого пользователя, нам нужно подставить в SQL-запрос такие данные (8 байт), чтобы он вернул чужой id.
SELECT id FROM people WHERE auth_rfid_sn = '$cardSn' |
Если в переменной $cardSn будет содержаться ‘ OR 1/*, запрос примет вид:
SELECT id FROM people WHERE auth_rfid_sn = '' OR 1/*' |
В результате, он вернет все записи из таблицы в БД, и для авторизации будет использована самая первая запись.
Для эксплуатации уязвимости можно использовать одну карточку, на всех записываемых секторах которой записаны байты ‘ OR 1/*.
После использования трюка с прерыванием контакта с такой карточкой, нас пускает в терминал под пользователем с наименьшим id:
Сливаем флаг себе на карточку:
Задание решила одна команда, бонусный флаг не нашёл никто :-(
P.S.
Бонусный флаг лежал у пользователя со вторым id. Его можно было вытащить, использовав немного другую инъекцию: ‘=id-2/*
Запрос превращается в:
SELECT id FROM people WHERE auth_rfid_sn = ''=id-2/*' |
Интерпретируется так:
SELECT id FROM people WHERE (auth_rfid_sn = '') = (id - 2) (auth_rfid_sn = '') == 0 (FALSE) значит 0 = (id - 2) т.е. id = 2 |
8 comments
1 ping
Skip to comment form
кто бы сомневался :)
а фотографии будут?)
Надо поискать. А ещё потрясем нашего фотографа :)
Ребятули, я конечно все понимаю, и что RuCTFe, и 0nights, но фитбек полностью хочется очень увидеть да и фоточки не помешали бы) Трясите фотографов!
Добавил ссылку на фото: фотографии с закрытия
Добавил разбор своего таска и содержание ;)
Добавил еще одно решение LeetSec
Читер!
[…] Разбор тасков […]