Graphql Batching in Aspnet
How GraphQL Batching Manifests in ASP.NET
In ASP.NET applications using GraphQL (typically via libraries like GraphQL.NET), batching allows multiple GraphQL operations to be sent in a single HTTP request, usually as an array of query objects. While intended for performance optimization, this feature creates a significant attack surface when not properly constrained. The ASP.NET GraphQL server processes each operation in the batch sequentially or in parallel, depending on configuration, against the same underlying data resolvers and authorization logic.
Attack Pattern 1: Query Flooding & Rate Limit Bypass
An attacker sends a batch containing dozens or hundreds of queries, each targeting a different resource ID (e.g., user profiles, orders). Because the batch is a single HTTP request, traditional rate limiting (often configured per-request, not per-operation) may count it as one request, allowing excessive data extraction. In an ASP.NET controller using GraphQL.NET, this might look like:
[HttpPost("graphql")]
public async Task Post([FromBody] GraphQLRequest request)
{
var result = await _executor.ExecuteAsync(request);
return Ok(result);
}
// Attacker's batch payload
[
{ "query": "query { user(id: 1) { email } }" },
{ "query": "query { user(id: 2) { email } }" },
// ... 500 more
] Attack Pattern 2: Nested Batched Queries (BOLA/IDOR Amplification)
An attacker can combine batching with field-level authorization weaknesses. For example, a batch might include queries that each request a list of sensitive records (e.g., { orders { items { creditCardLast4 } } }) for different customer IDs, exploiting a missing property-level check on the creditCardLast4 field. The ASP.NET resolver for Order might not validate that the requesting user owns each specific order in a high-volume batch, leading to mass data exposure.
Attack Pattern 3: Cost Exploitation via Complex Batches
Each query in a batch can be computationally expensive (e.g., deep recursion, large first: 1000 pagination). A batch of such queries can overwhelm the ASP.NET server's thread pool or database connection pool, causing a denial-of-service. The lack of per-operation cost analysis in the default GraphQL.NET pipeline makes this easy to exploit.
ASP.NET-Specific Detection with middleBrick
middleBrick's black-box scanner tests the unauthenticated attack surface of your ASP.NET GraphQL endpoint by sending crafted batched requests and analyzing the responses. It does not require access to your source code or configuration.
Detection Methodology:
- Batched Request Injection: middleBrick sends a POST request to
/graphql(or your configured endpoint) with a JSON array containing multiple queries. It varies the batch size (e.g., 5, 50, 200 operations) and observes response patterns. - Uniform Response Analysis (BOLA/IDOR): It looks for identical response structures across batch items that indicate successful data retrieval for different IDs. For example, if all 200 queries return a user object with an
emailfield, it flags potential Broken Object Level Authorization (BOLA). The scanner cross-references this with the OpenAPI/Swagger spec if available to understand expected data models. - Rate Limit & Performance Degradation: By measuring response times for batches of varying sizes, middleBrick detects the absence of effective rate limiting. A linear or sublinear increase in latency when doubling batch size suggests no per-operation throttling.
- LLM/AI Security (If Applicable): For ASP.NET endpoints serving LLMs (e.g., via
Microsoft.SemanticKernelintegrations), middleBrick's unique probes include sending batched prompt injection attempts to test for system prompt leakage or cost exploitation through multiple concurrent interactions.
The scan runs in 5–15 seconds and produces a risk score (0–100, A–F) with per-category breakdowns. For a vulnerable ASP.NET GraphQL batch endpoint, you'll typically see high-severity findings in the BOLA/IDOR and Rate Limiting categories, with remediation guidance specific to GraphQL.NET.
ASP.NET-Specific Remediation Strategies
Remediation focuses on implementing validation and throttling at the ASP.NET application layer, using native GraphQL.NET features and middleware. middleBrick's reports provide step-by-step guidance mapped to your codebase's patterns.
1. Enforce Strict Batch Size Limits
Configure the GraphQL.NET execution options to reject batches exceeding a safe threshold (e.g., 10 operations). This is set in your Startup.cs or program configuration:
services.AddGraphQL(options =>
{
options.ExecutionOptions =
{
// Reject batches with more than 10 operations
BatchSize = 10,
// Enable per-operation validation
EnableMetrics = true
};
});2. Implement Custom Validation Rules for BOLA/IDOR
Create a validation rule that inspects each operation in a batch for unauthorized ID access. This rule runs during the validation phase before execution:
public class BatchAuthorizationValidationRule : IValidationRule
{
public ValueTask ValidateAsync(ValidationContext context)
{
return new ValueTask(new EnterLeaveListener(_ =>
{
_.Match(field =>
{
var argument = field.Arguments.FirstOrDefault(a => a.Name == "id");
if (argument != null && argument.Value is IValue valueNode)
{
var requestedId = valueNode.Value?.ToString();
// Check if current user is authorized for this ID
if (!UserCanAccessId(context.UserContext, requestedId))
{
context.ReportError(new ValidationError(
context.Document.Source,
field,
"BATCH_BOLA",
$"Unauthorized access to ID '{requestedId}' in batched operation."));
}
}
});
}));
}
private bool UserCanAccessId(IServiceProvider userContext, string id)
{
// Integrate with ASP.NET Core's IAuthorizationService
var authService = userContext.GetService();
var user = userContext.GetService();
// Custom policy: e.g., user must own the resource
return authService.AuthorizeAsync(user, id, "OwnsResource").Result.Succeeded;
}
} Register this rule globally:
services.AddGraphQL(options =>
{
options.ValidationRules = DocumentValidator.CoreRules
.Concat(new[] { typeof(BatchAuthorizationValidationRule) })
.ToArray();
});3. Apply Per-Operation Rate Limiting Middleware
Use a distributed rate limiter (e.g., AspNetCoreRateLimit or custom middleware) that counts each operation in a batch separately. This middleware runs before GraphQL execution:
public class GraphQLBatchRateLimitMiddleware
{
private readonly RequestDelegate _next;
private readonly IRateLimitService _rateLimitService;
public GraphQLBatchRateLimitMiddleware(RequestDelegate next, IRateLimitService rateLimitService)
{
_next = next;
_rateLimitService = rateLimitService;
}
public async Task InvokeAsync(HttpContext context)
{
if (context.Request.Path.StartsWithSegments("/graphql") &&
context.Request.Method == HttpMethods.Post)
{
var batch = await JsonSerializer.DeserializeAsync>(context.Request.Body);
if (batch != null)
{
foreach (var operation in batch)
{
var key = $"{context.User.Identity?.Name}:graphql:operation";
var result = await _rateLimitService.AcquireAsync(key, 1, TimeSpan.FromSeconds(1));
if (!result.IsAccepted)
{
context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
await context.Response.WriteAsJsonAsync(new { error = "Rate limit exceeded per operation" });
return;
}
}
}
}
await _next(context);
}
}
Apply this middleware in Startup.cs before GraphQL routing.
4. Enable Query Complexity AnalysisGraphQL.NET provides a QueryComplexityAnalyzer to reject overly expensive queries. Configure it with a max score per operation and apply it to each item in a batch:
services.AddGraphQL(options =>
{
options.ExecutionOptions = new ExecutionOptions
{
ValidationRules = DocumentValidator.CoreRules
.Concat(new[]
{
new QueryComplexityValidationRule(maxScore: 1000),
new MaxDepthValidationRule(maxDepth: 10)
})
.ToArray()
};
});These measures ensure that batched requests in your ASP.NET GraphQL API are subject to the same security controls as individual requests, mitigating flooding, BOLA, and DoS risks.