Предистория:
Решал я таски на Root-Me и попалась таска XMPP - authentication. Основная цель таски состояла в том, чтобы по захвату пакетов вытащить пароль, который использовался при аунтефикации и я начал искать документацию к тому, как работает аунтефикация клиента.

Наткнулся на xmpp wiki и там же нашел документацию на аунтефикацию клиента SASL_Authentication_and_SCRAM. Из нее я выяснил то, как происходит распознавания того, что клиент знает правильный пароль и что просто так из пакетов пароль я не вытащу. И тут я заинтересовался тем, как вообще происходит подтверждение правильного пароля. Именно это и побудило меня написать эту статью.
Повествование о том, что такое этот ваш XMPP
XMPP(ранее jabber) - протокол обмена сообщениями с открытым исходным кодом, публикуемый под Apache License 2.0, основан на XML. Позволяет мгновенно передавать сообщения и информацию о присутствии, также с помощью расширений позволяет передавать файлы(XEP-0363 HTTP File Upload; XEP-0096 SI File Transfer; XEP-0065 SOCKS5 Bytestreams; XEP-0166: Jingle и т.д.), создавать комнаты(XEP-0045: Multi-User Chat), осуществлять голосовые и видео звонки(XEP-0166: Jingle), хранить произвольные данные на сервере(XEP-0049 Private XML Storage) и т.д.

А теперь о том, что я хотел рассказать в этой статье
У XMPP есть 2 способа аунтефикации клиента SASL и OAuth 2.0. Начнем по порядку - с механизмов аунтефикации SASL, используемые в XMPP.
SASL (Simple Authentication and Security Layer) — унифицированный фреймворк для реализации механизмов аутентификации и опционального шифрования при передаче данных по сети.
SASL:
Механизмы SASL, которые используются в XMPP:
External
SCRAM (Salted Challenge Response Authentication Mechanism)
PLAIN
DIGEST-MD5
(расположены в порядке убывания безопасности)
А теперь поподробней про каждый механизм:
External - Этот механизм позволяет клиенту аутентифицироваться с помощью внешних средств. Он позволяет входить в систему без пароля, подтверждая свою личность с помощью уникального цифрового сертификата
SCRAM(На нем я и заострю свое внимание) - Наиболее распространенный и обязательный для современного ПО XMPP механизм. Обеспечивает надежную защиту пароля. Что меня зацепило именно в этом механизме, это то, что клиент не передает пароль в открытом виде, а подтверждает свое знание пароля.
Как оно работает?
Клиент отправляет имя пользователя, под которым он хочет аунтефицироваться
Сервер отправляет обратно соль для этого пользователя и количество итераций
Клиент хэширует пароль с заданной солью для заданного количества итераций
Клиент отправляет результат обратно
Сервер хэширует полученное значение и отправляет клиенту, дабы клиент мог удостовериться в том, что у сервера был пароль/хэш пароля
Соль — случайное значение, добавляемое к исходному паролю перед хешированием. Обеспечивает уникальность хеша для одинаковых паролей и защищает от атак по предвычисленным таблицам.
Итерации — число повторных применений хеш‑функции к результату предыдущего вычисления. Увеличивает вычислительную сложность подбора пароля, замедляя атаки грубой силы.
Какие вариации SCRAM использует XMPP?
SCRAM-SHA-256-PLUS
SCRAM-SHA-1-PLUS
SCRAM-SHA-256
SCRAM-SHA-1
(расположены в порядке убывания безопасности, также есть SCRAM-SHA512(-PLUS) и SCRAM-SHA3-512(-PLUS), но на официальной вики и документе, на который она ссылается, нет информации о том, насколько они безопасны)
А теперь о том, как оно работает под капотом
Сначала нормализуется пароль(используя SASLprep)
Берется случайная строка(для примера будет 32 байта в Hex кодировке). Это будет clientNonce
Клиент отправляет первоначальное сообщение(initialMessage)
"n=" .. username .. ",r=" .. clientNonce
Клиент добавляет GS2 заголовок к initialMessage и кодирует полученное в base64 и отправляет это в качестве своего первого сообщения
biwsbj1yb21lbyxyPTZkNDQyYjVkOWU1MWE3NDBmMzY5ZTNkY2VjZjMxNzhl
Сервер отвечает вызовом. Данные закодированы в base64
cj02ZDQ0MmI1ZDllNTFhNzQwZjM2OWUzZGNlY2YzMTc4ZWMxMmIzOTg1YmJkNGE4ZTZmODE0YjQyMmFiNzY2NTczLHM9UVNYQ1IrUTZzZWs4YmY5MixpPTQwOTY=
Клиент декодирует это
r=6d442b5d9e51a740f369e3dcecf3178ec12b3985bbd4a8e6f814b422ab766573,s=QSXCR+Q6sek8bf92,i=4096
Клиент парсит отсюда:
r = Это serverNonce, клиент должен убедиться в том, что он начинается с clientNonce, который он отправил в своем первом сообщении
s = Это соль, закодированная в base64
i = Это количество итераций
Клиент вычисляет:
clientFinalMessageBare = "c=biws,r=" .. serverNonce
saltedPassword = PBKDF2-SHA-1(normalizedPassword, salt, i)
clientKey = HMAC-SHA-1(saltedPassword, "Client Key")
storedKey = SHA-1(clientKey)
authMessage = initialMessage .. "," .. serverFirstMessage .. "," .. clientFinalMessageBare
clientSignature = HMAC-SHA-1(storedKey, authMessage)
clientProof = clientKey XOR clientSignature
serverKey = HMAC-SHA-1(saltedPassword, "Server Key")
serverSignature = HMAC-SHA-1(serverKey, authMessage)
clientFinalMessage = clientFinalMessageBare .. ",p=" .. base64(clientProof)
Клиент кодирует в base64
clientFinalMessage
и отправляет это как ответ
Yz1iaXdzLHI9NmQ0NDJiNWQ5ZTUxYTc0MGYzNjllM2RjZWNmMzE3OGVjMTJiMzk4NWJiZDRhOGU2ZjgxNGI0MjJhYjc2NjU3MyxwPXlxbTcyWWxmc2hFTmpQUjFYeGFucG5IUVA4bz0=
Если все прошло успешно, то вы получите от сервера ответ ``:
dj1wTk5ERlZFUXh1WHhDb1NFaVc4R0VaKzFSU289
Если декодировать из base64 это сообщение, то получим:
v=pNNDFVEQxuXxCoSEiW8GEZ+1RSo=
11. Клиент должен убедиться, что значение v - это serverSignature закодированный в base64.
И вот и все, дальше можно добавить привязку канала(channel binding), для улучшения защиты(предотвращения проведения MitM атак).

