Уязвимый пример входа в систему
Следующий кодовый фрагмент имеет тонкую проблему безопасности с ним. Можете ли вы сказать, что не так?
// Returns true if the email/password pair is valid async function isValidCredentials(emailAddress, password) { // Fetch the password hash from the DB by email address const passwordHashOrNull = await fetchPasswordHash(emailAddress); // If there was no match, return false if (!passwordHashOrNull) { return false; } // Bcrypt is "a library to help you hash passwords" // Here we use the compare function to check that the // provided password matches the hashed password in the DB const doesPasswordMatch = await bcrypt.compare(password, passwordHashOrNull); return doesPasswordMatch; } // Fetches the password hash from the DB async function fetchPasswordHash(emailAddress) { // impl not important }
Как подсказка, давайте посмотрим, как долго несколько звонков на IsValidCredentials
берет:
async function timeIsValidCredentials(emailAddress, password) { console.time("Checking " + emailAddress); await isValidCredentials(emailAddress, password); console.timeEnd("Checking " + emailAddress); } await timeIsValidCredentials("test@test.com", "password"); // Checking test@test.com: 63.813ms await timeIsValidCredentials("test@test.com", "password2"); // Checking test@test.com: 62.867ms await timeIsValidCredentials("test2@test.com", "password"); // Checking test2@test.com: 4.017ms await timeIsValidCredentials("test3@test.com", "password"); // Checking test3@test.com: 4.008ms
Есть заметная разница между тем, как долго test@test.com
Пишите электронные письма и test2@test.com
или test3@test.com
Отказ
Оказывается, проблема в этих строках:
// If there was no match, return false if (!passwordHashOrNull) { return false; }
Вернувшись рано, если не было совпадения, злоумышленник может легко сказать, что test@test.com
есть аккаунт, но test2@test.com
и test3@test.com
не делать.
Временные атаки
Это распространенный пример атаки времени. Это класс атак, где промежуток времени, когда ваше приложение требует, чтобы выполнить задачу, утечки некоторой информации.
В случае входа в систему, разница во времена добилась довольно очевидной из даже одного запроса. Если разница была более тонкой, злоумышленник может внести много запросов в течение длительного времени и в среднем их вместе, чтобы различать разные случаи.
Это большое дело?
Это может не казаться большой сделкой, но скажем, я пытаюсь найти чей-то личный адрес электронной почты. У меня только их имя, и я знаю, что они подписались на ваш сайт.
Я могу попробовать кучу вариаций FirstName.lastname@gmail.com
или Фамилия {3-значный номер }@gmail.com
И так далее, пока я не найду действительного.
Кроме того, есть и другие временные атаки, которые протекают еще более конфиденциальную информацию, которую мы увидимся.
Как мы можем исправить это?
Есть несколько стратегий, но самый простой ответ: «Убедитесь, что все кодепаты занимают столько же времени». Вам не нужно делать это везде, просто в чувствительных частях кодовой базы.
Вместо того, чтобы возвращать рано, мы могли бы проверить пароль против некоторых хэш, а затем вернул false:
// If there was no match, waste time and then return false if (!passwordHashOrNull) { await bcrypt.compare(password, RANDOM_PASSWORD_HASH); return false; }
Также полезно добавить ограничение скорости, когда это возможно. Если злоумышленник нуждается в большом количестве запросов, чтобы различать различные случаи, ограничение скорости их может сделать атаку непрактично.
Временные атаки на практике
Недавно умный Атака ГРМ была найдена в пароле Lobste.rs «Сброс пароля Отказ Это использовало тот факт, что базы данных при сравнении двух строк вернутся рано, если строки не совпадают.
Так проверять
"a".repeat(10000) === "b".repeat(10000)
должен занять меньше времени, чем
"a".repeat(10000) === "a".repeat(9999) + "b"
Это означает, что тем больше символов у вас есть правильные, тем дольше примет звонок. Злоумышленник может попробовать разные префиксы и посмотреть, какой из них требуется самое длинное, чтобы медленно определить действительный токен сброса пароля.
Эта же уязвимость существует в любом месте, где кто-то проверяет секретное значение непосредственно против базы данных, поэтому, в то время как она может показаться довольно теоретически, определенно существуют реальные случаи мира, которые были зарегистрированы и исправлены.
Оригинал: “https://dev.to/propelauth/understanding-timing-attacks-with-code-examples-32e6”