Unicode Normalization in Feathersjs
How Unicode Normalization Manifests in Feathersjs
Unicode normalization attacks in Feathersjs applications typically exploit how the framework handles string comparisons and database queries. When users can control string inputs that are later used for authentication, authorization, or data retrieval, attackers can bypass security controls by submitting visually identical but technically different Unicode representations.
The most common attack pattern involves submitting usernames or email addresses with Unicode characters that normalize to the same value as legitimate accounts. For example, an attacker might submit "admin" using a Cyrillic small letter 'a' (U+0430) instead of the Latin 'a' (U+0061). In many normalization forms, these characters appear identical but have different byte representations.
Feathersjs applications often use MongoDB or PostgreSQL with ORMs like Sequelize. These databases handle Unicode differently, and Feathersjs's query interface doesn't always normalize inputs before database operations. An attacker could exploit this by registering "admin" with a homograph character, then using the same technique to bypass authentication checks that compare against the normalized username.
Another manifestation occurs in URL parameter handling. Feathersjs services that use dynamic routing with Unicode characters in resource identifiers can be vulnerable to path traversal or IDOR attacks when normalization isn't applied consistently. An attacker might craft URLs using precomposed versus decomposed Unicode forms to access resources they shouldn't have permission to view.
Feathersjs's authentication hooks and authorization middleware are particularly susceptible when they perform string comparisons without normalization. The framework's flexible service architecture means that custom authentication logic often directly compares user-provided strings against stored values, creating opportunities for normalization-based bypasses.
Real-world examples include bypassing email verification systems where the verification link contains a Unicode email parameter, or circumventing role-based access controls where role names are compared as raw strings without normalization.
Feathersjs-Specific Detection
Detecting Unicode normalization vulnerabilities in Feathersjs applications requires examining both the application code and runtime behavior. Start by reviewing all authentication and authorization logic for direct string comparisons without normalization.
Look for patterns like:
// Vulnerable comparison in Feathersjs authentication hook
const user = await this.app.service('users').find({ email: data.email });
if (user && user.password === data.password) { /* vulnerable */ }Examine all service methods that accept user-controlled strings for identifiers, usernames, emails, or role names. Check if these values are normalized before being used in database queries or comparisons.
middleBrick's black-box scanning approach is particularly effective for detecting Unicode normalization issues in Feathersjs APIs. The scanner tests endpoints with Unicode homograph characters and attempts to bypass authentication by submitting visually identical but technically different inputs.
middleBrick specifically checks for:
- Authentication bypass attempts using Unicode homographs
- Authorization bypasses in role-based access controls
- Database query inconsistencies with Unicode inputs
- URL parameter handling vulnerabilities
- API endpoint responses that leak information about Unicode handling
- Missing input validation for Unicode characters
The scanner's 12 security checks include specific tests for authentication bypass vulnerabilities and input validation weaknesses that commonly manifest as Unicode normalization issues in Feathersjs applications.
middleBrick's OpenAPI analysis also examines service definitions to identify endpoints that accept string parameters without proper validation schemas, flagging potential Unicode attack surfaces before they're exploited.
Feathersjs-Specific Remediation
Remediating Unicode normalization vulnerabilities in Feathersjs requires a systematic approach to input handling and comparison. The most effective strategy is to normalize all user-controlled strings before any processing or storage.
Feathersjs applications can use the built-in unorm package or Node.js's unicodedata2 module for normalization. Here's how to implement it in authentication hooks:
const { unorm } = require('unorm');
class NormalizeHook {
async before(context) {
if (context.type === 'before' && context.method === 'create') {
const data = context.data;
// Normalize email and username fields
data.email = unorm.nfc(data.email.trim().toLowerCase());
data.username = unorm.nfc(data.username.trim());
}
return context;
}
}
// Apply the hook globally to authentication services
app.service('authentication').hooks({
before: {
create: [new NormalizeHook()]
}
});For database queries, normalize both the input and stored values before comparison:
const normalizeQuery = (query) => {
if (query.email) {
query.email = unorm.nfc(query.email.trim().toLowerCase());
}
if (query.username) {
query.username = unorm.nfc(query.username.trim());
}
return query;
};
// Use in service before hooks
const normalizeBeforeQuery = async (context) => {
if (context.method === 'find' || context.method === 'get') {
context.params.query = normalizeQuery(context.params.query);
}
return context;
};Implement consistent normalization across your entire Feathersjs application using global hooks:
app.hooks({
before: {
all: [async (context) => {
if (context.params.query) {
// Normalize all string query parameters
Object.keys(context.params.query).forEach(key => {
if (typeof context.params.query[key] === 'string') {
context.params.query[key] = unorm.nfc(context.params.query[key].trim());
}
});
}
})}
});For role-based access control, normalize role names before comparison:
const checkRole = async (context, rolesToCheck) => {
const user = context.params.user;
if (!user || !user.roles) return false;
const normalizedRoles = user.roles.map(role => unorm.nfc(role.trim()));
const normalizedCheck = rolesToCheck.map(role => unorm.nfc(role.trim()));
return normalizedCheck.some(role => normalizedRoles.includes(role));
};Finally, validate input schemas using Feathersjs's validation hooks to reject suspicious Unicode characters before they reach your application logic:
const validateUnicode = async (context) => {
const suspiciousPatterns = /[^ -]/; // Non-ASCII characters
const data = context.data || context.params.query;
Object.values(data).forEach(value => {
if (typeof value === 'string' && suspiciousPatterns.test(value)) {
throw new errors.BadRequest('Suspicious Unicode characters detected');
}
});
return context;
};