Cache Poisoning in Phoenix
How Cache Poisoning Manifests in Phoenix
Cache poisoning in Phoenix applications typically occurs when user-controlled input influences cached responses without proper validation. This vulnerability allows attackers to manipulate cached content that will then be served to other users, potentially spreading malicious data or leaking sensitive information.
In Phoenix, cache poisoning often appears in controller actions that cache responses based on request parameters. Consider a Phoenix controller that caches API responses based on user IDs:
def show(conn, %{"id" => id}) do
# Vulnerable: user-controlled ID directly used in cache key
cache_key = "user_#{id}"
conn
|> cache_response(cache_key, ttl: 3600)
|> render("show.json", user: get_user(id))
endThe vulnerability here is that an attacker can craft specific IDs that cause the application to cache poisoned data. For example, if the get_user/1 function doesn't properly validate the ID format, an attacker might trigger database errors or unexpected behavior that gets cached and served to legitimate users.
Another common pattern in Phoenix involves caching based on query parameters without validation:
def search(conn, %{"query" => query}) do
# Vulnerable: unvalidated query used in cache key
cache_key = "search_#{query}"
conn
|> cache_response(cache_key, ttl: 1800)
|> render("results.json", results: search_database(query))
endPhoenix applications using Phoenix.LiveView can also be vulnerable when caching view states that incorporate user input. The following pattern is particularly dangerous:
def mount(%{"user_id" => user_id}, _session, socket) do
# Vulnerable: user ID used without validation
user = Accounts.get_user(user_id)
socket
|> assign(:user, user)
|> cache_view_state("user_#{user_id}")
endPhoenix's built-in caching mechanisms, including Phoenix.Cache and third-party libraries like Nebulex, can all be exploited if user input isn't properly sanitized before being used in cache keys or cached content.
Phoenix-Specific Detection
Detecting cache poisoning in Phoenix applications requires examining both the code patterns and runtime behavior. Start by auditing your Phoenix controllers and LiveView mounts for the following patterns:
Code Pattern Analysis:
# Search for these vulnerable patterns
for function <- functions_in_project() do
if function.uses?(cache_response) and function.has?(user_input_in_cache_key) do
IO.puts "Potential cache poisoning: #{function.name}"
end
endUsing middleBrick's CLI tool, you can scan your Phoenix API endpoints for cache poisoning vulnerabilities:
npm install -g middlebrick
middlebrick scan https://your-phoenix-app.com/api/users
middleBrick specifically tests for cache poisoning by attempting to manipulate cache keys and observing whether poisoned responses are served to subsequent requests. The scanner checks for:
- Unvalidated user input in cache keys
- Dynamic content caching without proper sanitization
- Cache key collisions that could allow data leakage
- Time-based cache poisoning where TTL values are manipulated
For Phoenix applications using Phoenix.LiveView, middleBrick's LLM/AI Security module can detect if cached view states contain system prompts or sensitive data that could be leaked through cache poisoning attacks.
Runtime Detection:
Implement logging to detect suspicious cache access patterns:
defmodule CacheAudit do
def log_cache_access(cache_key, user_id) do
if suspicious_cache_key?(cache_key) do
Logger.warn("Suspicious cache access: #{cache_key} by user #{user_id}")
end
end
defp suspicious_cache_key?(key) do
String.contains?(key, ["../", "../", "../../", "..%2F"]) or
String.contains?(key, ["eval(", "system(", "exec("])
end
endmiddleBrick's continuous monitoring in the Pro plan can automatically detect cache poisoning attempts by tracking cache key patterns and alerting when anomalous behavior is detected.
Phoenix-Specific Remediation
Remediating cache poisoning in Phoenix requires a defense-in-depth approach that validates input, sanitizes cache keys, and implements proper caching strategies. Here are Phoenix-specific fixes for common vulnerabilities:
Input Validation:
def show(conn, %{"id" => id}) do
# Validate ID format before using in cache
case Integer.parse(id) do
{user_id, ""} when user_id > 0 ->
cache_key = "user_#{user_id}"
conn
|> cache_response(cache_key, ttl: 3600)
|> render("show.json", user: get_user(user_id))
_ ->
conn
|> put_status(:bad_request)
|> json(%{error: "Invalid user ID format"})
end
endCache Key Sanitization:
def sanitize_cache_key(input) do
input
|> String.replace(~r/[^a-zA-Z0-9_-]/, "_")
|> String.slice(0, 200) # Prevent excessively long keys
end
def search(conn, %{"query" => query}) do
sanitized_query = sanitize_cache_key(query)
cache_key = "search_#{Base.encode16(:crypto.hash(:sha256, sanitized_query))}"
conn
|> cache_response(cache_key, ttl: 1800)
|> render("results.json", results: search_database(query))
endPhoenix.LiveView Protection:
defmodule MyAppWeb.UserLive do
use MyAppWeb, :live_view
def mount(%{"user_id" => user_id}, _session, socket) do
with {:ok, validated_id} <- validate_user_id(user_id),
{:ok, user} <- get_and_validate_user(validated_id) do
socket
|> assign(:user, user)
|> cache_view_state("user_#{validated_id}")
{:ok, socket}
else
{:error, reason} ->
socket
|> put_flash(:error, reason)
|> redirect(to: "/"))
{:ok, socket}
end
end
defp validate_user_id(id) do
case Integer.parse(id) do
{user_id, ""} when user_id > 0 -> {:ok, user_id}
_ -> {:error, "Invalid user ID"}
end
end
endSafe Caching Strategy:
defmodule MyApp.Caching do
def safe_cache_response(conn, cache_key, ttl, content) do
# Validate cache key format
validated_key = validate_cache_key(cache_key)
# Check for potential cache poisoning patterns
if contains_poisoning_payload?(content) do
Logger.warn("Potential cache poisoning attempt detected")
return conn
end
# Use a secure cache implementation
Phoenix.Cache.put(:api_cache, validated_key, content, ttl: ttl)
conn
end
defp validate_cache_key(key) do
key
|> String.replace(~r/[^a-zA-Z0-9_-]/, "_")
|> String.slice(0, 200)
end
defp contains_poisoning_payload?(content) do
# Check for suspicious patterns
String.contains?(content, ["system(", "eval(", "../", "..%2F"]) or
String.length(content) > 10_000 # Unusually large payload
end
endmiddleBrick's Pro plan includes continuous monitoring that can automatically detect if your Phoenix application's cache poisoning mitigations are working correctly, providing alerts when suspicious caching patterns are detected.