Bleichenbacher Attack in Adonisjs with Cockroachdb
Bleichenbacher Attack in Adonisjs with Cockroachdb — how this specific combination creates or exposes the vulnerability
A Bleichenbacher attack is a padding oracle attack against RSA encryption schemes that rely on PKCS#1 v1.5 padding. In an AdonisJS application that uses CockroachDB as the data store, the risk arises when the app performs decryption of attacker-controlled ciphertexts and then uses the result in a database query without strict validation. If error messages or timing differences differ between invalid padding and valid-but-unexpected data, an attacker can iteratively decrypt ciphertexts or forge valid tokens without access to the private key.
Consider an API endpoint in AdonisJS that accepts an encrypted JSON Web Token or serialized session stored as a base64 string in a CockroachDB column (e.g., user_session_encrypted). If the endpoint decrypts the blob using a public-key or symmetric scheme and then directly passes fields from the decrypted payload to a query like SELECT * FROM sessions WHERE user_id = $1, the following chain can be exploited:
- The attacker sends modified ciphertexts to the endpoint.
- AdonisJS returns distinct errors for padding failures versus SQL or deserialization errors, creating an oracle.
- By observing these differences across many requests, the attacker can recover the plaintext or forge a valid token that impersonates another user.
When CockroachDB is used, additional exposure can occur if the application stores encrypted or signed values in columns and relies on application-layer logic rather than deterministic, authenticated encryption. For example, if the decrypted user identifier is used in an ORM call like User.findBy({ id: decryptedId }), timing differences or verbose error paths may leak information about the validity of the decrypted content. The database itself does not introduce the vulnerability, but the combination of RSA padding handling in AdonisJS and CockroachDB as the backend amplifies the impact when error handling is not uniform.
To illustrate a typical vulnerable pattern in AdonisJS with CockroachDB, consider decrypting an incoming field and using it in a query:
// vulnerable: error path may differ by padding vs data validity
try {
const raw = decryptRsaPkcs1(encryptedField); // returns Buffer or string
const payload = JSON.parse(raw.toString('utf8'));
const session = await Session.query().where('user_id', payload.userId).first();
ctx.response.send(session);
} catch (err) {
ctx.response.status(400).send({ error: err.message });
}
An attacker who can distinguish a padding error from a malformed JSON or missing row can use this to conduct a Bleichenbacher attack. The fix is to ensure that error handling is consistent and that decryption uses authenticated encryption with associated data (AEAD) rather than raw RSA PKCS#1 v1.5 where possible.
Cockroachdb-Specific Remediation in Adonisjs — concrete code fixes
Remediation focuses on removing the oracle and using authenticated cryptography. In AdonisJS, prefer modern encryption libraries that provide AEAD (e.g., @stablelib/aes_gcm or Node.js crypto with aes-256-gcm) instead of RSA PKCS#1 v1.5. If you must use RSA, use OAEP padding, which is designed to be less error-prone and does not leak information via padding errors in the same way.
Store only encrypted-at-rest or signed values in CockroachDB, and ensure that decryption and verification happen before any database operation. Always return the same generic error for invalid inputs or decryption failures to prevent timing or error-based leaks.
Example of secure handling in AdonisJS with CockroachDB using Node.js crypto with AES-256-GCM:
import crypto from 'crypto';
const ALGORITHM = 'aes-256-gcm';
const KEY = Buffer.from(process.env.ENCRYPTION_KEY!, 'hex'); // 32 bytes
function encrypt(data: object): string {
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv(ALGORITHM, KEY, iv);
const plaintext = Buffer.from(JSON.stringify(data));
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
const tag = cipher.getAuthTag();
return Buffer.concat([iv, tag, encrypted]).toString('base64');
}
function decrypt(token: string): object {
const combined = Buffer.from(token, 'base64');
const iv = combined.subarray(0, 12);
const tag = combined.subarray(12, 28);
const ciphertext = combined.subarray(28);
const decipher = crypto.createDecipheriv(ALGORITHM, KEY, iv);
decipher.setAuthTag(tag);
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
return JSON.parse(decrypted.toString('utf8'));
}
In your AdonisJS controller, use the same error path for all failures:
try {
const payload = decrypt(encryptedField);
const session = await Session.query().where('user_id', payload.userId).first();
if (!session) {
// Do not reveal whether the ID was valid; return generic response
return ctx.response.send({ data: null });
}
ctx.response.send(session);
} catch {
// Always the same error, no details
ctx.response.status(400).send({ error: 'invalid_request' });
}
If you must work with RSA, use OAEP with SHA-256 and avoid returning distinct errors for padding issues:
import crypto from 'crypto';
const PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----`;
function decryptRsaOaep(ciphertextB64: string): Buffer {
return crypto.privateDecrypt({
key: PRIVATE_KEY,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
}, Buffer.from(ciphertextB64, 'base64'));
}
Store the ciphertext as a base64 string in CockroachDB and always verify integrity (e.g., via authentication tag or OAEP) before using the decrypted value. Never construct SQL strings by interpolating raw decrypted fields; use parameterized queries to avoid SQL injection in addition to protecting confidentiality.
| Approach | Padding Scheme | Error Handling | Storage in CockroachDB |
|---|---|---|---|
| Vulnerable | PKCS#1 v1.5 | Distinct errors by failure type | Encrypted column |
| Secure | OAEP or AEAD (e.g., AES-GCM) | Generic error for all failures | Encrypted column with authentication |