Cache Poisoning in Sails with Basic Auth
Cache Poisoning in Sails with Basic Auth — how this specific combination creates or exposes the vulnerability
Cache poisoning occurs when an attacker causes a cache to store malicious content, leading other users to receive attacker-controlled responses. In Sails with Basic Auth, this risk arises when caching logic does not consider authentication credentials as part of the cache key. Sails applications often use HTTP-level caches or reverse proxies that cache responses based on the request URL. If a route protected by Basic Auth returns sensitive data based on Authorization headers but the cache ignores those headers, a cached response meant for one user can be served to others.
Consider a Sails controller that returns user-specific data:
module.exports = {
friendlyName: 'Get user profile',
fn: async function (req, res) {
const user = await User.findOne(req.user.id);
return res.ok(user);
},
};
If this endpoint is cached at the proxy layer without incorporating the Authorization header, User A’s profile could be cached and later served to User B who sends a different Authorization header. Because Basic Auth encodes credentials in each request, the cache may treat each Authorization header as a distinct request, yet misconfigured shared caches might normalize requests and overlook the header, causing credential confusion and data leakage.
Additionally, if the Sails app exposes endpoints that include nonces or predictable identifiers in URLs while using Basic Auth, an attacker might leverage those identifiers to poison the cache. For example, a report endpoint that embeds a user ID in the path but does not factor the Authorization header into cache segregation can become a vector for serving one user’s sensitive data to another user who happens to request the same path.
In the context of middleBrick’s 12 security checks, such misconfigurations are surfaced under BOLA/IDOR and Data Exposure categories. The scanner tests whether cached responses vary correctly with authentication context and flags endpoints where the cache does not respect Authorization headers. This is particularly important for Basic Auth, where credentials are always present in headers, making it essential to ensure caching respects those headers to avoid cross-user data exposure.
Basic Auth-Specific Remediation in Sails — concrete code fixes
To mitigate cache poisoning when using Basic Auth in Sails, ensure that authentication credentials are factored into cache keys and that responses are not cached across different users. Below are concrete remediation examples.
1. Disable caching for authenticated endpoints
Configure HTTP responses to prevent caching when Basic Auth is used. This ensures each request is processed independently and no sensitive data is stored in shared caches.
module.exports = {
friendlyName: 'Get secure profile',
fn: async function (req, res) {
// Ensure no caching of authenticated responses
res.set({
'Cache-Control': 'no-store, no-cache, must-revalidate, private',
'Pragma': 'no-cache',
'Expires': '0',
});
const user = await User.findOne(req.user.id);
return res.ok(user);
},
};
2. Include credentials in cache key at the proxy or application level
If you must cache authenticated responses, incorporate the Authorization header into the cache key. This prevents one user’s cached response from being served to another user.
// Example using a custom cache key in Sails with a Redis adapter
const crypto = require('crypto');
module.exports = {
friendlyName: 'Cached profile with auth key',
fn: async function (req, res) {
const authHeader = req.headers.authorization || '';
const cacheKey = 'user_profile_' + crypto.createHash('sha256').update(authHeader).digest('hex');
let cached = await Cache.get(cacheKey);
if (cached) {
return res.ok(JSON.parse(cached));
}
const user = await User.findOne(req.user.id);
await Cache.set(cacheKey, JSON.stringify(user), { ttl: 300 });
return res.ok(user);
},
};
3. Use Vary header correctly
When caching is necessary, use the Vary header to indicate that the response varies based on the Authorization header. This informs caches and CDNs to store separate copies per credential set.
module.exports = {
friendlyName: 'Profile with Vary header',
fn: async function (req, res) {
res.set('Vary', 'Authorization');
const user = await User.findOne(req.user.id);
return res.ok(user);
},
};
These steps align with middleBrick’s findings by addressing the root cause: ensuring that authentication context is part of cache differentiation. The scanner will highlight endpoints where cache controls are missing or improperly configured, and the remediation guidance provided by middleBrick helps prioritize fixes based on severity.