Insecure Deserialization in Rails with Api Keys
Insecure Deserialization in Rails with Api Keys — how this specific combination creates or exposes the vulnerability
Insecure deserialization occurs when an application accepts and processes serialized data without sufficient integrity checks, allowing an attacker to manipulate object graphs during reconstruction. In Ruby on Rails, common culprits include Marshal.load, YAML.load (with permitted classes not restricted), and unsafe use of ActiveSupport::Cache::Store or GlobalID when the backing store is reachable to an attacker. When API keys are handled in Rails via serialized payloads—such as storing key material in cookies, session stores, or cache entries—the combination of deserialization and key handling can expose secrets or enable privilege escalation.
Consider a Rails service that persists API keys in the session, which may be stored server-side (e.g., cache store) or client-side (e.g., cookie store). If the session store uses Marshal to serialize and deserialize data, and an attacker can tamper with the stored blob (via XSS, subdomain takeover, or log leakage), they may inject crafted serialized content. Upon deserialization, this can lead to arbitrary code execution or, at minimum, unintended object construction that reveals or modifies the API key. Even when API keys are passed as request headers or query parameters, Rails developers sometimes place them into serialized caches or background job arguments; unsafe deserialization of those artifacts can leak keys or allow an attacker to escalate privileges by forging admin-level objects.
Real-world patterns increase risk. For example, using YAML.safe_load without an explicit allowed class list or using the default unsafe YAML.load can permit instantiation of arbitrary Ruby classes. If an API key is embedded in a serialized YAML payload stored in a cache key or a cookie, an attacker who can control the payload may trigger deserialization during cache read or cookie parsing, leading to information disclosure or remote code execution. Similarly, Rails’ GlobalID—used to reference models across processes—relies on serialization; if an application uses GlobalID to reference objects that contain or wrap API keys, and the deserialization path is not restricted, attackers may manipulate references to access unauthorized resources.
An API security scanner such as middleBrick runs parallel checks including Input Validation and Data Exposure to surface these risks. By testing unauthenticated attack surfaces and inspecting OpenAPI specifications alongside runtime behavior, it can identify places where serialized data flows intersect with key handling. Findings typically highlight the use of unsafe deserialization routines and insufficient key isolation, mapping them to the OWASP API Top 10 and providing prioritized remediation guidance.
Api Keys-Specific Remediation in Rails — concrete code fixes
Remediation focuses on eliminating unsafe deserialization and hardening API key storage and transmission. Avoid passing sensitive API keys through serialized formats; if you must store keys server-side, keep them out of client-influenced stores and use secure, opaque references instead.
1. Avoid Marshal for user-influenced data
Never use Marshal.load on data that may originate from the client. If you need to serialize complex objects, prefer formats with strict schema validation such as JSON, and never reconstruct executable objects from untrusted input.
# Unsafe
user_data = cookies[:session_blob]
object = Marshal.load(Base64.strict_decode64(user_data)) # DANGER
# Safer: use signed, encrypted cookies and keep keys server-side
session[:api_key_ref] = key_id # store only a reference
2. Use safe YAML loading with explicit whitelisting
If YAML is used for configuration or metadata, always use YAML.safe_load with an explicit list of permitted classes.
# Unsafe
config = YAML.load(cached_blob) # DANGER
# Safe
allowed_classes = [Symbol, String, Integer, Array, Hash]
config = YAML.safe_load(cached_blob, permitted_classes: allowed_classes)
3. Secure API key handling in Rails controllers and initializers
Store API keys in server-side secrets and reference them by name, not by serialized payload. Use Rails credentials or environment variables, and avoid embedding keys in cookies or cache entries that may be serialized.
# config/master.key and config/credentials.yml.enc
# Access in code:
api_key = Rails.application.credentials.dig(:external_services, :api_key)
# Example initializer that avoids serialization of the key itself
ExternalService.configure do |config|
config.api_key = api_key
end
4. Prevent key leakage in logs and error messages
Ensure API keys are filtered from logs and error reports. Use Rails filters to scrub sensitive parameters.
# config/initializers/filter_parameter_logging.rb
Rails.application.config.filter_parameters += [:api_key, :access_token]
5. Use opaque identifiers and server-side mapping
Instead of storing the actual key in a cookie or cache entry, store a random identifier and map it to the key in a server-side store (e.g., database or vault). This minimizes the impact of deserialization attacks because the identifier alone is not useful.
# Example mapping record
class ApiKeyRef < ApplicationRecord
belongs_to :user
# token is a random UUID, not the actual key
end
# In controller:
ref = ApiKeyRef.find_by(token: cookies[:token])
if ref&.active?
api_key = ref.key_value # retrieved securely from vault
end