Статья Как я сдампил GeekBrains: Проходим тесты, не открывая браузер

Как я сдампил GeekBrains. Проходим тесты, не открывая браузер

KDPV.jpeg


Отказ от девственности ответственности
Данному тексту около полугода, поэтому я не могу утверждать, что все моменты, описанные в статье, продолжают существовать до сих пор. Статья лежала у меня «в столе» некоторое время после того, как я написал в поддержу Mail.ru (заявка была принята, но ответа не последовало), а после я как-то забыл об этой забавной истории. На текущий момент эту статью следует рассматривать скорее как «байку из склепа», хотя я практически не сомневаюсь, что вся логика проведения сбора дампа осталась такой же. Но заниматься последним я, разумеется, никого не призываю, никому не рекомендую и вообще осуждаю все плохие дела.

Здравствуй, Codeby. В какой-то момент я понял, чего мне не хватает в этой жизни — сертификатов! Еще неделю назад я чувствовал себя хуже некуда: у меня была низкая самооценка, я был робок, застенчив и безынициативен. Жалкое зрелище. Теперь же, с гордостью сообщаю, что, став сертифицированным специалистом в областях вёрстки, программирования (на большинстве популярных языках), работы с разнообразными фреймворками, администрирования БД (разных вендоров), разработки игр (на Unity и не только), Linux-систем и компьютерных сетей, информационной безопасности, английского языка и многих, многих других, я чувствую себя хозяином этой жизни. И благодарю я за это отнюдь не свою Альма-матер, о нет. В этом мне помог образовательный портал GeekBrains, и это моя исповедь...

Шучу. Я всего лишь сдампил базу данных их тестов. А ниже мы поговорим о том, почему не стоит показывать результаты пройденного только что тестирования в виде списка заваленных вопросов; почему следует блокировать регистрацию новых пользователей с 10-минутных почт; а также о таком феномене, как «риторический вопрос в плоскости учебной площадки GeekBrains». Не проходите мимо, СЕРТИФИКАТЫ ДЛЯ ВСЕХ, ДАРОМ, И ПУСТЬ НИКТО НЕ УЙДЁТ ОБИЖЕННЫЙ!

Минутка серьезности
Разумеется, выкладывать исходные коды написанной утилиты для автоматизации сбора дампа и/или сами ответы на тесты, тем самым обесценив труды команды GeekBrains по созданию последних, я не стану — это аморально, некрасиво и вообще фу таким быть. Но о сложившейся ситуации не могу не рассказать. В образовательных, развлекательных и поучительных целях.

Пруф того, что дамп существует — . Некто B Rainfux за неделю прошел бо́льшую часть тестов, вот, кстати, и его (нужно быть авторизованным, чтобы видеть подробности).

banner.png

Рисунок 1 — Случайно выбранный тест из собранного дампа

Ну а теперь к случившемуся...

Пролог

О существовании портала я знаю не первый год. Войдя в состав компании Mail.ru Group, GeekBrains стали вести достаточно агрессивную рекламную кампанию — баннеры, предлагающие пройти тот или иной курс, красуются на многих сайтах крупных агрегаторов IT-новостей, в социальных сетях, а также летают в почтовых рассылках (которые даже Яндекс.Почта уже зачастую трактует как спам). О качестве предлагаемых услуг говорить не буду, ибо самому никогда не доводилось присутствовать на вебинарах / проходить курсы виновника этого повествования, однако, судя по отзывам в сети с первых ссылок поисковика, в общем и целом народ не очень доволен организацией обучения.

Но мы немного отвлеклись, вернемся к теме. Когда я был молод и глуп (ох уж эти полгода назад), начитавшись о том, с каким благоговением на западе смотрят на обладателей сертификатов типа CCNA, CEH, OSCP и иже с ними, я думал, что любая бумажка (особенно на которой стои́т печать и роспись гендира компании с говорящем именем) может сыграть решающую роль при устройстве на работу. К счастью, мое наваждение длилось недолго, и очень скоро мне открылась истина: если сможешь «здесь и сейчас» продемонстрировать свои умения, мудрый собеседующий не взглянет даже на наличие у тебя диплома об окончании вуза. Гиперболизирую, конечно, но в целом это так. И это правильный подход.

Но на GeekBrains я все же время от времени заходил — просто когда было скучно и хотелось что-то порешать, и... очень часто уходил оттуда без призового сертификата, хотя считаю себя неплохим специалистом в тех областях, которые я выбирал на тестированиях. И я постараюсь объяснить, почему так происходило.

