Cache Poisoning in Aspnet (Csharp)
Cache Poisoning in Aspnet with Csharp — how this specific combination creates or exposes the vulnerability
Cache poisoning in an ASP.NET application occurs when an attacker causes cached responses to store malicious or incorrect data, which is then served to other users. In the ASP.NET ecosystem, this can arise from unsafe use of output caching, response caching middleware, or in-memory caches where the cache key does not sufficiently isolate content by user, tenant, or request context.
ASP.NET Core provides several caching mechanisms, such as ResponseCache attributes, IMemoryCache, and distributed caches. If these are configured without considering request-specific factors like authentication state, tenant identifier, or user input, an attacker can craft requests that poison the shared cache. For example, an endpoint that includes user-controlled query parameters in the cache key without validation may store a response containing sensitive data or malicious content under a key that is later reused for different users or unauthenticated requests.
Consider an endpoint that caches product details but incorporates the raw category query parameter into the cache logic without normalization or strict validation. An attacker might request a category name that triggers inclusion of admin-only information or inject script-like values if the cached response is later rendered in a vulnerable client context. In a scenario where the endpoint returns sensitive headers or cookies and the cache stores the full response, subsequent requests for the same category may inadvertently expose that data.
Another common pattern is output caching in controller actions where Vary rules are incomplete. If the cache does not vary by critical dimensions such as user ID or tenant ID, a logged-in user’s personalized response might be cached and then served to another user. This is particularly relevant when using response caching in ASP.NET Core with VaryByCustom or when relying on default behavior that does not account for authorization context.
Because middleBrick tests unauthenticated attack surfaces, it can detect endpoints where caching behavior may expose data across users or contexts. Findings typically highlight missing input validation on cache-influencing parameters, missing user context in cache keys, and lack of secure defaults in caching headers. These issues map to insecure design patterns aligned with OWASP API Top 10 categories and relevant compliance considerations.
Csharp-Specific Remediation in Aspnet — concrete code fixes
To mitigate cache poisoning in ASP.NET, ensure cache keys incorporate user, tenant, and authorization context, and validate or sanitize all inputs that influence caching behavior. Below are concrete examples using IMemoryCache and response caching in ASP.NET Core.
1. Safe IMemoryCache usage with user-aware keys
When using IMemoryCache, construct cache keys that include a stable user or tenant identifier and avoid incorporating raw user input without normalization.
// Example: caching user-specific data safely in C#
public class ProductService
{
private readonly IMemoryCache _cache;
private readonly IHttpContextAccessor _httpContextAccessor;
public ProductService(IMemoryCache cache, IHttpContextAccessor httpContextAccessor)
{
_cache = cache;
_httpContextAccessor = httpContextAccessor;
}
public string GetProductDetails(string category)
{
var userId = _httpContextAccessor.HttpContext?.User?.Identity?.Name ?? "anonymous";
// Normalize category to avoid path traversal or encoding issues
var normalizedCategory = Uri.UnescapeDataString(category ?? "default")
.Replace("..", string.Empty, StringComparison.Ordinal);
var cacheKey = $"product:{userId}:{normalizedCategory}";
if (!_cache.TryGetValue(cacheKey, out string cachedResult))
{
// Simulate fetching and caching product details
cachedResult = FetchProductDetailsFromSource(normalizedCategory);
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(5))
.SetPriority(CacheItemPriority.Normal);
_cache.Set(cacheKey, cachedResult, cacheEntryOptions);
}
return cachedResult;
}
private string FetchProductDetailsFromSource(string category)
{
// Placeholder for actual data retrieval logic
return $"Details for {category}";
}
}
2. Response caching with explicit Vary rules
When using response caching, explicitly vary by user identifier or authorization context to prevent cross-user leakage.
// Example: ResponseCache with VaryByCustom in ASP.NET Core
[ApiController]
[Route("api/[controller]")]
public class ProductController : ControllerBase
{
[HttpGet]
[ResponseCache(Duration = 60, VaryByQueryKeys = new string[] { "category", "userId" }, VaryByCustom = "User")]
public IActionResult Get(string category, [FromQuery] string userId)
{
// Ensure userId is derived from authenticated context, not raw query when sensitive
var resolvedUserId = User.Identity?.IsAuthenticated == true ? User.FindFirst("sub")?.Value ?? "anon" : "anon";
var model = new { Category = category, UserId = resolvedUserId, Data = "sample" };
return Ok(model);
}
}
// In Startup.cs or Program.cs, configure VaryByCustom for custom logic if needed
public override string GetVaryByCustomString(HttpContext context, string customKey)
{
if (customKey == "User")
{
return context.User.Identity?.IsAuthenticated == true
? context.User.FindFirst("sub")?.Value ?? "anonymous"
: "anonymous";
}
return base.GetVaryByCustomString(context, customKey);
}
3. Validate and sanitize inputs that affect caching
Treat parameters that influence cache behavior as untrusted. Validate against allowlists and avoid using raw values directly in cache keys or response headers.
// Example input validation for a category parameter
public static class CacheInputValidator
{
private static readonly HashSet<string> AllowedCategories = new HashSet<string>
{
"electronics", "books", "clothing"
};
public static bool TryNormalizeCategory(string input, out string normalized)
{
normalized = null;
if (string.IsNullOrWhiteSpace(input))
{
return false;
}
var trimmed = input.Trim().ToLowerInvariant();
if (!AllowedCategories.Contains(trimmed))
{
return false;
}
// Further normalization can include removing dangerous characters
normalized = trimmed;
return true;
}
}
By combining user-aware cache keys, explicit Vary rules, and strict input validation, you reduce the risk of cache poisoning in ASP.NET applications. These practices help ensure cached responses are isolated appropriately and do not inadvertently expose data or enable injection through cached content.