Тестирование двухфакторной аутентификации и возможные варианты обхода
Еще до того, как я начал постигать сложную науку информационной безопасности, мне казалось, что 2FA аутентификация — это гарантированный способ защитить свой аккаунт и никакие «эти ваши хакеры» не смогут, скажем, увести мою внутреннюю валюту для покупки одежды персонажам на игровом аккаунте. Но с течением времени опытным путем было доказано, — система двухфакторной аутентификации может иметь большое количество уязвимостей.
Говоря простыми словами, двухфакторная аутентификация — это подтверждение действия с помощью ввода сгенерированного кода для увеличения безопасности и забрасывания палок в колеса условным хакерам во время движения или до его начала.
Система подтверждения с помощью кода очень распостранена, используется повсеместно на различных сайтах и может подключаться как для первичного входа, так и вторичного. Но этим применение не ограничивается, — разработчики прикрепляют подтверждение на функционал восстановления паролей, подтверждения регистрации/подписки, дополнительного подтверждения финансовых операций, смены паролей, изменения личных данных. Также, изредка в качестве стены после logout-а по таймингу используется именно 2FA, а не пароль или другой способ подтверждения.
В данной статье я собрал способы тестирования 2FA на уязвимости, их эксплуатацию, а также возможные варианты обхода существующей защиты от некоторых видов атак. Давайте же рассмотрим список проверок на предмет уязвимостей, которые применяются к 2FA:
1. Отсутствие Rate-лимита
Алгоритм Rate-лимита используется для проверки возможности пользовательского сеанса (или IP-адреса) быть ограниченым в попытках или скорости, и при каких обстоятельствах это происходит. Если пользователь выполнил слишком много запросов в течение определенного промежутка времени, веб приложение может ответить 429 кодом (много запросов) или применить Rate-лимит, не показав при этом ошибок. Отсутствие rate-лимита предполагает, что при обычном переборе нет никаких ограничений на количество попыток и/или скорости, — позволено перебирать коды произвольное количество раз (при любой скорости) в пределах срока действия сессии/токена.
Довольно часто приходится сталкиваться с «бесшумным» rate-limit-ом, — если вы увидели, что ошибок нет и тело/код HTTP не изменяется в последующих запросах,- радоваться рано, и для начала нужно проверить конечный результат атаки, применив валидный код.
2. Rate лимит существует, но его можно обойти
Кейсы, которые раньше приходилось встречать:
1) Ограничение скорости потоков с отсутствием блокировки после достижения определенной скорости
Зачастую исследователи безопасности пытаются подобрать код с использованием 5-и или более количества потоков, чтобы быстрее выполнить атаку (в Burp Intruder количество потоков по умолчанию- 5 без задержки). Но иногда система безопасности от перебора или обычный Load Balancer может реагировать только на этот единственный фактор. Если вы пытаетесь брутфорсить с 5-ю потоками, стоит уменьшить количество до 1-го, а потом до 1-го с задержкой в одну секунду. Ранее мне посчастливилось наблюдать за таким поведением и именно с помощью таких манипуляций произошел успешный подбор кода, что привело к Account Takeover. Если у 2FA кода нет определенного срока действия, то у нас есть много времени на перебор. Если же срок действия присутствует, то успешность атаки уменьшена, но потенциальная опасность уязвимости все равно присутствует, так как шанс попадания в нужный код все же есть.
2) Генерируемый OTP код не изменяется
Это касается не постоянно изменяющихся кодов как в Google Authenticator, а только статичных, которые приходят в SMS, email или личным сообщением в мессенджере.
Суть данного обхода состоит в том, что постоянно или в течении некоторого времени, например, 5 минут, в SMS отправляется один и тот же OTP код, который в течении всего этого времени является валидным. Так же стоить следить за тем, чтобы не произошел silent rate-limit.
Пример репорта: hackerone.com/reports/420163
Допустим, приложение генерирует случайный код от 001 до 999 и присылает его на телефон, в течении 10-ти минут при задействовании функционала «отправить повторно» мы получаем один и тот же код. Но к запросу привязан rate-limit, который ограничивает количество попыток на один токен запроса. Мы можем постоянно запрашивать новый код, генерировать новый токен запроса, применять его к последующему запросу (с помощью grep-match в burp suite или с помощью собственного скрипта) и производить брутфорс диапазона чисел от 001 до 999. Таким образом, постоянно используя новый токен запроса мы успешно подберем правильный код, так как он не изменяется и является статичным в определенный промежуток времени. Ограничения данной атаки — длинное число или смешивание букв с числами в качестве кода подтверждения.
Такую ситуцию обезнадеживать не стоит, следует попытаться перебрать хотя бы часть нашего списка, потому что существует вероятность, что генерируемый код окажется в этой части списка, так как он генерируется случайно. При переборе нужно надеятся на рандом, но все же есть шанс попадания в правильную комбинацию, что доказывает уязвимость, которую точно необходимо исправить.
3) Сброс rate-limit-a при обновления кода.
В запросе проверки кода, rate-лимит присутствует, но после задействования функционала повторной отправки кода он сбрасывается и позволяет продолжать брутфорс кода.
Примеры репортов:
https://hackerone.com/reports/149598, — теория;
hackerone.com/reports/205000, — практический эксплойт основанный на предыдущем репорте.
4) Обход rate-лимита путем смены IP адреса
Множество блокировок основаны на ограничении приема запросов с IP, который достиг порога определенного количества попыток при выполнении запроса. Если IP-адрес сменить, то есть возможность обойти это ограничение. Для того, чтобы проверить данный способ, просто смените свой IP с помощью Proxy-сервера/VPN и увидите, зависит ли блокировка от IP.
Способы смены IP:
- Прокси могут быть использованы в атаке с помощью дополнения IP Rotator для программы Burp Suite github.com/RhinoSecurityLabs/IPRotate_Burp_Extension. На мой взгляд это лучший выбор, потому что он дает нам ~неограниченные попытки перебора и IP-адреса, которые позволяют выполнять brute-force атаку без 42x ошибок и прерываний.
- Неплохим вариантом может быть python скрипт с proxy requests модулем, но для начала нужно где-то раздобыть большое количество валидных прокси.
Так как IP rotate тулза отправляет запросы с помощью AWS IP-адресов, все запросы будут блокироваться, если веб приложение находится за CloudFlare фаерволом.
В данном случае нужно дополнительно обнаружить IP оригинального веб сервера или найти способ, не касающийся AWS IP-адресов.
5) На сайте включена поддержка X-Forwarded-For
Встроенный header X-Forwarded-For может использоваться для смены IP. Если в приложение встроена обработка данного хедера, просто отправьте X-Forwarded-For: desired_IP для подмены IP, чтобы обойти ограничение без использования дополнительных прокси. Каждый раз, когда будет отправлен запрос с X-Forwarded-For, веб-сервер будет думать, что наш IP адрес соответствует значению, переданному через хедер.
Материалы на эту тему:
hackerone.com/reports/225897
medium.com/@arbazhussain/bypassing-rate-limit-protection-by-spoofing-originating-ip-ff06adf34157
3. Обход 2фа с помощью подстановки части запроса из сессии другого аккаунта
Если для верификации кода в запросе отправляется параметр с определенным значением, попробуйте отправить значение с запроса другого аккаунта.
Например, при отправке OTP-кода проверяется ID формы, ID пользователя или cookie, которое связано с отправкой кода. Если применить данные с параметров аккаунта, на котором нужно обойти code-верификацию (Account 1), на сессию совсем другого аккаунта(Account 2), получим код и введем его на втором аккаунте, то сможем обойти защиту на первом аккаунте. После перезагрузки страницы 2FA должна исчезнуть.
4. Обход 2FA с помощью «функционала запомининания»
На многих сайтах, поддерживающих 2FA авторизацию, есть функционал «запомнить меня». Он полезен в том случае, когда пользователь не желает вводить 2FA код при последующих входах в аккаунт. Важно идентифицировать способ, с помощью которого 2FA «запоминается». Это может быть cookie, значение в session/local storage или просто крепление 2FA к IP адресу.
1) Если крепление 2FA происходит с помощью установки cookie, то значение cookie должно быть unguessable
То есть, если cookie состоит из набора цифр, которые возрастают для каждого аккаунта, то к значению cookie вполне можно применить brute-force атаку и обойти 2FA. Разработчикам стоит снабжать куку (вместе с ключевым cookie сессии и CSRF токеном) атрибутом HttpOnly, чтобы ее нельзя было украсть с помощью XSS и применить для обхода 2FA.
2) Если 2FA крепится к IP-адресу, то можно попытаться его подменить
Чтобы идентифицировать данный метод, войдите в свой аккаунт с помощью функции запоминания 2FA, потом перейдите в другой браузер или incognito режим текущего браузера и попробуйте войти снова. Если 2FA не запрашивается вовсе, значит произошло крепление 2FA к IP-адресу.
Для подмены IP адреса можно использовать X-Forwarded-For хедер на этапе ввода логина и пароля, если веб приложение его поддерживает.
С помощью данного хедера также можно обойти функцию «IP address white-list», если такая присутствует в настройках аккаунта. Она может использоваться в связке с 2FA как дополнительная защита аккаунта или 2FA может даже не запрашиваться если IP адрес совпадает с white-list (с согласия пользователя). Таким образом, даже без крепления 2FA к IP адресу, в некоторых случаях 2FA можно обойти с помощью обхода сопутствующих способов защиты.
В целом крепление 2FA к IP адресу не совсем безопасный способ защиты, так как во время присутствия в одной сети, при подключении к одному и тому же VPN/интернет провайдеру со статичным IP адресом, 2FA можно обойти.
Самый безопасный способ защиты — это не запоминать 2FA вовсе в ущерб юзабилити.
5. Improper access control баг на странице ввода 2FA
Иногда страница-диалог для ввода 2FA представлена в виде URL с параметрами. Доступ к такой странице с параметрами в URL с cookies, которые не соответствуют тем, которые использовались при генерации страницы или вообще без cookies, — это не безопасно. Но если разработчики решили принять риски, то нужно пройтись по нескольким важным пунктам:
- истекает ли ссылка для ввода 2FA;
- индексируется ли ссылка в поисковиках.
Если ссылка имеет продолжительный срок существования и/или в поисковиках присутствуют рабочие ссылки для ввода 2FA/ссылки могут быть проиндексированы (нет правил в robots.txt/meta тегах), то существует вероятность применения обхода 2FA механизма на странице ввода 2FA, при котором можно будет полностью обойти ввод логина и пароля, и получить доступ к чужому аккаунту.
6. Недостаточная цензура персональных данных на странице 2FA
При отправке OTP кода на странице используется цензура для защиты персональных данных, таких как email, номер телефона, никнейм, etc. Но эти данные могут полностью раскрываться в API эндпоинтах и других запросах, на которые у нас хватает прав на этапе 2FA. Если изначально эти данные не были известны, например мы вводили только логин без знания номера телефона, то это считается уязвимостью «Information Disclosure». Знание номера телефона/email может использоваться для последующих фишинговых и брутфорс атак.
Пример эксплуатации уязвимости с помощью Credentials Stuffing. Допустим, в публичном доступе есть база данных с логинами и паролями для сайта A. Злоумышленники могут применить данные с этой базы на сайте B:
- Сначала они проверяют, существует ли пользователь в базе данных сайта B с помощью бага «Accounts Enumeration» в регистрации/восстановлении пароля. Обычно, многие сайты не считают это уязвимостью и принимают риски. «Уязвимость» заключается в присутствии ошибки о факте регистрации пользователя на сайте. В идеале безопасное сообщение на странице восстановления пароля выглядит следующим образом:
- После получения базы существующих пользователей, злоумышленники применяют пароли к этим аккаунтам.
- Если они сталкиваются с 2FA, то попадают в тупик. Но в случае недостаточной цензуры данных пользователя, они могут дополнить свою базу его личными данными (если их не было в изначальной базе).
7. Игнорирование 2FA при определенных обстоятельствах
При выполнении некоторых действий, которые приводят к автоматическому входу в аккаунту, 2FA может не запрашиваться.
1) Игнорирование 2FA при восстановлении пароля
Многие сервисы выполняют автоматический вход в аккаунт после завершения процедуры восстановления пароля. Так как доступ к аккаунту предоставляется мгновенно, при входе в аккаунт 2FA может миноваться и полностью игнорироваться.
Impact аналогичного репорта на hackerone, который я прислал недавно:
Если злоумышленник получит доступ к электронной почте жертвы (он может взломать учетную запись с помощью фишинга, brute-force атаки, credentials stuffing и тд), он может обойти 2FA, хотя в этом случае 2FA должен защищать учетную запись. На данный момент для 2FA есть проверка кода Google Authenticator или резервного кода, но не кода из электронного письма, поэтому данный Bypass имеет смысл.
2) Игнорирование 2FA при входе через соцсеть
К аккаунту пользователя можно прикрепить социальную сеть для быстрого входа в аккаунт и одновременно настроить 2FA. При входе в аккаунт через соцсеть, 2FA может игнорироваться. Если email жертвы будет взломан, то можно будет восстановить пароль к аккаунту соцсети (если она позволяет это сделать) и войти на сервис без ввода 2FA.
Impact одного из репортов:
- Связка с другими уязвимостями, такими как ранее присланный OAuth misconfiguration #577468, для полного захвата учетной записи, преодолевая 2FA.
- Если злоумышленник взломал электронную почту пользователя, он может попытаться восстановить доступ к аккаунту социальной сети и войти в учетную запись без дополнительной проверки.
- Если злоумышленник однажды взломал учетную запись жертвы, он может связать социальную сеть с учетной записью и входить в аккаунт в будущем, полностью игнорируя 2FA и ввод логина/пароля.
3) Игнорирование 2FA в более старой версии приложения
Разработчики нередко добавляют staging версии web приложения на домены/поддомены для тестирования определенных функций. Интересно, что если войти под своим логином и паролем, 2FA не будет запрашиваться. Возможно, разработчики используют более старую версию приложения, в которой нет защиты для 2FA, сама 2FA отключена или её намеренно отключили для тестирования.
Также, можно заодно проверить и другие уязвимости, — регистрация нового пользователя на staging сервере, email которого существует в базе production версии может выдать нам личные данные с production; отсутствие rate-лимита в важном функционале, например в восстановлении пароля. Пример последней уязвимости — $15 000 facebook bug, который позволял взломать аккаунт с помощью брутфорса кода для восстановления пароля на beta.facebook.com www.freecodecamp.org/news/responsible-disclosure-how-i-could-have-hacked-all-facebook-accounts-f47c0252ae4d.
4) Игнорирование 2FA в случае кроссплатформенности
Имплементации 2FA в мобильной или десктопной версии могут отличаться от web версии приложения. 2FA может быть слабее, чем в web версии или вовсе отсутствовать.
7. При отключении 2FA не запрашивается текущий код.
Если при отключении 2FA не запрашивается дополнительное подтверждение, такое как текущий код с google authenticator приложения, код с email/телефона, то в таком случае имеются определенные риски. При чистом запросе существует вероятность CSRF атаки. Если будет найден вектор обхода CSRF защиты (если она есть), то 2FA можно будет отключить. Также может использоваться clickjacking уязвимость, — после пары кликов от ничего не подозревающего пользователя 2FA будет отключена. Подтверждение предыдущего кода добавит дополнительную защиту 2FA, учитывая потенциальные CSRF/XSS/Clickjacking атаки, а также CORS misconfigurations.
В качестве примера приведу сайт hackerone.com, — при отключении 2FA в одной форме одновременно требуется ввод двух переменных, - текущего кода с google authenticator приложения и пароля. Это лучшая и рекомендуемая защита.
8. Ранее созданные сессии остаются валидными после активации 2FA
При включении 2FA желательно, чтобы параллельные сессии на том же аккаунте завершались и выводился диалог ввода 2FA, то же самое с изменением пароля. Если аккаунт был скомпрометирован и первой реакцией жертвы будет включение 2FA, то сессия злоумышленника будет инвалидирована и следующий ввод логина и пароля будет требовать 2FA. В целом, это best practise, которой следует придерживаться. Я часто замечаю, как криптовалютные биржи добавляют подобную защиту. Пример репорта на HackerOne, —
https://hackerone.com/reports/534450.
9. Отсутствие Rate-limit-а в личном кабинете
2FA может внедряться в различные функционалы личного кабинета пользователя для большей безопасности. Это может быть изменение email адреса, пароля, подтверждение изменения кода для осуществления финансовых операций, etc. Наличие rate-limit-a в личном кабинете может отличаться от наличия rate-limit-a в 2FA при входе в аккаунт. Я не раз сталкивался с подобными случаями, когда можно было беспрепятственно подбирать 2FA код в личном кабинете, в то время как при входе был установлен «Strict» rate-limit.
Если разработчики изначально добавили защиту против несанкционированного изменения данных, то данную защиту нужно поддерживать и исправлять все возможные bypass-ы. Если bypass найден, то это расценивается как уязвимость обхода «security feature», которая была имплементирована разработчиками.
10. Манипуляция версиями API
Если вы видите в запросе web приложения что-то вроде /v*/, где * — это цифра, то есть вероятность, что можно переключиться на более старую версию API. В старой версии API может быть слабая защита или таковой может вовсе не быть. Это довольно редкое явление и возникает в том случае, если разработчики забыли удалить старую версию API в production/staging среде.
К примеру, /endpoint/api/v4/login выполняет запрос на вход, проверяя логин и пароль. Если 2FA присутствует в аккаунте, за этим запросом должен следовать /endpoint/api/v4/2fa_check, никак иначе. Если мы заменим версию API ещё до 2FA, то, в некоторых случаях, сможем её избежать. /endpoint/api/v3/login может привести к /endpoint/v3/login_successful?code=RANDOM, игнорируя 2fa_check потому, что в данной версии API, 2FA попросту не была имплементирована.
Еще один пример, — в запросе /endpoint/api/v4/2fa_check присутствует rate-limit, в то время как /endpoint/api/v3/2fa_check позволяет перебирать коды без каких-либо ограничений.
11. Improper Access Control в запросе резервных кодов
Резервные коды генерируются сразу после включения 2FA и доступны по единому запросу. После каждого последующего обращения к запросу коды могут генерироваться по новой или оставаться неизменными (статичные коды). Если существуют CORS мисконфигурации/XSS уязвимости и другие баги, позволяющие «вытащить» резервные коды из ответа запроса эндпоинта отображения резервных кодов, тогда злоумышленник может украсть коды и обойти 2FA, если известен логин и пароль.
В общем и целом, 2фа является одним из самых надежных способов защиты аккаунта, если, конечно, разработчики не хранят текущие 2FA коды всех пользователей web приложения в админ панели, — и такое в моей практике бывало. Также хотел бы отметить, что разработчики некоторых компаний создают приложения для генерации собственных 2FA кодов (примеры тому Salesforce, Valve) и ввиду недостаточной защищенности такой упор на независимость от использования других приложений для аутентификации дает злоумышленникам дополнительную точку входа для обхода 2FA. Спектр применения данной защиты достаточно большой и дает желающим обойти 2FA защиту больше возможностей, пространства для креатива и увеличивает количество вариаций 2FA bypass-ов.
Надеюсь, эта информация была полезной для исследователей информационной безопасности, bug bounty хантеров, а также для разработчиков, чтобы минимизировать количество уязвимостей в разрабатываемом сервисе. Stay safe!