HIGH cache poisoningphoenix

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))
end

The 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))
end

Phoenix 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}")
end

Phoenix'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
end

Using 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
end

middleBrick'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
end

Cache 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))
end

Phoenix.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
end

Safe 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
end

middleBrick'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.

Frequently Asked Questions

How does middleBrick detect cache poisoning in Phoenix applications?
middleBrick scans Phoenix API endpoints by sending crafted requests with manipulated cache keys and parameters. It then verifies whether the poisoned responses are served to other users or requests. The scanner tests for unvalidated user input in cache keys, dynamic content caching without sanitization, and cache key collision vulnerabilities. For Phoenix.LiveView applications, middleBrick's LLM/AI Security module additionally checks for cached view states containing sensitive data or system prompts that could be leaked through cache poisoning.
Can cache poisoning in Phoenix affect LiveView applications?