Unicode Normalization in Aspnet with Firestore
Unicode Normalization in Aspnet with Firestore — how this specific combination creates or exposes the vulnerability
Unicode normalization inconsistencies between ASP.NET model binding and Firestore string handling can lead to authentication bypass, IDOR, and data exposure. In ASP.NET, incoming request data (form values, route data, JSON payloads) is bound to action parameters using default or developer-chosen encoding rules. If normalization is not applied consistently, semantically identical strings may have different binary representations. For example, the character é can be represented as a single code point U+00E9 or as a composed sequence U+0065 U+0301. Without normalization, an API endpoint that looks up a document by an ID or key may fail to match the stored document because Firestore stores strings as UTF-8 and does not implicitly normalize on write.
Consider an ASP.NET Core API that uses a user identifier in a route: /users/{userId}. If the client submits a normalized form of the ID but the document was stored with a different normalization form, Firestore queries may return no results. This mismatch can be exploited in BOLA (Broken Level Access) or IDOR scenarios: an attacker supplies a visually identical but differently normalized identifier to access another user’s data. Because Firestore queries are exact byte comparisons for string values, the server-side query may resolve to a different document or no document, allowing the attacker to bypass expected access controls.
The vulnerability is amplified when identifiers or sensitive fields are compared in application code after retrieving documents from Firestore. For instance, if a normalized username is used to construct a query but the stored username is not normalized, the query may fall back to a secondary index scan or return incomplete results, leading to inconsistent enforcement of authorization. Attackers may also leverage mixed normalization in JSON Web Token claims, session keys, or resource names to bypass input validation or authorization checks that rely on string equality. Because Firestore does not perform normalization, the responsibility falls to the ASP.NET application to normalize inputs and stored values consistently using a canonical form such as NFC or NFD before any comparison or query operation.
Real-world impact includes accounts being accessed across normalization boundaries, information disclosure through timing differences, and potential privilege escalation when authorization checks fail to align with data stored in Firestore. This is particularly relevant for applications that accept user-controlled identifiers, display names, or keys from external sources. Because the attack surface includes unauthenticated endpoints, a scanner testing unauthenticated attack surfaces can detect normalization-related mismatches by comparing query results and stored document keys across normalization forms. Remediation requires canonical normalization at ingestion and before every query or comparison, ensuring that both ASP.NET and Firestore operate on the same string representation.
Firestore-Specific Remediation in Aspnet — concrete code fixes
To mitigate Unicode normalization issues between ASP.NET and Firestore, normalize all user-supplied strings to a canonical form before using them in queries, document IDs, or comparisons. The following example demonstrates normalization at the point of model binding and before Firestore operations in an ASP.NET Core controller.
using System.Text;
using Google.Cloud.Firestore;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly FirestoreDb _firestore;
public UsersController(FirestoreDb firestore)
{
_firestore = firestore;
}
// Normalize incoming identifier to NFC before using it
[HttpGet("{userId}")]
public async Task<IActionResult>GetUser(string userId)
{
string normalizedUserId = userId.Normalize(NormalizationForm.FormC);
DocumentReference docRef = _firestore.Collection("users").Document(normalizedUserId);
DocumentSnapshot snapshot = await docRef.GetSnapshotAsync();
if (!snapshot.Exists)
{
return NotFound();
}
var user = snapshot.ConvertTo<User>();
return Ok(user);
}
// Normalize both stored and incoming values when querying by display name
[HttpGet("by-display-name")]
public async Task<IActionResult>GetByDisplayName([FromQuery] string displayName)
{
string normalizedDisplayName = displayName.Normalize(NormalizationForm.FormC);
Query query = _firestore.Collection("users")
.WhereEqualTo("normalized_display_name", normalizedDisplayName);
QuerySnapshot querySnapshot = await query.GetSnapshotAsync();
if (querySnapshot.Count == 0)
{
return NotFound();
}
return Ok(querySnapshot.ConvertTo<List<User>>());
}
// Ensure stored documents contain normalized keys and fields
[HttpPost]
public async Task<IActionResult>CreateUser([FromBody] UserInput input)
{
string normalizedUserId = input.UserId.Normalize(NormalizationForm.FormC);
string normalizedDisplayName = input.DisplayName.Normalize(NormalizationForm.FormC);
DocumentReference docRef = _firestore.Collection("users").Document(normalizedUserId);
var user = new User
{
UserId = normalizedUserId,
DisplayName = normalizedDisplayName,
NormalizedDisplayName = normalizedDisplayName
};
await docRef.SetAsync(user);
return CreatedAtAction(nameof(GetUser), new { userId = normalizedUserId }, user);
}
}
public class User
{
public string UserId { get; set; }
public string DisplayName { get; set; }
public string NormalizedDisplayName { get; set; }
}
public class UserInput
{
public string UserId { get; set; }
public string DisplayName { get; set; }
}
In this example, the ASP.NET application normalizes incoming strings to NFC using string.Normalize(NormalizationForm.FormC) before using them as Firestore document IDs or query values. The stored document includes a separate NormalizedDisplayName field to support efficient queries without additional runtime normalization. This pattern ensures that comparisons and queries are consistent across layers and prevents bypasses that exploit normalization differences.
For existing data, backfill normalized values and keys using a migration script that reads each document, normalizes the relevant fields and IDs, and writes to a new document or collection. Once normalized data is in place, update Firestore security rules (if used) to require that query parameters match the normalized schema. By combining input normalization, canonical storage, and explicit query fields, you eliminate the class of vulnerabilities that arise from inconsistent Unicode handling between ASP.NET and Firestore.