Padding Oracle in Feathersjs with Cockroachdb
Padding Oracle in Feathersjs with Cockroachdb — how this specific combination creates or exposes the vulnerability
A padding oracle in a Feathersjs service using Cockroachdb typically arises when error handling for encrypted data inadvertently reveals whether ciphertext is valid before decryption. If Feathersjs hooks or services call a Cockroachdb query with user-supplied encrypted payloads and the application returns different errors or timing behavior for invalid padding versus other failures, an attacker can iteratively decrypt or sign arbitrary data.
Consider a Feathersjs hook that decrypts a JWE stored in a Cockroachdb column. If the hook does not use constant-time validation and instead performs decrypt-then-authenticate patterns, an attacker can supply modified ciphertexts and observe differences in HTTP status codes or response times. Cockroachdb itself does not introduce padding issues, but queries that return distinct errors—such as row not found versus decryption failure—create an oracle surface when combined with misconfigured Feathersjs error handling.
For example, a Feathersjs before hook that directly passes encrypted input to a decryption routine and then queries Cockroachdb with the decrypted user ID can expose behavior via timing or error messages:
const jwt = require('jsonwebtoken');
const feathers = require('@feathersjs/feathers');
const app = feathers();
app.configure(require('@feathersjs/transport-commons'));
app.use('/records', {
async create(data) {
const { token } = data;
let decrypted;
try {
// Unsafe: error messages differ between padding failures and other issues
decrypted = jwt.verify(token, process.env.JWT_SECRET, { algorithms: ['HS256'] });
} catch (error) {
// If padding errors are surfaced differently, this becomes an oracle
throw { message: 'Invalid token', code: 401 };
}
const { id } = decrypted;
// Cockroachdb query that may return different errors based on prior decryption behavior
const result = await app.service('records').Model.findOne({ where: { id } });
if (!result) {
throw { message: 'Record not found', code: 404 };
}
return result;
}
});
In this pattern, if the JWT verification fails due to bad padding, the error path may differ from a missing record or a database constraint violation. An attacker can exploit these distinctions by observing HTTP status codes or response timing, effectively treating the Feathersjs+Cockroachdb stack as a padding oracle. This is especially risky when the service uses AES-CBC without proper AEAD and does not standardize error responses for all failure modes.
Another vector involves stored procedures or ORM queries in Cockroachdb that return distinct errors for malformed encrypted blobs. If Feathersjs relays these errors without normalization, the application unintentionally exposes whether a ciphertext’s padding is valid before higher-level checks complete.
Cockroachdb-Specific Remediation in Feathersjs — concrete code fixes
Remediation focuses on ensuring that error handling and decryption workflows do not leak padding validity. In Feathersjs, normalize all failure paths to return the same generic error and use constant-time cryptographic operations. Avoid decrypt-then-authenticate; prefer authenticated encryption with associated data (AEAD) such as AES-GCM where available. When interacting with Cockroachdb, ensure queries do not depend on the validity of the ciphertext before performing decryption.
Use a consistent error response structure and delay to mitigate timing attacks. Below is a hardened Feathersjs create hook that processes encrypted input, queries Cockroachdb via an ORM, and avoids padding oracle conditions:
const crypto = require('crypto');
const feathers = require('@feathersjs/feathers');
const app = feathers();
const { Sequelize, SequelizeCockroachdb } = require('@feathersjs/sequelize');
// Constant-time comparison helper
function safeCompare(a, b) {
return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
}
// Use AEAD (e.g., AES-GCM) for encryption; here we simulate decryption with proper error handling
async function decryptPayload(encrypted) {
const iv = encrypted.slice(0, 12);
const tag = encrypted.slice(-16);
const ciphertext = encrypted.slice(12, -16);
const key = Buffer.from(process.env.ENC_KEY, 'hex');
try {
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(tag);
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
return decrypted.toString('utf8');
} catch (err) {
// Always throw a generic error to avoid padding oracle
throw new Error('Invalid data');
}
}
app.use('/records', {
async create(data) {
const { encrypted } = data;
let payload;
try {
payload = await decryptPayload(Buffer.from(encrypted, 'base64'));
} catch (error) {
// Generic error prevents oracle; log internally for monitoring
console.error('Decryption failure');
throw { message: 'Bad request', code: 400 };
}
let id;
try {
id = JSON.parse(payload).id;
} catch (err) {
throw { message: 'Bad request', code: 400 };
}
// Cockroachdb query with parameterized inputs to avoid injection and timing leaks
const result = await app.service('records').Model.findOne({
where: { id },
type: SequelizeCockroachdb
});
if (!result) {
throw { message: 'Not found', code: 404 };
}
return result;
}
});
Key points:
- Use AEAD modes (e.g., AES-GCM) via
crypto.createDecipherivwith an authentication tag to eliminate padding altogether. - Catch all decryption errors and throw a single, generic message so the application does not distinguish between padding failures and other issues.
- Ensure Cockroachdb queries rely on parsed, validated data rather than on the success or failure of low-level decryption steps.
- Consider integrating the middleBrick CLI (
middlebrick scan <url>) to validate that your Feathersjs endpoints do not exhibit padding oracle characteristics during development.