OIDC + PKCE в SPA на примере гугл аутентификации, часть 1
Предупреждение!
Многие вещи далее стоит рассматривать как пояснение происходящего и не стоит воспринимать как руководство к действию. Предпочтительнее будет по-возможности использовать готовые библиотеки, которые используются множеством людей и проходят какое никакое ревью широких масс. Как результат любые уязвимости в них будут закрыты. Однако чтоб использовать эти библиотеки я настойчиво рекомендую изучать их исходный код до использования. И то о чем пойдет речь далее может помочь именно в этом вопросе.
Что за зверь PKCE?
Чтоб ответить на этот вопрос давйте для начала ответим на вопрос почему в природе есть authorization_code flow и implicit_flow. В первом у нас есть client_secret, во втором нет.
Ответ довольно прост. Любой секрет хранимый внутри js/html не является секретом. Следовательно client_secret в implicit flow созданном для мобильных/нативных приложений смысла не имеет. В тоже время в authorization_code flow где редирект с кодом авторизации происходит в браузере, а запрос кода происходит на сервер секрет имеет смысл. Так как возможности получить этот самый секрет кроме как взломав сервер нету.
И здесь возникает резонный вопрос. Разве implicit flow безопасен? Ответ: нет. Более того он никогда не был безопасен. Более того даже к PKCE для SPA и мобильных приложений есть масса вопросов и безопасными их называть не охота.
Ок, как можно атаковать implicit flow? Ну самое банальное - перехватывать урл переадресации на клиентском устройстве. Как результат мы получаем к себе в руки сразу токен. Если делать тоже самое с PKCE мы получаем в руки authroization_code. Получение в руки authorization_code не так опасно так как он не позволяет получать доступ к API напрямую.
Но разве это не катастрофа? Факт, того, что кто-то слушает редиректы на нашем устройстве. Пожалуй если кто-то слушает наши редиректы это катастрофа.
Теперь сразу вопрос. В каких случаях мы можем украсть данные при переадресации?
- Если переадресация идет на кастомную схему и мы вынудили операционную систему слушать наш обработчик схемы вместо обработчика приложения. В виндовс схемы для Uri прописываются в пользовательской или системной ветке реестра. В пользовавательскую ветку писать могут все, то есть нашему експлойту не нужны админ привелегии, чтоб перехватить на себя контроль и после запуска приложения и клика на кнопку логин наш юзер отдаст токен атакующему, а не своему приложению.
- Если переадресация идет на локалхост и мы можем запустить приложение слушающее порт переадресации до того, как приложение делающее аутентификацию захватит этот самый порт.
Как от этого может защитить authorization_code ? Никак не может на клиенте. Так как атакующий может получить бинарники нашего приложения, сделать реверс инжиниринг и написать код, который получив код авторизации просто сделает запрос за токеном.
Но мы сейчас говорим тут о клиентских приложениях, локалхост, кастомная схема, с SPA что не так?
- Загрузка SPA в IFrame. Мы же можем в некоторых ситуациях получить то, что передается в строке страницы внутри IFrame.
- History API + XSS
Мы можем запретить как-то такие поведения всякими CSP, политиками куки? Да, можем, а еще лучше если мы полностью запретим походы из JS с нашей странички на внешние сайты и не будем делать аутентификацию внутри SPA, даже с PKCE.
Но может же быть еще случай, когда мы пишем PWA (ненавижу этот “термин”). И тут нам нужно как-то аутентифицировать пользователя внутри SPA. Да и не стоит забывать о мобильных приложениях на самом деле.
Ладно, убедил, рассказывай, что такое PKCE.
PKCE - proof key of code exchange. Простыми словами это просто временный секрет, который генерируется на лету приложением запрашивающим аутентификацию, которым оно ни с кем не делится. Но как можно этот ключ передать тому, кто аутентифицирует через строку браузера и чтоб не украли? Ответ простой: через hash функцию.
Ничего не понятно. Дайте картинок!!!
Выше приведен implicit flow
Давайте теперь сделаем картинку с PKCE для наглядности.
Вторая схема как бы гарантирует факт того, что токен получит тот, кто его запросил. Она не гарантирует того факта, что токен запросил не тот, кто должен запрашивать. Она просто гарантирует факт того, что кто запросил - тот и получил. Не стоит считать, что этот подход супербезопасный и токенам полученным таким путем можно доверять. Так или иначе хотя бы в процессе получения токена вторым путем его куда сложнее перехватить чем с первой схемой.
Сразу отмечу, что полученный токен нам по-прежнему нужно хранить в JS, который может быть атакован через XSS и в последствие XSS может передать токен куда не надо. Как с этим бороться? Не хранить токен в хорошо просматриваемых персистентных стореджах вроде localStorage или куки созданные из JS. Применять CSP (к слову она легко добавляется meta HTML тегами, может быть полезно если у нас приложение хостится статически и нет возможности менять хедеры сервера), в PWA будет работать. Так как PWA рассчитаны на браузеры с поддержкой CSP (не IE) это имеет смысл.
В следующей части мы попробуем написать код примитивный для браузера чтоб совершить получение токена через PKCE.