Код-ревью безопасности: вход и профиль
Вопрос
Перед вами фрагмент API на ASP.NET Core 8: вход, просмотр профиля и загрузка аватара. Найдите проблемы безопасности и надёжности.
builder.Services.AddAuthentication().AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = false,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("secret"))
};
});
builder.Services.AddCors(o => o.AddDefaultPolicy(p => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()));
app.MapPost("/login", async (LoginDto dto, AppDbContext db) =>
{
var user = await db.Users.FirstOrDefaultAsync(u => u.Email == dto.Email && u.Password == dto.Password);
if (user == null) return Results.BadRequest($"Пользователь {dto.Email} не найден или пароль неверный");
return Results.Ok(TokenFor(user));
});
app.MapGet("/users/{id}", async (int id, AppDbContext db) => await db.Users.FindAsync(id));
app.MapPost("/avatar", async (IFormFile file) =>
{
var path = Path.Combine("wwwroot/avatars", file.FileName);
await using var fs = File.Create(path);
await file.CopyToAsync(fs);
}).DisableAntiforgery();
Коротко
Что должен найти ревьюер
- Безопасностьстрока 5Проверка издателя, аудитории и срока жизни токена выключена: подойдёт любой токен с этой подписью, в том числе просроченный.
- Безопасностьстрока 8Ключ подписи захардкожен и слишком короткий. Секрет хранят вне кода (переменные окружения, хранилище секретов), длина ключа для HS256 от 256 бит.
- Безопасностьстрока 11CORS разрешает любой источник: если API когда-нибудь начнёт работать с cookie, это откроет запросы с чужих сайтов. Нужен явный список доменов.
- Безопасностьстрока 15Пароли хранятся и сравниваются в открытом виде. Нужен медленный хеш с солью (
PasswordHasher<T>, Argon2, bcrypt) и сравнение хешей. - Безопасностьстрока 16Сообщение об ошибке раскрывает, существует ли email, и отражает ввод пользователя. Ответ должен быть одинаковым для любого неуспешного входа.
- Безопасностьстрока 13Нет ограничения частоты попыток входа: перебор паролей не остановить. Нужен rate limiter (
AddRateLimiter) и блокировка после неудач. - Безопасностьстрока 20Профиль любого пользователя доступен без авторизации и без проверки владельца (IDOR), а наружу уходит сущность целиком, вместе с хешем пароля. Нужны
RequireAuthorization, проверка прав и DTO. - Безопасностьстрока 24Имя файла берётся от клиента: возможен обход каталога (
../) и перезапись чужих файлов. Генерировать своё имя, проверять тип и размер файла. - Безопасностьстрока 27Загрузка без авторизации и с отключённой антифорджери-защитой: любой может заливать файлы в публичную папку.
Разбор
В коде собраны самые частые ошибки безопасности API. Аутентификация фактически не работает: токен не проверяется на срок и издателя, а секрет в коде. Пароли хранятся открыто, и вход раскрывает существование аккаунтов. Профиль доступен кому угодно и отдаёт лишние поля, это нарушение контроля доступа (IDOR), первое место в OWASP Top 10. Загрузка файлов позволяет писать по произвольному пути.
Исправления: строгие TokenValidationParameters и секрет из конфигурации, PasswordHasher<TUser> из ASP.NET Core Identity, RequireAuthorization и проверка владельца, DTO вместо сущностей, rate limiting на /login, своё имя файла через Guid и хранение вне wwwroot.
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.