Код-ревью Senior ASP.NET Core обновлено

Код-ревью безопасности: вход и профиль

Вопрос

Перед вами фрагмент 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();

Коротко

В коде собраны самые частые ошибки безопасности API. Аутентификация фактически не работает: токен не проверяется на срок и издателя, а секрет в коде.

Что должен найти ревьюер

  1. Безопасностьстрока 5
    Проверка издателя, аудитории и срока жизни токена выключена: подойдёт любой токен с этой подписью, в том числе просроченный.
  2. Безопасностьстрока 8
    Ключ подписи захардкожен и слишком короткий. Секрет хранят вне кода (переменные окружения, хранилище секретов), длина ключа для HS256 от 256 бит.
  3. Безопасностьстрока 11
    CORS разрешает любой источник: если API когда-нибудь начнёт работать с cookie, это откроет запросы с чужих сайтов. Нужен явный список доменов.
  4. Безопасностьстрока 15
    Пароли хранятся и сравниваются в открытом виде. Нужен медленный хеш с солью (PasswordHasher<T>, Argon2, bcrypt) и сравнение хешей.
  5. Безопасностьстрока 16
    Сообщение об ошибке раскрывает, существует ли email, и отражает ввод пользователя. Ответ должен быть одинаковым для любого неуспешного входа.
  6. Безопасностьстрока 13
    Нет ограничения частоты попыток входа: перебор паролей не остановить. Нужен rate limiter (AddRateLimiter) и блокировка после неудач.
  7. Безопасностьстрока 20
    Профиль любого пользователя доступен без авторизации и без проверки владельца (IDOR), а наружу уходит сущность целиком, вместе с хешем пароля. Нужны RequireAuthorization, проверка прав и DTO.
  8. Безопасностьстрока 24
    Имя файла берётся от клиента: возможен обход каталога (../) и перезапись чужих файлов. Генерировать своё имя, проверять тип и размер файла.
  9. Безопасностьстрока 27
    Загрузка без авторизации и с отключённой антифорджери-защитой: любой может заливать файлы в публичную папку.

Разбор

В коде собраны самые частые ошибки безопасности API. Аутентификация фактически не работает: токен не проверяется на срок и издателя, а секрет в коде. Пароли хранятся открыто, и вход раскрывает существование аккаунтов. Профиль доступен кому угодно и отдаёт лишние поля, это нарушение контроля доступа (IDOR), первое место в OWASP Top 10. Загрузка файлов позволяет писать по произвольному пути.

Исправления: строгие TokenValidationParameters и секрет из конфигурации, PasswordHasher<TUser> из ASP.NET Core Identity, RequireAuthorization и проверка владельца, DTO вместо сущностей, rate limiting на /login, своё имя файла через Guid и хранение вне wwwroot.

Ответьте сами, а не прочитайте

В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.