CORS Fouten en Request Validation: De Blinde Vlekken in API Security
CORS misconfiguratie en ontbrekende request validation zijn twee van de meest onderschatte kwetsbaarheden in moderne APIs. Praktische aanpak om ze te fixen.
Jean-Pierre Broeders
Freelance DevOps Engineer
CORS Fouten en Request Validation: De Blinde Vlekken in API Security
Vrijwel elk security-artikel over APIs gaat over authenticatie en rate limiting. Terecht, maar er zitten twee gaten in de meeste applicaties die zelden aandacht krijgen: CORS misconfiguratie en slechte request validation. Beide zijn triviaal om te exploiten en verrassend makkelijk om te voorkomen.
Waarom CORS Misconfiguratie Gevaarlijk Is
CORS (Cross-Origin Resource Sharing) regelt welke domeinen requests mogen doen naar een API. In ontwikkeling staat dit vaak helemaal open — Access-Control-Allow-Origin: * — en dat is prima. Het probleem ontstaat wanneer die wildcard in productie terechtkomt. Of erger: wanneer de origin dynamisch wordt gespiegeld zonder validatie.
Een typisch scenario. Een .NET API die de origin header gewoon terugkaatst:
// FOUT - nooit doen
app.UseCors(policy => policy
.SetIsOriginAllowed(_ => true)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials());
Dit staat letterlijk élke website toe om authenticated requests te doen namens de gebruiker. Een aanvaller hoeft alleen een pagina te maken die een fetch doet naar de API, en de browser stuurt cookies mee alsof het een legitiem verzoek is.
De fix is simpel maar vereist discipline:
app.UseCors(policy => policy
.WithOrigins(
"https://freelyit.nl",
"https://app.freelyit.nl"
)
.WithMethods("GET", "POST", "PUT", "DELETE")
.WithHeaders("Authorization", "Content-Type")
.AllowCredentials());
Expliciet. Geen wildcards met credentials. Elke origin die toegang nodig heeft wordt met de hand toegevoegd.
De Subtielere Variant: Regex Origins
Sommige teams gebruiken regex om origins te matchen, bijvoorbeeld voor staging-omgevingen:
// Gevaarlijk als de regex niet goed is
.SetIsOriginAllowed(origin =>
origin.EndsWith(".freelyit.nl"))
Dit matcht ook evil-freelyit.nl. Een punt mist:
.SetIsOriginAllowed(origin =>
{
var uri = new Uri(origin);
return uri.Host == "freelyit.nl"
|| uri.Host.EndsWith(".freelyit.nl");
})
Beter, maar eigenlijk hoort dit soort logica niet in applicatiecode. Gebruik een allowlist uit configuratie en hou het simpel.
Request Validation: Vertrouw Niets
Het tweede blinde vlek. Veel APIs valideren input alleen op type-niveau — is het een string, is het een number — maar niet op inhoud. Dat opent de deur voor injection, oversized payloads, en data die logisch gezien nergens op slaat.
Een voorbeeld met FluentValidation in .NET:
public class CreateProjectValidator : AbstractValidator<CreateProjectRequest>
{
public CreateProjectValidator()
{
RuleFor(x => x.Name)
.NotEmpty()
.MaximumLength(200)
.Matches(@"^[\w\s\-\.]+$")
.WithMessage("Projectnaam bevat ongeldige tekens");
RuleFor(x => x.Description)
.MaximumLength(5000);
RuleFor(x => x.Budget)
.GreaterThan(0)
.LessThanOrEqualTo(10_000_000);
RuleFor(x => x.StartDate)
.GreaterThanOrEqualTo(DateTime.UtcNow.Date)
.When(x => x.StartDate.HasValue);
}
}
Dit gaat verder dan "is het een string". De naam mag geen rare tekens bevatten. Het budget kan niet negatief zijn of absurd hoog. Startdatum ligt niet in het verleden. Stuk voor stuk checks die logische aanvallen voorkomen.
Payload Size Limits
Request body limits worden vaak vergeten tot iemand een 500MB JSON-blob naar de API stuurt en de server omvalt. In .NET:
builder.WebHost.ConfigureKestrel(options =>
{
options.Limits.MaxRequestBodySize = 10 * 1024 * 1024; // 10MB
});
// Of per endpoint met een attribute
[RequestSizeLimit(1_048_576)] // 1MB
[HttpPost("upload")]
public async Task<IActionResult> Upload(IFormFile file) { }
Op API gateway-niveau — als er een NGINX of Azure API Management voorzit — dezelfde limits instellen. Defense in depth.
Content-Type Enforcement
Een minder bekende aanvalsvector: requests zonder of met een verkeerde Content-Type header. Sommige frameworks parsen de body toch, wat onverwacht gedrag oplevert.
app.Use(async (context, next) =>
{
if (context.Request.Method is "POST" or "PUT" or "PATCH")
{
var contentType = context.Request.ContentType;
if (string.IsNullOrEmpty(contentType) ||
!contentType.StartsWith("application/json"))
{
context.Response.StatusCode = 415;
await context.Response.WriteAsJsonAsync(new
{
error = "Unsupported Media Type"
});
return;
}
}
await next();
});
Streng, maar het voorkomt een hele categorie aanvallen waarbij browsers requests sturen met text/plain om CORS preflight te omzeilen.
Checklist
Voor wie dit direct wil toepassen:
- CORS origins expliciet opgeven, geen wildcards met credentials
- Regex matching vermijden voor origins, of zeer zorgvuldig testen
- Input validatie op inhoudsniveau, niet alleen types
- Payload limits op zowel applicatie als gateway-niveau
- Content-Type afdwingen voor muterende requests
- Preflight caching instellen om onnodige OPTIONS-requests te reduceren
Geen van deze maatregelen is complex. Het kost misschien een uur om ze allemaal te implementeren. Maar zonder ze is een API kwetsbaarder dan de meeste teams denken.
