Cryptographic Failures in Express
How Cryptographic Failures Manifests in Express
Cryptographic failures in Express applications typically stem from improper key management, weak algorithms, and insecure token handling. The most common pattern involves developers using default or hardcoded secrets, which attackers can easily extract from source code or configuration files.
A classic example is using process.env without validation:
const jwtSecret = process.env.JWT_SECRET || 'fallback-secret-123'; // DANGEROUS
const jwt = require('jsonwebtoken');
app.post('/login', (req, res) => {
const token = jwt.sign({ userId: user.id }, jwtSecret, { expiresIn: '1h' });
res.json({ token });
});When JWT_SECRET is undefined, the fallback secret becomes trivial to guess. Attackers can generate valid tokens for any user, leading to complete account takeover.
Session management vulnerabilities are equally prevalent. Many Express apps use express-session with insecure defaults:
app.use(session({
secret: 'keyboard-cat', // Predictable secret
resave: true, // Unnecessary
saveUninitialized: true // Security risk
}));This configuration exposes session IDs to fixation attacks and uses a guessable secret. The resave: true option forces session storage even when unmodified, while saveUninitialized: true creates sessions for unauthenticated users.
Another critical failure point is improper cookie security:
res.cookie('session', sessionId, {
httpOnly: false, // XSS vulnerability
secure: false, // Not HTTPS-only
sameSite: 'none' // CSRF risk without secure flag
});Missing httpOnly allows JavaScript to access session cookies, enabling XSS-based theft. Without secure: true, cookies transmit over HTTP, and improper sameSite settings enable cross-site request forgery.
Password handling often reveals cryptographic weaknesses. Developers sometimes implement custom hashing:
const crypto = require('crypto');
function hashPassword(password) {
return crypto.createHash('md5').update(password).digest('hex');
}MD5 is cryptographically broken and should never be used for password hashing. Even SHA-256 without proper salting and key stretching is insufficient. Express applications frequently store passwords using weak algorithms like MD5, SHA-1, or even plain text.
API key exposure represents another failure vector. Hardcoded API keys in route handlers:
app.get('/api/data', (req, res) => {
const apiKey = 'sk-1234567890'; // Exposed in source
fetch(`https://api.service.com/data?apikey=${apiKey}`)
.then(r => r.json())
.then(data => res.json(data));
});If this code reaches a public repository, the API key becomes compromised. Express applications often concatenate secrets directly into URLs or headers without environment variable validation.
Token expiration misconfiguration creates additional risks. Developers sometimes set excessively long expiration times:
const token = jwt.sign(payload, secret, { expiresIn: '365d' }); // Too longA one-year token window gives attackers ample time to exploit stolen credentials. Short-lived tokens with refresh mechanisms are the secure approach.
Express-Specific Detection
Detecting cryptographic failures in Express requires examining both code patterns and runtime behavior. Static analysis tools can identify hardcoded secrets and weak algorithms, but runtime scanning reveals configuration issues.
middleBrick's black-box scanning approach tests Express endpoints without requiring source code access. It examines HTTP responses for cryptographic weaknesses:
middlebrick scan https://api.example.com/login
# Returns JSON with security findings
# Includes cryptographic failures category with severity levelsThe scanner detects missing secure flags on cookies, weak session configurations, and improper JWT implementations. It tests whether session cookies are accessible via JavaScript by attempting XSS-style access patterns.
Middleware inspection reveals configuration issues:
const session = require('express-session');
const cookieParser = require('cookie-parser');
// Check session configuration
app.use((req, res, next) => {
if (req.sessionOptions && req.sessionOptions.secret.length < 16) {
console.warn('Session secret too short');
}
});Runtime detection can monitor for insecure cookie attributes:
app.use((req, res, next) => {
const cookies = res.getHeader('Set-Cookie') || [];
cookies.forEach(cookieHeader => {
if (!cookieHeader.includes('HttpOnly')) {
console.warn('Missing HttpOnly flag');
}
if (!cookieHeader.includes('Secure') && req.secure) {
console.warn('Missing Secure flag on HTTPS');
}
});
next();
});Token validation testing identifies weak JWT implementations. The scanner attempts to decode tokens without proper verification:
app.post('/test-jwt', (req, res) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (token) {
try {
const decoded = jwt.decode(token); // No verify!
res.json({ decoded });
} catch (err) {
res.status(400).json({ error: 'Invalid token' });
}
}
});This endpoint would fail middleBrick's authentication checks, revealing that tokens aren't properly validated.
Configuration file analysis detects hardcoded secrets in .env files, config.js, or route files. The scanner looks for patterns like:
JWT_SECRET=supersecretkey
API_KEY=1234567890
PASSWORD_HASH=$2b$10$abcdefghijklmnopqrstuvMiddleBrick's LLM security module specifically tests for AI-related cryptographic failures, such as system prompt leakage through improper token handling in AI endpoints.
Express-Specific Remediation
Fixing cryptographic failures in Express requires systematic changes to secret management, algorithm selection, and configuration practices. Start with environment variable validation:
const crypto = require('crypto');
function validateSecret(secret, minEntropy = 128) {
if (!secret || secret.length < 32) {
throw new Error('Secret too short or missing');
}
// Check for common weak secrets
const weakSecrets = ['secret', 'password', 'key', '1234'];
if (weakSecrets.some(w => secret.toLowerCase().includes(w))) {
throw new Error('Secret contains weak patterns');
}
// Check entropy (basic check)
const entropy = -secret.split('').reduce((acc, char) => {
const freq = (secret.match(new RegExp(char, 'g')) || []).length / secret.length;
return acc + freq * Math.log2(freq);
}, 0);
if (entropy < 3) {
throw new Error('Secret has low entropy');
}
}
// Usage
const jwtSecret = process.env.JWT_SECRET;
validateSecret(jwtSecret);
const jwt = require('jsonwebtoken');For session management, use secure defaults:
const session = require('express-session');
const RedisStore = require('connect-redis');
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false, // Only save when modified
saveUninitialized: false, // Don't create sessions for unauthenticated users
cookie: {
secure: process.env.NODE_ENV === 'production', // HTTPS only in production
httpOnly: true, // Prevent JS access
sameSite: 'lax', // Mitigate CSRF
maxAge: 24 * 60 * 60 * 1000 // 24 hours
},
store: new RedisStore({
client: redisClient,
ttl: 86400 // 24 hours
})
}));Password hashing should use bcrypt or Argon2 with proper salting:
const bcrypt = require('bcrypt');
async function hashPassword(password) {
const saltRounds = 12; // Increase for better security
return await bcrypt.hash(password, saltRounds);
}
async function verifyPassword(password, hash) {
return await bcrypt.compare(password, hash);
}For JWT implementation, use strong algorithms and proper validation:
const jwt = require('jsonwebtoken');
function createToken(payload, expiresIn = '2h') {
return jwt.sign(
{ ...payload, iat: Math.floor(Date.now() / 1000) },
process.env.JWT_SECRET,
{
expiresIn,
issuer: 'your-app.com',
audience: 'your-app.com'
}
);
}
function verifyToken(token) {
try {
return jwt.verify(token, process.env.JWT_SECRET, {
issuer: 'your-app.com',
audience: 'your-app.com',
algorithms: ['HS256']
});
} catch (err) {
return null;
}
}Cookie security middleware ensures consistent protection:
function secureCookies(req, res, next) {
if (process.env.NODE_ENV === 'production' && !req.secure) {
throw new Error('HTTPS required in production');
}
// Override res.cookie to enforce security
const originalCookie = res.cookie;
res.cookie = function(name, val, opts = {}) {
opts.httpOnly = opts.httpOnly !== false;
opts.secure = opts.secure !== false;
opts.sameSite = opts.sameSite || 'lax';
return originalCookie.call(this, name, val, opts);
};
next();
}
app.use(secureCookies);API key management should use environment variables with validation:
function getApiKey(serviceName) {
const key = process.env[`${serviceName}_API_KEY`];
if (!key || key.length < 20) {
throw new Error(`Invalid API key for ${serviceName}`);
}
return key;
}
// Usage
const stripeKey = getApiKey('STRIPE');
const apiKeyHeader = `Bearer ${stripeKey}`;Regular security audits using middleBrick's continuous monitoring can detect when cryptographic configurations drift from secure standards, providing alerts before vulnerabilities are exploited.