HIGH webhook abusefeathersjshmac signatures
Webhook Abuse in Feathersjs with Hmac Signatures
Webhook Abuse in Feathersjs with Hmac Signatures — how this specific combination creates or exposes the vulnerability
FeathersJS is a framework for real-time web applications that commonly uses webhooks to integrate with external services. A webhook is an HTTP callback that notifies a consumer when an event occurs. When HMAC signatures are used to authenticate webhook messages, the intent is to verify that the payload originates from a trusted sender by checking a cryptographic signature.
Hmac Signatures-Specific Remediation in Feathersjs — concrete code fixes
To securely verify HMAC signatures in FeathersJS, compare the signature in a header with a computed HMAC of the payload using a constant-time comparison to prevent timing attacks. Use a strong secret, and ensure the secret is not exposed in logs or error messages. Below are concrete, working examples for Express-style FeathersJS services.
Example: Securing a FeathersJS webhook endpoint with HMAC verification
const crypto = require('crypto');
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const app = express(feathers());
// Shared secret stored securely (e.g., environment variable)
const HMAC_SECRET = process.env.WEBHOOK_HMAC_SECRET;
if (!HMAC_SECRET) {
throw new Error('WEBHOOK_HMAC_SECRET must be set');
}
// Middleware to verify HMAC signature
function verifyHmac(req, res, next) {
const signature = req.get('x-hub-signature-256');
if (!signature) {
return res.status(401).send('Missing signature');
}
const payload = JSON.stringify(req.body);
const hmac = crypto.createHmac('sha256', HMAC_SECRET);
const digest = 'sha256=' + hmac.update(payload).digest('hex');
// Use timing-safe comparison
const isValid = crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(digest)
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
next();
}
// Example webhook service
app.use('/webhook', {
async create(data, params) {
// The signature was verified by middleware
// data is the verified webhook payload
console.log('Received webhook:', data);
return { received: true };
},
async update(id, data, params) {},
async patch(id, data, params) {},
async remove(id, params) {}
}, verifyHmac);
// Parse JSON bodies before HMAC verification
app.use(express.json());
// Start server (for local testing only)
app.listen(3030).then(server => {
console.log('Feathers webhook listener on 3030');
});
Example: Sending a signed webhook from a producer to a FeathersJS consumer
const crypto = require('crypto');
function sendSignedWebhook(url, payload, secret) {
const body = JSON.stringify(payload);
const hmac = crypto.createHmac('sha256', secret);
const signature = 'sha256=' + hmac.update(body).digest('hex');
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Hub-Signature-256': signature
},
body: body
}).then(res => {
if (res.status !== 200) {
console.error('Webhook failed:', res.status);
}
return res;
});
}
// Usage
const secret = process.env.WEBHOOK_HMAC_SECRET;
sendSignedWebhook('http://localhost:3030/webhook', { event: 'order.created', orderId: 123 }, secret)
.then(() => console.log('Sent signed webhook'))
.catch(err => console.error('Error:', err));
Frequently Asked Questions
What should I do if a webhook signature verification fails in FeathersJS?
Return a 401 status and log the failure without processing the payload. Do not attempt to parse or act on the body, and avoid exposing whether the failure was due to a malformed signature or missing secret in error details returned to the sender.
How should I store and rotate the HMAC secret for FeathersJS webhooks?
Store the secret in a secure environment variable or a secrets manager. Rotate it periodically and support a short overlap window where endpoints accept either the old or new secret to avoid breaking existing integrations during rotation.