Memory Leak with Basic Auth
How Memory Leak Manifests in Basic Auth
Memory leaks in Basic Authentication contexts typically occur through credential caching mechanisms and improper session handling. When Basic Auth credentials are transmitted via the Authorization header, they're base64-encoded but remain in memory throughout the request lifecycle. The most common manifestation is credential accumulation in connection pools and authentication middleware.
Consider a Node.js Express application using Basic Auth middleware:
const users = { 'admin': 'password123' };
const authMiddleware = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader) return res.status(401).end();
const [type, credentials] = authHeader.split(' ');
if (type !== 'Basic') return res.status(401).end();
const [username, password] = Buffer.from(credentials, 'base64')
.toString('utf8')
.split(':');
if (users[username] === password) {
req.user = { username }; // Credential remains in memory
next();
} else {
res.status(401).end();
}
};
The critical issue occurs in the req.user assignment. Each authenticated request stores credentials in the request object, which may persist in connection pools if not properly cleaned up. In high-traffic scenarios, this accumulates memory overhead.
Another manifestation appears in HTTP client libraries that cache Basic Auth credentials:
const axios = require('axios');
const instance = axios.create({
auth: {
username: 'api-user',
password: 's3cr3t'
}
});
// Each request retains credentials in memory
// No cleanup mechanism exists in the library
Connection pooling exacerbates this problem. When HTTP clients maintain persistent connections with Basic Auth headers, the credentials remain encoded in TCP buffers and socket buffers, potentially surviving connection reuse across different API endpoints or services.
Basic Auth-Specific Detection
Detecting memory leaks in Basic Auth implementations requires examining both runtime behavior and code patterns. The most effective approach combines static analysis with runtime monitoring.
Static detection focuses on credential handling patterns:
const detectAuthLeaks = (sourceCode) => {
const patterns = [
/req\.user\s*=\s*{[^}]*username[^}]*}/g, // Credential storage in request
/auth:\s*{[^}]*username[^}]*password[^}]*}/g, // Hardcoded credentials
/Buffer\.from\([^)]*base64[^)]*\)/g, // Base64 decoding without validation
/authorization\s*header/gi // Authorization header handling
];
const findings = [];
patterns.forEach(pattern => {
const matches = sourceCode.match(pattern);
if (matches) {
findings.push({
pattern: pattern.toString(),
occurrences: matches.length
});
}
});
return findings;
};
Runtime detection involves monitoring memory allocation patterns during authentication flows. Using Node.js's process.memoryUsage() before and after authentication operations reveals leaks:
const monitorAuthMemory = async (authFn, iterations = 1000) => {
const initial = process.memoryUsage().heapUsed;
for (let i = 0; i < iterations; i++) {
await authFn();
}
const final = process.memoryUsage().heapUsed;
const delta = final - initial;
return {
initial,
final,
delta,
leakDetected: delta > (initial * 0.1) // 10% increase indicates leak
};
};
Automated scanning with middleBrick specifically targets Basic Auth vulnerabilities:
| Check Type | Basic Auth Focus | Detection Method |
|---|---|---|
| Credential Storage | Request object persistence | Static analysis of auth middleware |
| Base64 Decoding | Unsafe credential parsing | Regex pattern matching |
| Connection Pooling | Credential retention in sockets | Runtime memory profiling |
middleBrick's scanner identifies these patterns in seconds without requiring access credentials, testing the unauthenticated attack surface where memory leaks might be exploited.
Basic Auth-Specific Remediation
Remediating memory leaks in Basic Auth implementations requires systematic credential lifecycle management. The most effective approach uses explicit cleanup and minimal credential retention.
First, implement credential isolation:
const secureAuthMiddleware = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader) return res.status(401).end();
const [type, credentials] = authHeader.split(' ');
if (type !== 'Basic') return res.status(401).end();
const decoded = Buffer.from(credentials, 'base64').toString('utf8');
const [username, password] = decoded.split(':');
// Validate without storing credentials in request object
const isValid = validateCredentials(username, password);
if (isValid) {
// Store only user ID, not credentials
req.userId = getUserId(username);
next();
} else {
res.status(401).end();
}
};
const validateCredentials = (username, password) => {
// Use constant-time comparison to prevent timing attacks
const user = userDatabase.get(username);
if (!user) return false;
const passwordMatch = crypto.timingSafeEqual(
Buffer.from(password),
Buffer.from(user.passwordHash)
);
// Explicitly clear sensitive data
username = null;
password = null;
return passwordMatch;
};
Second, implement connection pool cleanup for HTTP clients:
const axios = require('axios');
const instance = axios.create({
baseURL: 'https://api.example.com',
timeout: 5000,
headers: { 'Connection': 'close' } // Force connection closure
});
// Custom interceptor to clean up after requests
instance.interceptors.response.use(
response => {
// Clean up any cached auth data
delete response.config.auth;
return response;
},
error => {
delete error.config.auth;
return Promise.reject(error);
}
);
Third, use memory-safe credential handling in connection pools:
const http = require('http');
const createSecureBasicAuthAgent = (username, password) => {
const authHeader = 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64');
return new http.Agent({
keepAlive: true,
keepAliveMsecs: 5000,
maxSockets: 10,
maxFreeSockets: 5,
scheduling: 'fifo',
createConnection: (port, host) => {
const socket = new http.Socket();
socket.setKeepAlive(true, 5000);
return socket;
}
});
};
// Explicitly destroy agent when done
const agent = createSecureBasicAuthAgent('user', 'pass');
// ... use agent ...
agent.destroy(); // Critical cleanup
For long-running services, implement periodic credential cache cleanup:
const credentialCache = new Map();
const MAX_CACHE_AGE = 5 * 60 * 1000; // 5 minutes
const cleanupCredentials = () => {
const now = Date.now();
for (const [key, { timestamp }] of credentialCache.entries()) {
if (now - timestamp > MAX_CACHE_AGE) {
credentialCache.delete(key);
}
}
};
setInterval(cleanupCredentials, 60000); // Run every minute