Cache Poisoning in Aspnet with Firestore
Cache Poisoning in Aspnet with Firestore — how this specific combination creates or exposes the vulnerability
Cache poisoning in an ASP.NET application that reads from Cloud Firestore occurs when an attacker causes cached responses to store attacker-controlled data or to be shared across users. Because Firestore documents are often keyed by identifiers derived from request data (such as IDs, query parameters, or route values), an unsafe caching layer may treat attacker-influenced values as cache keys or as part of the cached payload. This can lead to data leakage across users, cache deception that bypasses authorization checks, or stale data being served with elevated trust.
With Firestore, common patterns include using a document path like users/{userId}/profile or querying a collection with filters that include a client-supplied parameter. If the ASP.NET cache key incorporates these values without strict validation and normalization, two different users may map to the same cache entry. For example, an IDOR-prone endpoint that returns a Firestore document snapshot could be cached based on the resource ID alone. An attacker who can control the ID can poison the cache so that user A receives user B’s data from the cache, believing it is their own. Because Firestore rules operate at query and document access time and not at the cache layer, cached results can sidestep intended rule evaluations, effectively exposing data that would otherwise be restricted.
Another scenario involves query result caching. If an ASP.NET service performs a Firestore query such as cities.WhereEqualTo("country", countryCode) and caches the resulting documents under a key derived only from countryCode, an attacker supplying a sensitive or unexpected value for countryCode can cause the cache to store and later serve data to other users. Because Firestore does not inherently understand the caching layer, it will return whatever documents match the query at cache-insert time. If those documents include fields that should be redacted or filtered per user, the cached output can disclose PII or internal references. This becomes especially risky when combined with overly permissive Firestore rules that allow broad read access but are assumed to be enforced by the cache.
Cache poisoning in this context can also stem from response normalization issues. Firestore returns data in a structured format; if the ASP.NET cache stores the raw JSON without canonicalizing keys, type differences, or encoding variations, an attacker may supply input that results in semantically different but cache-key-identical representations. Subsequent requests with normalized inputs may then receive the poisoned response, leading to incorrect behavior or information exposure. Because Firestore indexes and query semantics differ from relational databases, subtle changes in how queries are parameterized can produce different document sets that get collapsed into a single cache entry.
Because middleBrick scans unauthenticated attack surfaces and tests input validation and data exposure, it can surface unsafe caching behaviors that involve Firestore-backed services. Findings typically highlight places where cache keys incorporate untrusted input or where cached responses bypass intended access controls. Remediation focuses on ensuring cache keys are deterministic, normalized, and scoped to the correct user or context, and on validating that cached data does not violate least-privilege access.
Firestore-Specific Remediation in Aspnet — concrete code fixes
To mitigate cache poisoning when using Cloud Firestore in ASP.NET, treat cache keys as sensitive values and avoid incorporating raw user input. Normalize and scope cache keys to the authenticated context and the exact query or document path. Use strong serialization boundaries and avoid storing raw Firestore snapshots directly in the cache without considering field-level sensitivity.
Example of a vulnerable pattern:
// Vulnerable: cache key uses raw userId from request
var userId = httpContext.Request.Query["userId"];
var cacheKey = $"user_profile_{userId}";
if (!_cache.TryGetValue(cacheKey, out Profile profile)) {
var doc = await _firestoreDb.Document($"users/{userId}/profile").GetSnapshotAsync();
profile = doc.ConvertTo<Profile>();
_cache.Set(cacheKey, profile);
}
return Ok(profile);
This allows an attacker to vary userId and cause cache collisions or retrieve other users’ profiles from cached entries. An attacker who can control userId can effectively force other users to receive a cached response intended for a different identity.
Secure approach with explicit scoping and canonicalization:
// Secure: derive cache key from authenticated user ID and a normalized scope
var userId = _userManager.GetUserId(User); // authenticated identity
var normalizedScope = "profile_v1";
var cacheKey = $"user:{userId}:{normalizedScope}";
if (!_cache.TryGetValue(cacheKey, out Profile profile)) {
var doc = await _firestoreDb.Document($"users/{userId}/profile").GetSnapshotAsync();
if (doc.Exists) {
profile = doc.ConvertTo<Profile>();
// Optionally remove sensitive fields before caching
profile.SensitiveField = null;
_cache.Set(cacheKey, profile, new MemoryCacheEntryOptions {
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
});
} else {
profile = new Profile();
}
}
return Ok(profile);
For Firestore query caching, avoid keying solely on parameters that an attacker can influence. Instead, include the query fingerprint and the authenticated subject:
// Secure: key includes authenticated user and a hash of the query parameters
var userId = _userManager.GetUserId(User);
var countryCode = Request.Query["country"].ToString();
// Normalize and validate countryCode before using
if (!IsValidCountry(countryCode)) {
return BadRequest("Invalid country");
}
var queryFingerprint = Convert.ToBase64String(SHA256.HashData(Encoding.UTF8.GetBytes($"cities_country_{countryCode}")));
var cacheKey = $"query:cities:{userId}:{queryFingerprint}";
if (!_cache.TryGetValue(cacheKey, out List<City> cities)) {
var query = _firestoreDb.Collection("cities").WhereEqualTo("country", countryCode);
var snapshot = await query.GetSnapshotAsync();
cities = snapshot.Documents.Select(d => d.ConvertTo<City>()).ToList();
// Filter out fields not needed for this context to reduce exposure
cities.ForEach(c => c.InternalRef = null);
_cache.Set(cacheKey, cities, new MemoryCacheEntryOptions {
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
});
}
return Ok(cities);
Additionally, enforce field-level redaction before caching any Firestore document. Do not rely on Firestore security rules to prevent cached data exposure, because the cache may serve data beyond the scope of those rules.
| Remediation Focus | Why it matters for Firestore in ASP.NET | Implementation guidance |
|---|---|---|
| Cache key scoping | Prevents cross-user cache collisions and poisoning via manipulated IDs or query parameters | Include authenticated user identifier and a normalized query fingerprint; avoid raw IDs from untrusted input |
| Input validation and canonicalization | Reduces risk that semantically different inputs map to the same cache key | Validate against allowlists, normalize encoding, and hash structured parameters before using them in keys |
| Sensitive data filtering before caching | Firestore rules are not enforced by the cache; cached snapshots may contain over-permissive fields | Strip or nullify sensitive fields (PII, internal references) before storing in the ASP.NET cache |