TL;DR: Реальный кейс разработчика, который изобрел свою “авторизацию” с клиентской валидацией, хранением хэшей в localStorage и открытым доступом к админке через /a. Итог: $40k убытков и компрометация данных пользователей.
Введение
Безопасность веб-приложений — это не просто best practice, а обязательный минимум для любого разработчика. Однако, как показывает практика, даже опытные инженеры могут допускать катастрофические ошибки. Сегодня мы разберем реальный кейс, который наглядно демонстрирует, как НЕ нужно делать авторизацию и работать с пользовательскими данными.
Основная часть
1. Клиентская валидация как основной механизм безопасности
Один из самых вопиющих моментов — полное отсутствие серверной валидации. Вместо этого разработчик полагался исключительно на клиентскую логику. Например, успешный вход в систему определялся только по ответу сервера (201 статус), после чего пользователь перенаправлялся на админ-панель.
// Пример катастрофической реализации
fetch('/login', {
method: 'POST',
body: JSON.stringify({ username, password })
})
.then(response => {
if (response.status === 201) {
window.location.href = '/a'; // Админка открыта для всех
}
});
Проблема: любой, кто знает URL админки (/a), мог получить доступ к защищенным ресурсам без авторизации.
2. Хранение чувствительных данных в localStorage
Логин пользователя и его данные сохранялись в localStorage, что само по себе является плохой практикой. Но хуже того — разработчик возвращал хэш пароля в ответе на успешный логин.
// Пример ужасной реализации
localStorage.setItem('user', JSON.stringify({
id: 1,
email: 'user@example.com',
passwordHash: 'sha512hash' // Хэш пароля в localStorage
}));
Проблема: хэш пароля стал фактически паролем, так как сервер сравнивал именно его при авторизации.
3. Открытые эндпоинты и отсутствие безопасности
Эндпоинт /users возвращал список всех пользователей, включая их хэши паролей. Это не только нарушение конфиденциальности, но и прямая угроза безопасности.
# Пример запроса к открытому эндпоинту
GET /users
Проблема: любой мог получить доступ к данным всех пользователей.
4. Хэширование паролей на клиенте
Пароли хэшировались на клиенте, а затем сравнивались с хэшем на сервере. Это полностью нивелирует смысл хэширования, так как хэш становится фактическим паролем.
// Пример некорректного хэширования
const hashedPassword = sha512(password);
fetch('/login', {
method: 'POST',
body: JSON.stringify({ username, hashedPassword })
});
Проблема: если злоумышленник получит хэш, он может использовать его для входа в систему.
Практическое применение
Как правильно делать авторизацию
- Серверная валидация: Все проверки должны происходить на сервере. Клиентская валидация — это только UX улучшение.
- HTTPS: Все данные должны передаваться по защищенному соединению.
- JWT или сессии: Используйте токены или сессии для управления состоянием авторизации.
- Хэширование на сервере: Пароли должны хэшироваться только на сервере с использованием современных алгоритмов (например, bcrypt).
- Закрытые эндпоинты: Ограничивайте доступ к чувствительным данным с помощью middleware и ролей.
Пример правильной реализации:
// Серверная часть
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = await User.findOne({ username });
if (!user || !await bcrypt.compare(password, user.passwordHash)) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = jwt.sign({ userId: user.id }, process.env.SECRET_KEY);
res.json({ token });
});
Заключение
Этот кейс — яркий пример того, как НЕ нужно подходить к разработке, особенно когда речь идет о безопасности. Даже если вы начинающий разработчик, всегда изучайте лучшие практики и не изобретайте велосипеды. Помните, что ошибки в безопасности могут привести к серьезным последствиям, включая финансовые потери и утрату доверия клиентов. Будьте профессионалами, и ваши проекты будут надежными и безопасными.
Источник: https://www.reddit.com/r/webdev/comments/1qrejoy/most_dumbest_thing_a_web_dev_has_ever_done/