Разбор двух реальных багов в Safekom, zero-knowledge менеджере паролей: email enumeration и ошибка конвертации RSA-ключей DER в PEM.
Как я искал баги в собственном менеджере паролей
Safekom я делал не для того, чтобы кого-то удивить, а чтобы иметь свою площадку, где можно спокойно проверять идеи по безопасности без риска сломать чужой продакшен. Это zero-knowledge менеджер паролей, то есть шифрование и расшифровка данных происходят только в браузере пользователя, а сервер хранит исключительно зашифрованный набор байт, который без ключа клиента бесполезен в принципе, даже для меня как владельца сервера.
Звучит это солидно на бумаге, AES-256-GCM, Argon2id для вывода ключа из пароля, сервер ничего не знает. Но написать такую систему и написать систему, которая действительно не протекает ни в одной мелочи, это разные уровни сложности. Я сам нашёл в собственном проекте два бага, и оба интересны именно тем, насколько легко такое пропустить, если не искать специально.
Первый был классикой жанра, email enumeration. Форма восстановления доступа принимала email и в зависимости от того, существует такой аккаунт или нет, отвечала чуть по-разному, разница была в доле секунды и в формулировке сообщения об ошибке. Для обычного пользователя незаметно, для скрипта, который перебирает список email и смотрит на точное время ответа или текст, это готовый способ узнать, кто пользуется сервисом, даже не имея доступа ни к одному паролю. Сама по себе эта утечка не компрометирует зашифрованные данные, но компрометирует приватность самого факта использования сервиса, а в контексте менеджера паролей это тоже чувствительная информация.
Второй баг спрятался глубже, в конвертации RSA-ключей из формата DER в формат PEM, это рутинная операция при работе с криптографией, которую обычно делают готовой библиотекой и не глядя. У меня в одном из редких edge-кейсов терялись ведущие нули в числовом представлении ключа при сериализации, что в теории могло привести к повреждению ключа в определённых пограничных значениях. На практике вероятность столкнуться с этим конкретным набором байт крайне мала, но «крайне мала» не значит «невозможна», а в криптографии разница между этими формулировками может стоить реальных данных пользователя.
Оба бага я нашёл не потому что кто-то их указал, а потому что относился к собственному проекту так же, как отношусь к чужому коду на платном аудите: читал внимательно, проверял граничные случаи, не доверял библиотеке просто потому что она популярная. Разница только в том, что здесь некому было платить за эту работу кроме собственного интереса не выпускать в мир дырявую криптографию с гордой надписью «zero-knowledge» на главной странице.
На собственном проекте можно позволить себе роскошь переделывать архитектуру заново, если находится более правильный подход, и именно поэтому там удобно показывать, как выглядит security-ревью не в теории, а на практике, со всеми мелкими неприятностями вроде потерянных ведущих нулей в сериализации ключа. Такой же внимательный разбор я провожу и на платном аудите кода, только там находки чужие, а не мои собственные.