Cache Poisoning in Grape with Mutual Tls
Cache Poisoning in Grape with Mutual Tls — how this specific combination creates or exposes the vulnerability
Cache poisoning occurs when an attacker tricks a caching layer into storing malicious responses that are served to other users. In Grape, a Ruby API framework, this can happen when responses are cached based on insufficient or attacker-controlled inputs. When mutual TLS (mTLS) is enforced, the server validates client certificates, but this does not inherently protect cached responses if the cache key does not incorporate client identity or certificate attributes.
With mTLS, each request presents a client certificate. A naive caching strategy might key cache entries only on the request path and query parameters, ignoring the certificate or principal. If two different clients with different authorizations hit the same endpoint, a cached response from a privileged client could be served to an unprivileged client. This violates authorization boundaries and can lead to BOLA/IDOR via cache side channels.
Grape applications often use Rack-level caching (e.g., with rack-cache) or custom middleware. If the cache key omits the client certificate fingerprint or a mapped user identifier derived from the certificate, the cache becomes mutable across clients. Attackers can probe endpoints with varied certificates and observe whether responses are served from cache, inferring sensitive data or bypassing intended access controls.
Real-world considerations include ensuring that cache keys include stable, non-secret attributes derived from the client certificate, such as a subject or serial mapped to an internal user ID. Without this, even strong mTLS enforcement does not prevent cache poisoning in Grape. Findings from middleBrick scans can highlight missing key components and map risks to OWASP API Top 10 and PCI-DSS controls.
Mutual Tls-Specific Remediation in Grape — concrete code fixes
Remediation focuses on incorporating client identity into cache keys and ensuring cached responses are never shared across distinct authenticated principals. Below are concrete steps and code examples for Grape applications using mTLS.
1. Derive a cache key from the client certificate
Extract a stable identifier from the client certificate presented during the TLS handshake. In Rack middleware or within a Grape before block, map the certificate to a user or role and include it in the cache key.
# config/initializers/middleware.rb
class ClientCertCacheKey
def initialize(app)
@app = app
end
def call(env)
# Assuming the certificate is set by the mTLS-terminating proxy in env['SSL_CLIENT_CERT']
cert = env['SSL_CLIENT_CERT']
fingerprint = cert ? OpenSSL::X509::Certificate.new(cert).fingerprint.gsub(':', '') : 'no_cert'
# Set a custom header for downstream use (optional)
env['HTTP_X_CLIENT_FINGERPRINT'] = fingerprint
@app.call(env)
end
end
# In your Grape app or Rack builder
use ClientCertCacheKey
2. Use the fingerprint in Grape cache keys
When using rack-ttl-cache or a similar store, ensure the cache key includes the fingerprint so responses are segregated per client.
# In a Grape API class
class MyAPI < Grape::API
format :json
before do
def fingerprint = env['HTTP_X_CLIENT_FINGERPRINT']
# fallback if not set
fingerprint ||= 'anonymous'
@cache_key = ["v1", request.path_info, request.params.sort.to_s, "client:#{fingerprint}"].join(':')
end
get :sensitive_data do
# Read through the namespaced cache key
cache_key = @cache_key
result = cache_store.read(cache_key)
return result if result
# Compute and cache the response
data = compute_expensive_data(current_user)
cache_store.write(cache_key, data, expires_in: 5.minutes)
data
end
end
3. Avoid caching responses that contain user-specific data
If a response contains user-specific fields, either do not cache it or ensure per-user partitioning. For endpoints that must be cached globally, strip or generalize user-specific content before caching.
# Example: cache without user-specific fields
def public_data_for_cache(resource)
{
id: resource.id,
public_name: resource.public_name,
metadata: resource.public_metadata
}
end
4. Enforce mTLS at the API gateway and propagate identity
Ensure your gateway terminates mTLS and forwards the client identity (e.g., certificate fingerprint) in a header that Grape trusts. Validate this header in a before block to prevent spoofing.
# In Grape before block
before do
halt 403, { error: 'unauthorized' } unless env['HTTP_X_CLIENT_FINGERPRINT'].present?
# Optionally verify against a whitelist or mapping store
end
5. Use middleBrick to validate your cache-key design
Run middleBrick scans to confirm that your API’s cache behavior does not leak data across principals. The scans check for missing authorization context in caching and can map findings to compliance frameworks. With the Pro plan you can enable continuous monitoring so changes to caching logic trigger re-scans.