GS2‑заголовок — короткий префикс, добавляемый клиентом к своему первому сообщению в процессе аутентификации. Является частью фреймворка GS2, обеспечивающего совместимость между механизмами SASL (Simple Authentication and Security Layer) и GSS‑API (Generic Security Service Application Program Interface). Может содержать флаги gs2-cb-flag
и gs2-authzid
.
PLAIN - наиболее простой механизм аунтефикации, все данные для аунтефикации передаются закодированными в base64, по факту в открытом виде.
[authzid]\x00[username]\x00[password]
и при отправке выглядит примерно так
AGhhYnJfcmVhZGVyAGhlbGxvX3dvcmxk
Любому более или менее подготовленному человеку станет очевидно, что это небезопасный механизм аунтефикации, ведь пароль передается в открытом виде, для этого его используют в связке PLAIN + TLS
, так задачи безопасного хранения пароля делегируются серверу, а защита передачи делегируется современному способу шифрования.

DIGEST-MD5 - самые небезопасный механизм из списка, считается давно устаревшим и не рекомендуется к использованию. Данные для аунтефикации передаются уже в закрытом виде, но камнем преткновения стало то, что для использования этого механизма требуется хранить пароль клиента в открытом виде, либо хранить результат первого хеширования MD5(user:realm:password)
.
И вот небольшое пояснение для тех, кто еще не вкатился, почему PLAIN безопасней DIGEST-MD5, если проблему PLAIN с передачей в открытом виде можно решить внедрением TLS, то проблему DIGEST-MD5 с небезопасным хранением пароля на сервере никак не решить, кроме как сменой механизма аунтефикации. Но можно возразить, что PLAIN тоже хранит пароль в открытом виде, но в отличие от DIGEST-MD5, механизм PLAIN не навязывает серверу способ хранения паролей. Сервер получает пароль и волен хранить его с использованием современных стойких хеш-функций, таких, как bcrypt или Argon2, что является стандартом безопасности
OAuth 2.0
Шагнем назад, OAuth 2.0 - это протокол авторизации, а не аунтефикации. Его задача - позволить одному приложению получить ограниченный доступ к данным пользователя в другом сервисе, не спрашивая у пользователя его пароль от этого сервиса.
Как оно реализовано в XMPP?
Чтобы встроить этот веб-ориентированный подход в XMPP, был создан специальный механизм SASL — OAUTHBEARER. Его логика кардинально отличается от PLAIN или SCRAM.
Этап 1:
Пользователь в нашем приложении нажимает кнопку "Войти через Google".
Приложение перенаправляет его на страницу входа Google в браузере.
Пользователь вводит свой логин/пароль от Google, проходит 2FA (если настроено).
Google показывает экран согласия: "Приложение 'XXXXX' запрашивает доступ к вашему имени и адресу электронной почты. Разрешить?"
Пользователь нажимает "Разрешить".
Google перенаправляет пользователя обратно в наше приложение, прикрепив к редиректу authorization_code.
Наше приложение (его бэкенд) обменивает этот код на access_token и refresh_token, сделав прямой запрос к API Google.
Этап 2:
Клиент выбирает OAUTHBEARER и отправляет auth-станзу. Внутри, в base64, он передает
access_token
.n,
a=user@example.com
,^Aauth=Bearer a_very_long_token^A^A
<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl'
mechanism='OAUTHBEARER'>
bixhPWhhYnJfcmVhZGVyQGhhYnIuY29tLAFhdXRoPUJlYXJlciB0aGVyZSBzaG91bGQgaGF2ZSBiZWVuIHRva2VuLCBidXQgaXQgd291bGQgbm90IGJlOiBEAQA=
</auth>
Сервер получает auth-станзу, декодирует строку и извлекает access_token.
Сервер делает запрос к API Google и прикрепляет токен клиента
Сервер получает ответ от API и понимает, что этот токен валиден и был выпущен для нужного пользователя
Сервер отправляет клиенту <success/>
С аунтефикацией пользователя покончено, в принципе и статью можно на этом заканчивать, ведь основной идеей было разобрать механизмы аунтефикации SASL, а XMPP выступал в роли подопытного кролика.
Итог
Что в итоге? Провели ресерч, выяснили, что устаревшие технологии использовать небезопасно, что технологии, который на первый взгляд небезопасны(PLAIN) можно усовершенствовать и сделать их безопасными. А можно взять лучшее из лучших и использовать лучшие способы защиты пароля. Вот так начиная с CTF-задачки, перешли к современной криптографии и механизмам защиты. Если эта статья кому-то помогла лучше понять механизмы аунтефикации SASL или то, как оно реализовано в XMPP, буду безмерно счастлив, ведь это моя первая статья :D
P.S. Если есть какие-то предложения по улучшению этой статьи или будущих статей, пишите, на все постараюсь ответить