Обходим аутентификацию в FreshRSS с «полувалидными» паролями
#FreshRSS #AppSec #bcrypt #AuthBypass
Криптографический алгоритм, используемый в механизме аутентификации FreshRSS, который должен был защищать пароли, в итоге привел к возможности обхода аутентификации. Проблема кроется в особенностях работы bcrypt: при хэшировании он учитывает только первые 72 байта входной строки, игнорируя все последующие символы. Если атакующий знает или угадывает первые 72 байта настоящего пароля пользователя, он может сформировать другой пароль, начинающийся с той же последовательности, но содержащий любые дополнительные символы в конце, и пройти аутентификацию.
Баг затронул только ветку edge и так и не попал в прод, но случай все равно показательный.
♾️Принцип аутентификации♾️
FreshRSS вместо отправки пароля пользователя на сервер в открытом виде использует challenge-response схему, в которой bcrypt применяется на стороне клиента. Клиент сначала запрашивает у сервера nonce (случайное одноразовое значение) и salt1 — первые 29 символов сохраненного bcrypt-хэша пользователя (версия алгоритма, cost-factor и соль).
После этого клиент вычисляет:
// bcrypt hash of password (60 chars)
s = bcrypt.hashSync(password, salt1);
// bcrypt hash of nonce + s
c = bcrypt.hashSync(nonce + s, randomSalt);
Затем отправляет значение c (challenge) на сервер, который проверяет его следующим образом:
password_verify($nonce . $hash, $challenge);
То есть сервер повторно вычисляет bcrypt-хэш для строки nonce + hash и проверяет, совпадает ли он с полученным challenge.
Использование nonce защищает схему от replay-атак. Даже если атакующий перехватит hash и challenge, повторно использовать их нельзя.
♾️Как появилась проблема♾️
В октябре 2025 года в FreshRSS внесли изменения, направленные на усиление криптографической схемы аутентификации. В значение nonce вместо шестнадцатеричного SHA-1 (40 символов) начали использовать шестнадцатеричный SHA-256 (64 символа). Поскольку bcrypt учитывает только первые 72 байта входной строки, при вычислении bcrypt(nonce + s) длина nonce начинает влиять на то, какая часть s (то есть bcrypt-хэша пароля) реально участвует в вычислении.
При коротком nonce часть строки, зависящей от пароля, могла обрезаться из-за ограничения bcrypt, что приводило к тому, что разные значения s давали одинаковый результат при вычислении bcrypt(nonce + s). Это позволяло сформировать альтернативный пароль, который проходил проверку аутентификации, хотя не совпадал с исходным.
После изменения длины nonce поведение bcrypt изменилось: значимая часть s, зависящая от пароля, начала полностью помещаться в 72-байтовое окно, поэтому truncation перестала влиять на результат.
🔗 https://pentesterlab.com/blog/freshrss-bcrypt-truncation-auth-bypass
🌚 | 📲 https://max.ru/poxek |🌚 https://blog.poxek.cc/ | 📺
https://www.youtube.com/@poxek_official/videos | 📺 https://rutube.ru/channel/38946414/ | 📺
https://vk.com/video/@poxek_official