Padding Oracle in Chi with Cockroachdb
Padding Oracle in Chi with Cockroachdb — how this specific combination creates or exposes the vulnerability
A padding oracle attack occurs when an application reveals whether decrypted ciphertext has correct padding, allowing an attacker to iteratively decrypt or forge messages without knowing the key. In Chi, a common pattern is to decrypt a request parameter (e.g., id or token) using AES-CBC with a hard‑coded key and then use the decrypted value as a database key to fetch a record from Cockroachdb. If the application distinguishes between invalid padding and a valid-but-wrong value by returning different status codes or messages, it acts as an oracle.
Consider a Chi route that expects an encrypted ID, decrypts it with crypto.subtle, and queries Cockroachdb via a prepared statement. If the decryption fails due to bad padding, Chi may return a 400; if decryption succeeds but the ID does not exist, it may return a 404. This difference lets an attacker submit modified ciphertexts and learn about padding validity, eventually recovering plaintext or injecting arbitrary values.
When the decrypted value is used directly in a Cockroachdb SQL string (even via placeholders), a successful padding oracle can lead to logic bypass or sensitive data exposure. For example, an attacker might craft ciphertexts that decrypt to numeric IDs, causing the query to match a different row than intended. Because the scan includes checks for Input Validation and BOLA/IDOR, middleBrick flags this as a high-severity finding when encrypted parameters influence SQL execution.
In practice, the unauthenticated attack surface tested by middleBrick can expose the oracle if Chi endpoints accept encrypted inputs and interact with Cockroachdb without strict integrity checks. The scanner’s checks for Data Exposure and Encryption further highlight whether errors or responses inadvertently disclose padding validity, which is critical for attackers to mount the attack efficiently.
Cockroachdb-Specific Remediation in Chi — concrete code fixes
Remediation focuses on ensuring that decryption failures do not leak information and that database interactions remain safe regardless of padding validity. Use authenticated encryption (e.g., AES-GCM) instead of AES-CBC to provide integrity alongside confidentiality, and treat decryption as an all-or-nothing operation that never branches on padding correctness.
When you must use AES-CBC, always verify integrity before decryption—e.g., with an HMAC over the ciphertext—and reject the request if verification fails, returning a uniform error response. In Chi, implement this by performing the HMAC check first, then attempting decryption inside a single error-handling path that returns a generic 400 for any failure.
Below is a secure Chi handler that demonstrates these principles. It uses an HMAC to verify integrity, decrypts via crypto.subtle, and queries Cockroachdb with a parameterized statement, ensuring no information about padding reaches the client.
import { sql } from '@postgresjs/pg';
import crypto from 'crypto';
const db = sql('postgresql://user:pass@cockroachdb-host:26257/mydb?sslmode=require');
function verifyHmac(ciphertext, receivedHmac, key) {
const hmac = crypto.createHmac('sha256', key);
hmac.update(ciphertext);
const expected = hmac.digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(receivedHmac));
}
app.get('/api/entity/:token', async (c) => {
const token = c.req.param('token');
const [ciphertextB64, receivedHmac] = token.split('.');
const ciphertext = Buffer.from(ciphertextB64, 'base64');
const secretKey = Buffer.from(process.env.HMAC_KEY, 'hex');
// Uniform failure path: reject if HMAC fails
if (!verifyHmac(ciphertext, receivedHmac, secretKey)) {
return c.json({ error: 'invalid request' }, 400);
}
let decrypted;
try {
const iv = ciphertext.subarray(0, 12);
const content = ciphertext.subarray(12);
const decipher = crypto.createDecipheriv('aes-256-gcm', secretKey, iv);
decrypted = Buffer.concat([decipher.update(content), decipher.final()]);
} catch (err) {
return c.json({ error: 'invalid request' }, 400);
}
const id = decrypted.toString('utf8');
const result = await db.query('SELECT data FROM entities WHERE id = $1', [id]);
if (result.rows.length === 0) {
return c.json({ error: 'not found' }, 404);
}
return c.json(result.rows[0]);
});
If you must stay with AES-CBC, ensure that you use a KDF (e.g., HKDF) to derive two keys: one for encryption and one for the HMAC. Always compute the HMAC over the ciphertext, send or store the IV alongside the ciphertext, and perform decryption and verification in a single branch that returns the same error for any invalid input. This eliminates timing differences that could be used as an oracle.
Additionally, avoid using decrypted values directly as SQL identifiers or dynamic query parts. Use parameterized queries with Cockroachdb placeholders ($1, $2, …) and validate that IDs conform to an expected pattern (e.g., UUID or integer range) before using them. middleBrick’s checks for BOLA/IDOR and Input Validation will highlight cases where user-controlled decrypted data reaches the database layer without sufficient constraints.