Лучшие Практики Безопасности REST API в ASP.NET Core. Продолжение
1-3
4-7
8. Типы контента и размер запроса
Конечная точка, принимающая JSON, должна отклонять другие типы контента, и ни одна конечная точка не должна принимать неограниченное тело запроса. Загрузка нескольких гигабайтных файлов — простой способ исчерпать память сервера и вывести из строя ваш API. Ограничьте тип контента с помощью
[Consumes] и размер тела запроса с помощью [RequestSizeLimit]:[HttpPost]
[Consumes("application/json")]
[RequestSizeLimit(1_000_000)] // 1 MB
public IActionResult Create(CreateProductRequest request)
{
// …
}
Также можно задать лимит глобально в Kestrel:
builder.WebHost.ConfigureKestrel(opts =>
{
opts.Limits.MaxRequestBodySize = 1_000_000;
});
То же относится и к объёму данных, которые клиент может получить. Всегда используйте пагинацию с ограничением максимального размера страницы в конечных точках коллекций, чтобы клиент не мог запросить неограниченный набор результатов:
[HttpGet]
public IActionResult GetProducts(
int page = 1, int pageSize = 20)
{
// Принудительно ограничиваем размер страницы
pageSize = Math.Min(pageSize, 100);
// …
}
9. Параметризованные запросы и EF Core
Никогда не создавайте SQL-запросы путем конкатенации строк из пользовательского ввода — это способ внедрения SQL-инъекций. Параметризованные запросы сохраняют пользовательский ввод как данные, а не как исполняемый SQL. EF Core параметризует всё по умолчанию, поэтому LINQ-запрос всегда безопасен:
// EF Core параметризует 'search'
var products = await _dbContext.Products
.Where(p => p.Name.Contains(search))
.ToListAsync();
Когда вам нужен чистый SQL, используйте FromSql или FromSqlInterpolated, которые преобразуют интерполированные значения в параметры, а не в литеральный текст:
// 'category' становится параметром SQL
var products = await _dbContext.Products
.FromSqlInterpolated(
$"SELECT * FROM Products WHERE Category = {category}")
.ToListAsync();
// Никогда не делайте так
var sql = "SELECT * FROM Products WHERE Category = '" + category + "'";
// Не используйте FromSqlRaw с интерполяцией
var products = await _dbContext.Products
.FromSqlRaw(
$"SELECT * FROM Products WHERE Category = {category}")
.ToListAsync();
10. Лимитирование трафика
Без ограничений один клиент — или злоумышленник — может атаковать точку входа методом перебора паролей или завалить API запросами до тех пор, пока он не выйдет из строя. ASP.NET Core имеет встроенное лимитирование запросов, которое настраивается в Program.cs:
builder.Services.AddRateLimiter(opts =>
{
opts.AddFixedWindowLimiter("api", lim =>
{
lim.PermitLimit = 10;
lim.Window = TimeSpan.FromMinutes(1);
lim.QueueLimit = 0;
});
opts.RejectionStatusCode =
StatusCodes.Status429TooManyRequests;
});
var app = builder.Build();
app.UseRateLimiter();
Затем можно применить политику к методу действия контроллера:
[HttpPost("login")]
[EnableRateLimiting("api")]
public IActionResult Login(LoginRequest request)
{ /* … */ }или конечной точке минимальных API:
app.MapPost("/login", (LoginRequest request) =>
{ /* … */ })
.RequireRateLimiting("api");Когда клиент превышает лимит, он получает ошибку
429 Too Many Requests. Это снижает вероятность атак методом перебора паролей и атак типа «отказ в обслуживании».11. CORS
CORS (Cross-Origin Resource Sharing) контролирует, какие веб-источники могут отправлять запросы к вашему API. Опасно использовать
AllowAnyOrigin(), что позволяет любому веб-сайту в интернете вызывать ваш API от имени авторизованного пользователя. Определите именованную политику, которая точно перечисляет разрешённые источники, методы и заголовки:builder.Services.AddCors(opts =>
{
opts.AddPolicy("Web", p =>
p.WithOrigins("https://mywebsite.com")
.WithMethods("GET", "POST", "PUT", "DELETE")
.WithHeaders("Authorization", "Content-Type"));
});
var app = builder.Build();
app.UseCors("Web");
Так только
https://mywebsite.com может вызвать ваш API, только с этими методами и с такими заголовками. Всё остальное отклоняется. Оставьте AllowAnyOrigin для действительно общедоступных, неаутентифицированных API — и никогда не используйте его в сочетании с учётными данными.12. Минимальные сведения об ошибке
Ответ об ошибке должен помогать вызывающей стороне, а не злоумышленнику. Обычная страница исключения раскрывает трассировку стека, версии фреймворков, пути к файлам и SQL-запросы — карту ваших внутренних механизмов. Вместо этого возвращайте чистое, стандартное сообщение об ошибке, используя сведения о проблеме:
builder.Services.AddProblemDetails();
var app = builder.Build();
if (app.Environment.IsDevelopment())
app.UseDeveloperExceptionPage();
else
app.UseExceptionHandler();
В производственной среде необработанное исключение возвращает структурированное тело ProblemDetails с кодом состояния и стандартным сообщением — и ничего о ваших внутренних процессах.
Окончание следует…
Источник: https://antondevtips.com/blog/rest-api-security-best-practices-in-aspnetcore