Broken Authentication in Fiber with Cockroachdb
Broken Authentication in Fiber with Cockroachdb — how this specific combination creates or exposes the vulnerability
Broken Authentication in a Fiber application using CockroachDB typically arises when session or token handling is implemented on top of the database without adequate protections. For example, if session identifiers or password reset tokens are stored in CockroachDB as raw values and compared using string equality without constant-time logic, an attacker can exploit timing differences to infer valid tokens. A vulnerable pattern looks like this:
const db = new CockroachDB({ connectionString: process.env.COCKROACH_URI });
app.post('/login', async (req, res) => {
const { email, password } = req.body;
const row = await db.query('SELECT id, password_hash FROM users WHERE email = $1', [email]);
if (!row[0]) { return res.status(401).send('Unauthorized'); }
const match = await bcrypt.compare(password, row[0].password_hash);
if (!match) { return res.status(401).send('Unauthorized'); }
const sessionId = crypto.randomBytes(32).toString('hex');
await db.query('INSERT INTO sessions (id, user_id, expires_at) VALUES ($1, $2, $3)', [sessionId, row[0].id, new Date(Date.now() + 3600000)]);
res.cookie('session_id', sessionId, { httpOnly: true, secure: true });
res.send('OK');
});
This code appears safe, but if the email lookup is not protected against case-insensitive mismatches or if the sessions table lacks proper constraints, an attacker may leverage BOLA/IDOR by guessing sequential user IDs or session identifiers. Additionally, if session tokens are generated with insufficient entropy or if the application does not enforce binding to the user’s IP or user-agent, an attacker who steals a token can impersonate the user across requests. Another risk emerges when password reset tokens are stored in CockroachDB with weak randomness and long expiry windows, enabling token interception and replay. These issues are amplified in distributed CockroachDB deployments where secondary indexes and follower reads can expose race conditions if the application does not use strict serializable isolation or appropriate AS OF SYSTEM TIME constraints.
Moreover, without rate limiting on authentication endpoints, attackers can perform credential spraying or password spraying attacks against the Fiber routes backed by CockroachDB. Since middleBrick tests Authentication, BOLA/IDOR, and Rate Limiting in parallel, such misconfigurations are detectable as findings. For instance, an endpoint that reveals whether an email exists via different HTTP status codes or response times leaks information that facilitates account enumeration, a common precursor to Broken Authentication.
Cockroachdb-Specific Remediation in Fiber — concrete code fixes
To remediate Broken Authentication when using CockroachDB with Fiber, adopt constant-time comparisons, strict token handling, and secure session management. Use parameterized queries consistently to avoid injection, and enforce serializable transactions where correctness depends on isolation. The following example demonstrates a hardened login flow:
const db = new CockroachDB({ connectionString: process.env.COCKROACH_URI });
app.post('/login', async (req, res) => {
const client = await db.getClient();
try {
await client.query('BEGIN');
const { email, password } = req.body;
const result = await client.query('SELECT id, password_hash, failed_attempts, locked_until FROM users WHERE email = $1 AS OF SYSTEM TIME statement_timestamp()', [email]);
if (result.rows.length === 0) {
await client.query('ROLLBACK');
return res.status(401).send('Unauthorized');
}
const user = result.rows[0];
if (user.locked_until && new Date(user.locked_until) > new Date()) {
await client.query('ROLLBACK');
return res.status(403).send('Account locked');
}
const match = await bcrypt.compare(password, user.password_hash);
if (!match) {
await client.query('UPDATE users SET failed_attempts = failed_attempts + 1 WHERE id = $1', [user.id]);
await client.query('ROLLBACK');
return res.status(401).send('Unauthorized');
}
await client.query('UPDATE users SET failed_attempts = 0 WHERE id = $1', [user.id]);
const sessionId = crypto.randomBytes(32).toString('hex');
await client.query('INSERT INTO sessions (id, user_id, expires_at) VALUES ($1, $2, $3)', [sessionId, user.id, new Date(Date.now() + 3600000)]);
await client.query('COMMIT');
res.cookie('session_id', sessionId, { httpOnly: true, secure: true, sameSite: 'strict' });
res.send('OK');
} catch (err) {
await client.query('ROLLBACK');
res.status(500).send('Internal Server Error');
} finally {
client.release();
}
});
For password reset, generate a high-entropy token and store it with a short TTL. Validate the token in constant time and bind it to the user’s record with a serializable transaction:
app.post('/request-reset', async (req, res) => {
const client = await db.getClient();
try {
await client.query('BEGIN');
const { email } = req.body;
const user = await client.query('SELECT id FROM users WHERE email = $1 AS OF SYSTEM TIME statement_timestamp()', [email]);
if (user.rows.length === 0) {
await client.query('ROLLBACK');
return res.status(404).send('Not found');
}
const token = crypto.randomBytes(32).toString('hex');
await client.query('INSERT INTO password_resets (user_id, token, expires_at) VALUES ($1, $2, $3)', [user.rows[0].id, token, new Date(Date.now() + 900000)]);
await client.query('COMMIT');
// send token via email
res.send('Reset link sent');
} catch (err) {
await client.query('ROLLBACK');
res.status(500).send('Internal Server Error');
} finally {
client.release();
}
});
app.post('/reset-password', async (req, res) => {
const client = await db.getClient();
try {
await client.query('BEGIN');
const { token, newPassword } = req.body;
const now = new Date();
const result = await client.query('SELECT user_id FROM password_resets WHERE token = $1 AND expires_at > $2 AS OF SYSTEM TIME statement_timestamp()', [token, now]);
if (result.rows.length === 0) {
await client.query('ROLLBACK');
return res.status(400).send('Invalid or expired token');
}
const hash = await bcrypt.hash(newPassword, 10);
await client.query('UPDATE users SET password_hash = $1 WHERE id = $2', [hash, result.rows[0].user_id]);
await client.query('DELETE FROM password_resets WHERE user_id = $1', [result.rows[0].user_id]);
await client.query('COMMIT');
res.send('Password updated');
} catch (err) {
await client.query('ROLLBACK');
res.status(500).send('Internal Server Error');
} finally {
client.release();
}
});
Additionally, enforce rate limiting on authentication endpoints and use middleBrick’s free tier to scan your API regularly. Its CLI can be integrated into scripts to verify that no plaintext secrets or weak tokens remain in your CockroachDB-backed endpoints.
Related CWEs: authentication
| CWE ID | Name | Severity |
|---|---|---|
| CWE-287 | Improper Authentication | CRITICAL |
| CWE-306 | Missing Authentication for Critical Function | CRITICAL |
| CWE-307 | Brute Force | HIGH |
| CWE-308 | Single-Factor Authentication | MEDIUM |
| CWE-309 | Use of Password System for Primary Authentication | MEDIUM |
| CWE-347 | Improper Verification of Cryptographic Signature | HIGH |
| CWE-384 | Session Fixation | HIGH |
| CWE-521 | Weak Password Requirements | MEDIUM |
| CWE-613 | Insufficient Session Expiration | MEDIUM |
| CWE-640 | Weak Password Recovery | HIGH |