Use After Free in Feathersjs
How Use After Free Manifests in Feathersjs
Use After Free (UAF) vulnerabilities in Feathersjs typically occur when developers attempt to access or manipulate objects that have already been released from memory or marked as deleted. In the context of Feathersjs applications, this often manifests through improper handling of database records, service lifecycle events, and asynchronous operations.
One common pattern involves Feathersjs service hooks that operate on records after they've been deleted. Consider this vulnerable pattern:
app.service('users').hooks({
after: {
remove: async context => {
const userId = context.id;
// This callback might execute after the record is already deleted
// but before the transaction completes
await processUserData(userId);
}
}
});
The issue here is that processUserData might attempt to access properties or relationships of the user record after it's been removed from the database, leading to undefined behavior or exposing stale data.
Another Feathersjs-specific scenario involves the softDelete plugin. When using soft deletion, records aren't immediately removed but marked with a deletedAt timestamp. A race condition can occur:
// Vulnerable pattern with softDelete
app.service('messages').hooks({
after: {
remove: async context => {
const message = await context.app.service('messages').get(context.id);
// The record exists but is soft-deleted
// Accessing it might return inconsistent state
await notifyUser(message.userId, 'Message deleted');
}
}
});
The get call after deletion might return the soft-deleted record, but its relationships or computed properties could be in an inconsistent state, leading to use-after-free-like behavior in the application logic.
Feathersjs's real-time features (through feathers-hooks-common and feathers-websocket) can also introduce UAF vulnerabilities when clients subscribe to data that's being deleted:
// Vulnerable real-time pattern
const messageService = app.service('messages');
messageService.on('patched', async (data) => {
// Data might be in the process of being deleted
// but the event fires before deletion completes
await sendNotification(data.userId, 'Message updated');
});
The patched event fires before the delete operation completes, potentially allowing access to data that's in an invalid state.
Feathersjs-Specific Detection
Detecting Use After Free vulnerabilities in Feathersjs applications requires a combination of static analysis and runtime scanning. The middleBrick API security scanner is particularly effective at identifying these patterns in Feathersjs applications.
middleBrick's black-box scanning approach tests the unauthenticated attack surface of your Feathersjs API endpoints. For UAF detection, it specifically looks for:
- Race conditions in service hooks that execute after delete operations
- Inconsistent state handling in softDelete scenarios
- Real-time event subscriptions that access deleted records
- Async/await patterns that don't properly handle deleted state
Here's how to scan your Feathersjs API with middleBrick:
npm install -g middlebrick
middlebrick scan https://your-feathersjs-api.com
The scanner will test your API endpoints for UAF vulnerabilities by:
- Creating test records through your Feathersjs services
- Triggering delete operations and immediately attempting to access the deleted records
- Testing real-time event subscriptions during delete operations
- Analyzing the response patterns for inconsistent or stale data
For continuous monitoring, you can integrate middleBrick into your Feathersjs CI/CD pipeline:
# .github/workflows/security.yml
name: API Security Scan
on: [push, pull_request]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run middleBrick scan
run: |
npm install -g middlebrick
middlebrick scan https://staging.your-feathersjs-app.com
continue-on-error: true
- name: Fail on high-risk findings
if: failure()
run: |
echo "Security scan failed - check middleBrick report for details"
exit 1
middleBrick also provides OpenAPI/Swagger spec analysis that's particularly useful for Feathersjs applications. It can detect UAF vulnerabilities by analyzing your service definitions and hook configurations:
{
"useAfterFree": {
"severity": "high",
"description": "Race condition detected in users service remove hook",
"remediation": "Add proper state checking before accessing deleted records",
"feathersjs_version": "4.5.11"
}
}
Feathersjs-Specific Remediation
Remediating Use After Free vulnerabilities in Feathersjs requires careful attention to service lifecycle management and proper state handling. Here are Feathersjs-specific patterns and solutions:
1. Safe Hook Implementation
const { discard } = require('feathers-hooks-common');
app.service('users').hooks({
after: {
remove: async context => {
const userId = context.id;
// Safe pattern: check if record exists before processing
try {
const user = await context.app.service('users').get(userId, {
query: {
deletedAt: { $exists: false } // Only if not soft-deleted
}
});
if (user) {
await processUserData(user);
}
} catch (error) {
// Handle case where record is already deleted
console.warn(`User ${userId} not found during post-delete processing`);
}
}
}
});
2. Transaction-Based Deletion
const { sequelize } = require('./models'); // If using Sequelize
app.service('messages').hooks({
before: {
remove: async context => {
// Use transactions to ensure atomic operations
const transaction = await sequelize.transaction();
context.params.transaction = transaction;
return context.app.service('messages').get(context.id, {
transaction
}).then(message => {
context.message = message;
});
}
},
after: {
remove: async context => {
// Only process if deletion was successful
if (context.result && !context.result.deletedAt) {
await notifyUser(context.message.userId, 'Message deleted');
}
// Commit transaction if everything succeeded
if (context.params.transaction) {
await context.params.transaction.commit();
}
}
}
});
3. Safe Real-time Event Handling
const safeMessageHandler = async (data) => {
try {
// Check if data is in a valid state
if (!data || data.deletedAt) {
return; // Skip processing deleted records
}
await sendNotification(data.userId, 'Message updated');
} catch (error) {
console.error('Error in real-time message handler:', error);
}
};
app.service('messages').on('patched', safeMessageHandler);
4. Using Feathersjs's Built-in Safety Features
const { hooks } = require('feathers-hooks-common');
app.service('orders').hooks({
before: {
remove: async context => {
// Verify record exists and is in valid state
const existing = await context.app.service('orders').get(context.id);
if (!existing || existing.status === 'cancelled') {
throw new Error('Cannot delete cancelled order');
}
// Store the record for post-processing
context.existingRecord = existing;
}
},
after: {
remove: async context => {
// Only process if deletion succeeded
if (context.result && context.result.id) {
await archiveOrder(context.existingRecord);
}
}
}
});
5. Using middleBrick's Remediation Guidance
After scanning with middleBrick, you'll receive specific remediation guidance for your Feathersjs application. For example:
{
"finding": {
"id": "UAF-001",
"title": "Use After Free in users service",
"severity": "high",
"feathersjs_specific": true,
"remediation": {
"code_fix": "Add state validation before accessing deleted records",
"hook_pattern": "Use before hooks to validate state before delete operations",
"transaction_support": "Wrap delete operations in transactions for atomicity"
}
}
}