Часть I. Бесплатные сертификаты

Всем хорошо известно то место, где живет бесплатный сыр. Зачем, казалось бы, крупному образовательному порталу предоставлять возможность любому желающему (завершившему регистрацию на сайте) пройти тест и получить «задокументированное» подтверждение владения указанной проф. областью от своего имени?

Ответ, в свою очередь, лежит на поверхности, равно как и весь оригинальный (нет) бизнес-план этой организации (все, написанное ниже, является моим личным оценочным суждением, если что):
  1. Тесты на портале я разделяю на две категории, и первая — это «льстящие тесты». Тесты этого типа выполняют роль «ярморочных зазывал», с ними может справиться практически любой желающий, хоть как-то разбирающийся в выбранной тематике тестирования. Их задача (тестов) — ублажить эго всяк сюда входящего, «подкормить» его на портале и подвести к попытке прохождения теста из категории № 2 — «дразнящих тестов». Их же цель, как вы уже могли догадаться, «завалить» испытуемого неадекватными вопросами (на которые, порой, в прямом смысле нет правильного ответа в базе, о чем ниже) и показать в конце ободряющую новость, гласящую, что мол «не все потеряно, дружище, ты все еще респектабельный гражданин, только вот подучиться надо, и, разумеется, выгоднее и практичнее всего это сделать у нас» (вольный пересказ автора).
  2. Даже если человеку удается успешно завершить труднопроходимый тест, с портала взятки гладки, т. к. сами они прекрасно понимают, что ценность этой (электрической) бумажки равняется нулю, и кроме потенциального клиента в лице прошедшего данный тест человека они ничего не потеряют.
Так вот, несколько раз завалившись на тестах категории 2, я обозлился на весь мир в целом и на GeekBrains в частности и начал строить свои козни.

Часть II. Уязвимости в логике работы портала

«Я хотел велосипед и молился, чтобы он был. И Господь в мудрости своей решил его мне не давать. И я украл. И, конечно, молил о прощении. Это было началом, я не смог остановиться...»

В какой-то момент раздел «Тестирования» на сайте GeekBrains разросся до таких масштабов, что там появились тесты на знание английского, SMM-стратегий и скилов «фотошопирования». На момент написания статьи на портале доступны для прохождения 82 теста (один совсем новый — «Криптография») из 32-х областей, все области под спойлером ниже:
"Основы программирования",
"PHP",
"Unity 3D",
"Информационная безопасность",
"Django",
"HTML&CSS",
"JavaScript",
"Python",
"Java",
"C#",
"Photoshop",
"Веб-дизайн",
"C++",
"Android",
"Objective C",
"Linux",
"Ruby on Rails",
"Tarantool",
"Swift",
"Английский язык",
"Базы данных",
"Тестирование ПО",
"Алгоритмы и структуры данных",
"ASP.Net Core MVC",
"SMM",
"Spring Framework",
"C",
"myTarget",
"Компьютерные сети",
"Операционные системы",
"Разработка игр",
"Ruby".

Путешествуя по очередному тесту, я обратил внимание на очень забавную, как мне показалось, ситуацию: на скриншоте ниже (рис. 2) правильный ответ выделен в самом вопросе. Всему виной подсветка синтаксиса.

php-comments.png

Рисунок 2 — Вопрос из теста на базовое владение PHP

После такого ляпа для меня почему-то стало очевидным, что в логике работы системы тестирования будут вектора реализации автоматизированного сбора ответов на тесты из базы данных GeekBrains. Так оно и оказалось.

Первое: в структуре DOM динамически генерируемой странички с текущим вопросом по сути есть готовое API для построения бота, который будет нажимать за тебя на кнопки в виртуальном браузере (см. рис. 3).

dom-api-1.png

Рисунок 3 — Признаки, по которым можно создать однозначный маппинг "{вопрос: ответы}"

Второе: в конце проваленного теста в красивом отчете тебе будет рассказано, в каких местах ты сфейлился, что дает возможность атаковать тест простым перебором (см. рис. 4).

dom-api-2.png

Рисунок 4 — Финальный отчет после провала теста

Третье: после провала любого из тестов тебе дается тайм-аут, в течении которого ты не можешь заново пробовать пройти этот тест (для каких-то тестов это день, для каких-то — три). Разумно. Только вот для меня стало большим удивлением, что GeekBrains позволяет регистрировать новые аккаунты с одноразовых (или «десятиминутных») почт. Так как в этом случае процесс регистрации нового аккаунта так же легко поддается автоматизации, ценность таких «тайм-аутов» обращается в н0ль.

