HIGH cache poisoningsails

Cache Poisoning in Sails

How Cache Poisoning Manifests in Sails

Cache poisoning in Sails applications typically occurs through improper handling of user-controlled data in caching mechanisms. Sails.js provides several caching layers including the built-in cache service, Waterline ORM caching, and integration with external solutions like Redis. The vulnerability arises when attackers can manipulate cache keys or inject malicious content that gets stored and served to other users.

A common pattern in Sails applications involves caching database query results based on user input. Consider this vulnerable controller action:

module.exports = {
  findUsers: async function (req, res) {
    const search = req.query.search || '';
    const cacheKey = `user-search-${search}`;
    
    const cached = await Cache.get(cacheKey);
    if (cached) {
      return res.json(cached);
    }
    
    const users = await User.find({ 
      or: [
        { firstName: { contains: search } },
        { lastName: { contains: search } }
      ]
    });
    
    await Cache.set(cacheKey, users, 3600);
    return res.json(users);
  }
};

This implementation is vulnerable because an attacker can control the cacheKey entirely through the search parameter. By submitting crafted input, they could:

  • Overwrite existing cache entries by predicting cache keys
  • Poison the cache with malicious data structures
  • Trigger cache stampedes by forcing cache misses
  • Exploit cache timing to perform timing attacks

Another manifestation occurs with Waterline's automatic query caching. When developers enable cache: true on model queries without proper validation, user input can influence what gets cached:

async findProducts(req, res) {
  const category = req.query.category || 'all';
  const products = await Product.find({
    where: { category },
    cache: true, // Vulnerable if category is user-controlled
    ttl: 600
  });
  return res.json(products);
}

The Waterline cache stores results based on the query parameters, so an attacker can manipulate the category parameter to poison cached responses. This becomes particularly dangerous when combined with Sails's policy system, where cached responses might bypass authentication checks.

Sails's blueprint routes can also introduce cache poisoning vulnerabilities. When blueprint actions cache responses without proper isolation, one user's data might be served to another user. This is especially problematic with actions that include sensitive information like user profiles or order details.

Sails-Specific Detection

Detecting cache poisoning in Sails applications requires examining both the caching configuration and how user input influences cache keys and stored data. Start by reviewing your config/cache.js file to understand what caching adapters are configured and their default behaviors.

Look for these specific patterns in your Sails controllers and models:

// Patterns that indicate potential cache poisoning
const cacheKey = `search-${req.query.term}`; // User input directly in cache key
await Cache.set(req.param('id'), data, ttl); // User controls cache key
await Model.find({ where: userInput, cache: true }); // User input in cached query
res.cache({ key: req.query.id }); // User-controlled cache key in response

middleBrick's scanning engine specifically identifies these patterns through static analysis of your Sails application code. The scanner examines:

  • All controller actions that interact with the cache service
  • Model queries with cache: true where query parameters contain user input
  • Blueprint routes that might cache sensitive responses
  • Policy configurations that could be bypassed through cached responses

When scanning with middleBrick, you can target specific Sails endpoints:

npx middlebrick scan http://localhost:1337/api/users --framework sails

The scanner tests for cache poisoning by attempting to:

  • Manipulate cache keys through query parameters
  • Observe whether different user sessions receive cached responses
  • Check if authentication bypass occurs through cached data
  • Verify proper cache isolation between user contexts

middleBrick also analyzes your Sails configuration files to identify risky caching setups, such as:

// config/cache.js - vulnerable configuration
module.exports.cache = {
  adapter: 'memory',
  default: 'memory',
  // No cache key prefixing or isolation
};

The scanner generates a security score (0-100) with specific findings for cache-related vulnerabilities, including the severity level and exact line numbers where issues occur. This allows you to quickly identify and remediate cache poisoning vulnerabilities in your Sails application.

Sails-Specific Remediation

Remediating cache poisoning in Sails applications requires a defense-in-depth approach that validates input, isolates cache contexts, and implements proper cache key management. Here are specific fixes for Sails applications:

