Cache Poisoning in Grape
How Cache Poisoning Manifests in Grape
Cache poisoning in Grape APIs occurs when attackers manipulate cache keys or inject malicious content that gets stored and served to other users. This vulnerability is particularly dangerous in Grape because it's designed to be a lightweight API framework that often sits behind caching layers like Rack::Cache, reverse proxies, or CDNs.
The most common Grape cache poisoning pattern involves unvalidated parameters that become part of cache keys. Consider this endpoint:
class API < Grape::API
get '/users/:id' do
user = User.find(params[:id])
present user, with: API::Entities::User
end
endIf this endpoint is cached at the proxy level, an attacker can craft requests with special characters in the :id parameter that manipulate how the cache key is generated. For example, using path traversal characters or SQL injection payloads that get reflected in cached responses.
Another Grape-specific manifestation occurs with content-type manipulation. Since Grape automatically handles content negotiation, an attacker might exploit this by:
class API < Grape::API
get '/search' do
query = params[:q]
results = SearchService.search(query)
present results, with: API::Entities::SearchResult
end
endIf the search endpoint doesn't properly validate the query parameter and the response is cached, an attacker could poison the cache with malicious content that gets served to subsequent users searching for similar terms.
Grape's parameter coercion feature can also introduce cache poisoning risks. When parameters are coerced to specific types without validation, unexpected values might lead to cache collisions or injection:
class API < Grape::API
params do
requires :user_id, type: Integer
end
get '/profile' do
user = User.find(params[:user_id])
present user, with: API::Entities::UserProfile
end
endAn attacker could exploit type coercion to generate cache keys that collide with legitimate user IDs, potentially exposing cached data across user boundaries.
Grape-Specific Detection
Detecting cache poisoning in Grape APIs requires examining both the application code and the runtime behavior. The first step is identifying endpoints that are potentially cacheable and examining their parameter handling.
middleBrick's API security scanner specifically tests for cache poisoning vulnerabilities by:
- Analyzing parameter validation patterns across all endpoints
- Testing for reflected parameters in cached responses
- Checking for content-type manipulation opportunities
- Verifying proper cache key generation
- Scanning for unsafe consumption of cached data
The scanner examines Grape's routing structure to identify endpoints that might be cached at the proxy level. For each endpoint, it tests with various parameter payloads to detect if malicious input can affect cached responses.
Here's how you can manually test for cache poisoning in Grape:
# Test 1: Parameter reflection
curl -v "http://api.example.com/users/1?debug=true"
# Check if debug=true parameter affects the cached response# Test 2: Content-type manipulation
curl -H "Accept: application/json" "http://api.example.com/search?q=test"
curl -H "Accept: application/xml" "http://api.example.com/search?q=test"
# Verify content-type doesn't affect cache key generationmiddleBrick's scanner goes beyond manual testing by automating these checks across all endpoints and providing a comprehensive risk assessment. The tool specifically looks for Grape patterns like:
- Unvalidated path parameters in cached endpoints
- Reflected query parameters in responses
- Inconsistent content-type handling
- Missing cache key normalization
- Unsafe parameter coercion
The scanner's findings include specific line numbers in your Grape API files where vulnerabilities are detected, making remediation straightforward.
Grape-Specific Remediation
Remediating cache poisoning in Grape requires a defense-in-depth approach that validates inputs, normalizes cache keys, and ensures proper content handling. Here are Grape-specific remediation strategies:
1. Strict Parameter Validation
class API < Grape::API
params do
requires :user_id, type: Integer, values: ->(val) { val.positive? }
optional :debug, type: Boolean, default: false
end
get '/users/:user_id' do
user = User.find(params[:user_id])
present user, with: API::Entities::User
end
endBy using Grape's built-in parameter validation with value constraints, you prevent malicious input from affecting cache behavior.
2. Cache Key Normalization
class API < Grape::API
helpers do
def normalized_cache_key
# Remove sensitive or manipulable parameters
safe_params = params.except(:debug, :sensitive_token)
Digest::SHA256.hexdigest(safe_params.to_json)
end
end
before do
# Set cache key based on normalized parameters
env['api.cache_key'] = normalized_cache_key
end
endThis approach ensures that cache keys are generated from a consistent, sanitized set of parameters, preventing cache collisions or poisoning.
3. Content-Type Safe Handling
class API < Grape::API
content_type :json, 'application/json'
content_type :xml, 'application/xml'
before do
# Force consistent content-type handling
header 'Vary', 'Accept'
header 'Cache-Control', 'public, max-age=3600'
end
get '/search' do
content_type :json
query = params[:q]
# Validate query before processing
if query =~ /[^a-zA-Z0-9 ]/
error!('Invalid search query', 400)
end
results = SearchService.search(query)
present results, with: API::Entities::SearchResult
end
endThis code ensures that content-type negotiation doesn't introduce cache poisoning vectors by explicitly setting headers and validating inputs.
4. Safe Consumption Patterns
class API < Grape::API
helpers do
def safe_user_data(user)
# Only include safe attributes in cached responses
{
id: user.id,
name: user.name,
email: user.email
}
end
end
get '/users/:id' do
user = User.find(params[:id])
present safe_user_data(user), with: API::Entities::SafeUser
end
endBy controlling exactly what data gets cached and served, you prevent cache poisoning from introducing malicious content into responses.