После комбинации трех вышеописанные слабостей образовательной платформы на свет родился многопоточный Python-бот, который, используя мощности инструмента Selenium для автоматизации действий веб-браузера, за непродолжительное время собрал в красивые json'ки около 70 % базы данных тестов GeekBrains.

Часть III. Технические подробности

Чтобы не утомлять читателя большим количеством подробностей, остановлюсь только на самых интересных моментах, с которыми пришлось столкнуться при реализации бота.

1. Проксирование соединения

Для проксирования трафика использовались анонимные SOCKS-прокси сервера. Во время написания кода в PyPI лежала не самая свежая версия Selenium'а, в которой не был исправлен баг с настройкой SOCKS-соединения, заключающийся в невозможности указания версии SOCKS-протокола.

В идеале конфигурация Лисички из-под Селениума с настроенной проксёй должна выглядеть так:

Python:
from selenium import webdriver
from selenium.webdriver.common.proxy import Proxy, ProxyType

proxy = Proxy()
proxy.proxy_type = ProxyType.MANUAL
proxy.socks_proxy = '127.0.0.1:31337'
proxy.socks_version = 5

capabilities = webdriver.DesiredCapabilities.FIREFOX
proxy.add_to_capabilities(capabilities)

driver = webdriver.Firefox(capabilities=capabilities)

Где 127.0.0.1:31337 — айпи и порт используемого прокси-сервера. Однако корректный доступ к полю socks_version класса Proxy() завезли только с этим коммитом, поэтому пришлось править исходники либы вручную. Более детально с проблемой можно ознакомиться в соответствующем ишью.

2. Перебор вариантов ответов

Бóльшая часть вопросов имеет тип radio — случай, когда правильный ответ может быть только один (пример на рис. 2). Здесь все очевидно: в худшем случае, сколько вариантов ответа — столько раз и придется «пробежать» тест, чтобы гарантированно определить верный.

Интереснее вариант с вопросами типа checkbox, когда правильных ответов может быть несколько (и не сказано, сколько конкретно, см. рис. 5).

checkbox-ex.png

Рисунок 5 — Вопрос, в котором возможен не один верный вариант ответа (из того же теста на базовое владение PHP)

В этом случае вместе с самим текстом ответа я хранил два дополнительных значения: число k, которое определяло, какой вариант сочетания (из общего количества возможных ответов по k) в данный момент проверяется, и значение индекса массива, определяющее конкретную комбинацию из этого числа сочетаний, на проверке которой я остановился.

Если постановить, что идентификаторы ответов всегда отсортированы в одном порядке, то питоновский itertools.combinations всегда даст один и тот же генератор сочетаний, который можно благополучно юзать для тестирования очередной комбинации ответов на соответствие Истине.

На рис. 6 представлен пример checkbox-вопроса, ответами на который стали варианты с id 6 и 7 — это первые два элемента (о чем говорит значение combination_id) из сгенерированного списка сочетаний из четырех по два (о чем говорит значение combination_k).

checkbox-struct.png

Рисунок 6 — Пример хранения вопроса типа checkbox

Здесь, рассматривая самый неблагоприятный исход, когда нужная нам комбинация окажется последней (т. е. правильными окажутся все ответы), число «прогонов» такого вопроса будет равна сумме сочетаний из n по k без повторений при k, принадлежащему [1, n]:

checkbox-calc.png


Например, для вопроса с рис. 6 S будет равно:

checkbox-calc-ex.png


Из-за того, что некоторые тесты практически полностью состоят из вопросов второго типа, и не получилось собрать 100 % базы, потому что уже просто надоело мучить компьютер.

3. А был ли мальчик правильный ответ?

Отлаживая новорожденного бота, аки Франкенштейн свое чудовище, я заметил ряд странностей при просмотре получавшихся json'ок: некоторые вопросы для ключа "status" имели значение "False" для всех вариантов ответов. Подумав, что сбоит где-то мой скрипт, я перепроверил код, вручную поставил "True" для нужных ответов и повторил тестирования — нет, сайт уверенно продолжал настаивать на том, что ошибся я. Т. е. существует несколько вопросов (тривиальных, к слову), на которые даже GeekBrains не знает ответов! Ну или же это просто косяк их системы проверки тестов. Tertium non datur.

