Cache Poisoning in Rocket (Rust)
Cache Poisoning in Rocket with Rust — how this specific combination creates or exposes the vulnerability
Cache poisoning in the Rocket framework when using Rust typically arises from how dynamic values are incorporated into cache keys or cached responses. Rocket does not automatically key cached fragments by the full request context, so if a developer includes user-controlled data (such as headers, query parameters, or cookies) directly into the cache key without validation or normalization, an attacker can manipulate those inputs to poison the cache.
Consider a route that caches responses based on a query parameter without normalizing or excluding sensitive inputs:
#[get("/products")]
fn list_products(category: Option<String>) -> String {
// If category is used directly in a cache key or response,
// an attacker can supply ?category=..%2F.. or repeated values
// to cause cache key duplication or overwrite entries.
format!("Products for: {:?}", category)
}
An attacker can send repeated or encoded values (e.g., ?category=foo&category=bar or path traversal–style inputs) that lead to unexpected cache entries being stored or retrieved. Because Rocket compiles routes to static paths at build time, developers might assume inputs are constrained, but query and header parsing remain under application control. If the application caches based on these untrusted inputs without canonicalization, the poisoned cache can return malicious or incorrect data to other users.
Additionally, if caching is implemented via response guards or custom caching logic that does not factor the full request identity (including headers like Host or Origin), an attacker who can influence any part of the request can cause a cached response intended for one context to be served to another. This can lead to reflected data or privilege confusion, especially when combined with route variants or subdomain routing in Rocket.
Rocket’s strong type system and request guards reduce some classes of injection, but they do not eliminate risks from improperly designed cache keys. Developers must ensure that any data used to differentiate cache entries is validated, normalized, and scoped to the intended tenant or user context. Subdomain-based routing or host headers used in cache decisions require careful handling to avoid host header poisoning leading to cache confusion across virtual hosts.
Rust-Specific Remediation in Rocket — concrete code fixes
To remediate cache poisoning in Rocket with Rust, ensure cache keys are built from canonical, validated, and scoped inputs only. Avoid using raw user input directly in cache keys. Use structured data and explicit serialization to guarantee consistent key generation.
Example of a safe approach using a hashed, canonicalized key that excludes mutable headers and focuses on validated parameters:
use rocket::serde::json::Json;
use rocket::State;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
#[derive(serde::Deserialize)]
struct ProductQuery {
category: String,
// include tenant or account id if applicable
tenant_id: u64,
}
fn canonical_key(query: &ProductQuery) -> u64 {
let mut hasher = DefaultHasher::new();
query.category.to_lowercase().hash(&mut hasher);
query.tenant_id.hash(&mut hasher);
hasher.finish()
}
#[get("/products")]
fn list_products(
query: rocket::serde::json::Result<Json<ProductQuery>>
) -> String {
match query {
Ok(payload) => {
let key = canonical_key(&payload);
// Use `key` in your caching layer; it is deterministic and safe.
format!("Cached products for tenant {} category hash {:#x}", payload.tenant_id, key)
}
Err(_) => String::from("Invalid request"),
}
}
For response caching, prefer framework-managed caching where possible and scope cached responses by tenant or account. If implementing custom caching, include the tenant or user identifier in the cache key and normalize inputs (e.g., lowercasing strings, sorting repeated query parameters) to ensure consistent keys.
When using subdomain or host-based routing, bind the host or subdomain to a known set of values at route registration and avoid using raw host headers in cache decisions. If host-based routing is necessary, validate and sanitize the host before using it in any cache key or cache lookup:
fn safe_host_key(host: &str) -> Option<String> {
let allowed = ["api.example.com", "app.example.com"];
if allowed.contains(&host) {
Some(host.to_string())
} else {
None
}
}
By combining strict input validation, canonical key construction, and explicit scoping, you mitigate cache poisoning risks while retaining the performance benefits of caching in Rocket applications written in Rust.