First, always validate and sanitize user input before using it in cache operations:

const _ = require('lodash');

module.exports = {
  findUsers: async function (req, res) {
    const search = _.trim(req.query.search || '');
    
    // Validate input length and allowed characters
    if (search.length > 50 || /[^a-zA-Z0-9 ]/.test(search)) {
      return res.badRequest('Invalid search term');
    }
    
    // Use a consistent, safe cache key format
    const cacheKey = `user-search:${crypto.createHash('md5').update(search).digest('hex')}`;
    
    const cached = await Cache.get(cacheKey);
    if (cached) {
      return res.json(cached);
    }
    
    const users = await User.find({ 
      or: [
        { firstName: { contains: search } },
        { lastName: { contains: search } }
      ]
    });
    
    await Cache.set(cacheKey, users, 3600);
    return res.json(users);
  }
};

For Waterline queries, implement a whitelist approach for cached parameters:

const VALID_CATEGORIES = ['electronics', 'books', 'clothing', 'all'];

async findProducts(req, res) {
  const category = (req.query.category || 'all').toLowerCase();
  
  // Only allow whitelisted categories
  if (!VALID_CATEGORIES.includes(category)) {
    return res.badRequest('Invalid category');
  }
  
  // Create a safe cache key with user ID context
  const userId = req.session.userId || 'guest';
  const cacheKey = `products:${category}:${userId}`;
  
  const cached = await Cache.get(cacheKey);
  if (cached) {
    return res.json(cached);
  }
  
  const products = await Product.find({
    where: category === 'all' ? {} : { category },
    cache: true,
    ttl: 600
  });
  
  await Cache.set(cacheKey, products, 600);
  return res.json(products);
}

Implement cache isolation by including user context in cache keys:

// Policy to add user context to cache keys
module.exports = function (req, res, next) {
  if (req.options.cache) {
    // Add user ID and session data to cache key
    req.options.cache.key = `${req.options.cache.key}:${req.session.userId || 'anon'}`;
  }
  next();
};

For blueprint routes, disable caching on sensitive actions or implement custom policies:

// config/policies.js
module.exports.policies = {
  UserController: {
    // Disable caching on profile actions
    profile: ['cacheDisabled', 'isAuthenticated'],
    
    // Allow caching on public actions with safe keys
    search: ['cacheControl', 'isAuthenticated']
  }
};

Consider using Redis with proper namespace isolation for production deployments:

// config/cache.js
module.exports.cache = {
  adapter: 'redis',
  host: 'localhost',
  port: 6379,
  password: process.env.REDIS_PASSWORD,
  namespace: 'myapp:' + (process.env.NODE_ENV || 'development') + ':',
  ttl: 3600
};

Finally, implement cache-busting mechanisms for sensitive data:

async updateProfile(req, res) {
  const updatedUser = await User.updateOne({ id: req.session.userId })
    .set(req.body)
    .fetch();
  
  // Bust related cache entries
  await Cache.del(`user-profile:${req.session.userId}`);
  await Cache.del(`user-activity:${req.session.userId}`);
  
  return res.json(updatedUser);
}

Frequently Asked Questions

How does middleBrick detect cache poisoning in Sails applications?
middleBrick scans your Sails application code to identify patterns where user input directly influences cache keys or cached query parameters. It examines controller actions, model queries with caching enabled, and blueprint routes to find vulnerabilities. The scanner tests these endpoints by manipulating input to observe cache behavior and checks for proper isolation between user contexts. It provides specific findings with line numbers and severity levels to help you quickly locate and fix cache poisoning vulnerabilities.
Can cache poisoning in Sails lead to authentication bypass?
Yes, cache poisoning can potentially lead to authentication bypass in Sails applications. If cached responses contain sensitive data or authentication tokens, and the cache key can be manipulated through user input, an attacker might access another user's cached session data. This is particularly dangerous when blueprint actions cache responses without proper user isolation. Always include user context in cache keys and implement proper cache invalidation when authentication states change.