Вот, кстати, некоторые из этих вопросов:
/tests/1: data-question-id=39 (Что выведет приведённый ниже код...)
/tests/6: data-question-id=514 (Класс std::map реализует...)
/tests/7: data-question-id=660 (Для реализации отправления уведомления о состоянии объекта необходимо...)
/tests/22: data-question-id=1607 (Что произойдет при вызове метода method...)
/tests/28: data-question-id=2573 (Как подключатся к другим тарантулам из тарантула...)
/tests/37: data-question-id=4422 (Выберите правильный вариант ответа для следующего выражения...)
/tests/39: data-question-id=4655 (Является ли язык программирования Java кроссплатформенным...)
/tests/39: data-question-id=4711 (Какой класс является суперклассом для всех классов...)
/tests/39: data-question-id=4723 (Укажите способ создания именованного экземпляра класса...)
/tests/40: data-question-id=4806 (Укажите все ошибки в указанном коде...)
/tests/66: data-question-id=6357 (Какая аннотация является аналогом XML-атрибута init-method...)
/tests/72: data-question-id=6824 (Может ли IPv4-адрес хоста содержать 255...)
/tests/72: data-question-id=6841 (Симметричное шифрование -...)
/tests/108: data-question-id=7185 (Укажите какое значение возвращает следующее выражение...)
/tests/109: data-question-id=7224 (Какие интерфейсы необходимы для реализации UserStore...)
/tests/167: data-question-id=9391 (Какую основную функцию выполняет программа objdump...)
/tests/169: data-question-id=9648 (Какое значение для свойства align-content масштабирует элементы, чтобы сетка могла заполнить всю высоту контейнера...)

4. Изображение в качестве ответа

Некоторое тесты (например, тест 13 — «Веб-дизайн. Начальный уровень») содержат изображения в качестве вариантов ответов. В этом случае при необходимости (если без них не получалось получить сертификат) ответы в базу заносились вручную.

Post Scriptum

На этот аккаунт через некоторое время после его создания на GeekBrains прилетали такого рода сообщения:

message.png

Рисунок 7 — Письмо от поклонника

Мне совестно, что я не отвечал людям, поэтому отвечу, пожалуй, под завершение этой статьи на все вопросы разом по порядку:
  1. У меня ушло примерно 0,01923 лет (одна неделя).
  2. Так и есть.
  3. Спасибо, ты тоже классный.
  4. Почти одновременно. Многопоточно. Если понимаешь, о чем я. Kappa.
  5. Потому что сдал ее («криптографию») ручками в конце всей этой истории.
Спасибо за внимание!
 
Можно было бы получить денежку на BB, за такой баг.
 
Можно было бы получить денежку на BB, за такой баг.
Гипотетически, наверное, да, но:
  1. во-первых, у мейловцев нет гикбрейнса в скоупе багбаунти;
  2. во-вторых, как я написал, даже несмотря на первый пункт им в поддержку был отправлен тикет на эту тему, они отписались о получении, но бэкконнекта я от них так и не дождался, поэтому се ля ви.
 
Гипотетически, наверное, да, но:
  1. во-первых, у мейловцев нет гикбрейнса в скоупе багбаунти;
  2. во-вторых, как я написал, даже несмотря на первый пункт им в поддержку был отправлен тикет на эту тему, они отписались о получении, но бэкконнекта я от них так и не дождался, поэтому се ля ви.
Есть он в скоупе, вчера только получил выплату по багу.

Screenshot_2020-01-17-21-36-23-445_com.android.chrome.jpg
 
Есть он в скоупе
Не знал. Да, сейчас есть, но на момент обнаружения и написания в поддержку этого домена там точно не было (проверял специально). Плюс, как по мне, этот кейс — типичный пример business-logic-бага, который "accepted without bounty".

1.png
 
Не знал. Да, сейчас есть, но на момент обнаружения и написания в поддержку этого домена там точно не было (проверял специально). Плюс, как по мне, этот кейс — типичный пример business-logic-бага, который "accepted without bounty".

Посмотреть вложение 37090
Он в скоупе уже почти год.
А вот и подобный баг, июльский, и тогда баунти были маленькие:

А за этот, модно было получить от 500 - 1000. Зависит уже, как команда оценит security impact.
 
Он в скоупе уже почти год.
А вот и подобный баг, июльский, и тогда баунти были маленькие:

А за этот, модно было получить от 500 - 1000. Зависит уже, как команда оценит security impact.
Хм, не буду спорить, почему-то был уверен, что на тот момент его не было. Но что опубликовано, то опубликовано.
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!