Cache Poisoning in Aspnet with Mutual Tls
Cache Poisoning in Aspnet with Mutual Tls
Cache poisoning in ASP.NET occurs when an attacker causes a cache (for example output cache, response cache, or a downstream proxy/CDN) to store a response that is specific to one user or request and later served to other users. In an ASP.NET application protected by mutual TLS (mTLS), the presence of mTLS does not automatically prevent cache poisoning; it changes the observable surface and can inadvertently introduce risks if request attributes used for cache keying are derived from client-supplied data rather than from strong mTLS-bound identities.
With mutual TLS, the server authenticates the client using its certificate, which can provide a strong identity for authorization. However, developers sometimes mistakenly use parts of the request that are not tied to the client certificate—such as headers, query strings, or cookies—for cache key construction. If the cache key does not include the mTLS-derived principal (or a stable representation of it), the same cached response may be reused across different authenticated clients. For example, an endpoint that varies output by a user ID taken from a header could inadvertently serve User A’s cached data to User B if the cache key ignores the mTLS certificate identity and the header is attacker-controlled.
Additionally, mTLS does not protect against query parameters or path-based inputs that are reflected in the response and cached. An endpoint like /orders?includeDetails=true could be cached based solely on the path and query string. If the response contains user-specific data but the cache key excludes the client certificate identity, a poisoned entry can be constructed by tricking one client into requesting a parameterized URL that gets cached and then served to others. Another scenario involves varying cache by HTTP headers that mTLS does not validate (such as custom X-Request-ID or Accept-Language), enabling an attacker to manipulate headers to poison the cache for subsequent clients.
In ASP.NET, common caching mechanisms include Response Cache, OutputCache, and IDistributedCache. If you use OutputCache on an action while relying on unvalidated inputs for VaryByParam, VaryByHeader, or VaryByQueryStrings, you risk binding cached entries to attacker-influenced values. Even with mTLS ensuring transport-level client authentication, the cache layer remains unaware of the certificate unless you explicitly incorporate the certificate’s identity into the cache key. Without this, the cache may treat distinct mTLS-authenticated requests as equivalent when they are not, leading to information disclosure across users.
To understand the interaction, consider a concrete risk: an endpoint decorated with [OutputCache(Duration=60, VaryByQueryStrings=new[] { "filter" })] and relying on mTLS for authentication. If the query parameter "filter" is user-influenced and the certificate identity is not part of the vary clause, a malicious client could induce the server to cache a response containing sensitive data attached to one mTLS identity, and that response might later be served to another authenticated client with a different certificate. This is a cache poisoning vector enabled by misalignment between mTLS identity and cache key composition.
Mutual Tls-Specific Remediation in Aspnet
Remediation centers on ensuring that cache keys explicitly incorporate the client identity established by mutual TLS. In ASP.NET, you can access the client certificate from the request and use its thumbprint or subject as a component of cache variability. Avoid relying solely on headers or query strings for cache variation when mTLS is used. Below are concrete code examples for ASP.NET Core that demonstrate how to align caching with mTLS identity.
Example 1: OutputCache with mTLS identity in ASP.NET Core (minimal API)
using System.Security.Cryptography.X509Certificates;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOutputCache();
var app = builder.Build();
app.UseOutputCache();
app.MapGet("/orders", (HttpRequest req) =>
{
// Obtain client certificate from the request
// In Kestrel with mTLS configured, the client certificate is available via Connection.ClientCertificate
X509Certificate2? cert = req.HttpContext.Connection.ClientCertificate;
string cacheKey = req.Path + req.QueryString;
if (cert is not null)
{
// Include certificate thumbprint in the vary-by to bind cache entries to the client identity
cacheKey += $"|cert={cert.Thumbprint}";
}
// Produce response (pseudocode)
var data = GetOrders(req.Query["filter"], cert?.Thumbprint);
return Results.Ok(data);
}).AddOutputCache(policy => policy.SetVaryByQueryStrings(new[] { "filter" }, null));
app.Run();
Example 2: Response Caching with explicit certificate vary in MVC Controller
using System.Security.Cryptography.X509Certificates;
[ApiController]
[Route("api/[controller]")]
public class ReportsController : ControllerBase
{
[HttpGet]
[ResponseCache(Duration = 120, VaryByQueryStrings = new[] { "reportType" }, VaryByHeader = "X-Report-Version")]
public IActionResult GetReport(string reportType)
{
// Include client certificate thumbprint in custom vary logic
var cert = HttpContext.Connection.ClientCertificate as X509Certificate2;
string certThumb = cert?.Thumbprint ?? string.Empty;
// Combine standard vary with certificate identity for robust cache separation
Response.GetTypedHeaders().CacheControl = new Microsoft.Net.Http.Headers.CacheControlHeaderValue
{
Public = true,
MaxAge = TimeSpan.FromSeconds(120)
};
// Use a composite cache key in your caching store if implementing custom caching
string compositeKey $"reportType={reportType}|cert={certThumb}";
var report = BuildReport(reportType, certThumb);
return Ok(report);
}
}
Example 3: Incorporating certificate identity in IDistributedCache
using System.Security.Cryptography.X509Certificates;
public class SecureReportService
{
private readonly IDistributedCache _cache;
public SecureReportService(IDistributedCache cache) => _cache = cache;
public async Task<string> GetCachedReportAsync(string reportType, HttpRequest req, CancellationToken ct)
{
var cert = req.HttpContext.Connection.ClientCertificate as X509Certificate2;
string cacheKey = $"report:{reportType}:cert:{cert?.Thumbprint}";
var cached = await _cache.GetStringAsync(cacheKey, ct);
if (cached != null) return cached;
var data = await BuildReportAsync(reportType, ct);
var options = new DistributedCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMinutes(10));
await _cache.SetStringAsync(cacheKey, data, options, ct);
return data;
}
}
In summary, to prevent cache poisoning in ASP.NET with mutual TLS, explicitly include the mTLS client identity (e.g., certificate thumbprint) in your cache key or vary-by rules. Do not assume that mTLS alone segregates cached responses; ensure your caching configuration and custom vary logic account for the authenticated client identity and do not rely on attacker-influenced inputs without incorporating the certificate-based identity.