Insecure Deserialization in Rails with Cockroachdb
Insecure Deserialization in Rails with Cockroachdb — how this specific combination creates or exposes the vulnerability
Insecure deserialization occurs when an application processes untrusted serialized data and reconstructs objects without sufficient validation. In a Ruby on Rails app using CockroachDB as the backend, the database stores serialized or structured data—commonly as JSONB, XML, or Ruby Marshal—often read and written through model attributes. If Rails directly deserializes user-supplied input (for example, params stored in a serialized column or a custom YAML field) without strict type checks or schema validation, an attacker can supply crafted payloads that instantiate malicious objects during deserialization.
With CockroachDB, which behaves like PostgreSQL in terms of SQL standards, serialized data is typically stored in JSONB columns. While JSON itself is not inherently dangerous, Rails’ use of ActiveModel::Serialization and libraries like ActiveSupport::JSON.decode or custom YAML/Marshal parsing can introduce risks if the application reconstructs objects from attacker-controlled input. For instance, if a controller assigns a serialized field from a JSON payload directly to an ActiveRecord attribute and later instantiates or evaluates that data, it may trigger gadget chains that lead to remote code execution or privilege escalation.
The interaction between Rails and CockroachDB can expose these vulnerabilities when developers assume the database enforces schema safety. CockroachDB’s JSONB validates structure but does not prevent malicious object construction at the application layer. If Rails uses Marshal.load on data retrieved from CockroachDB without verifying its origin, an attacker who can influence stored JSON (via API input or compromised admin interfaces) may exploit known Ruby gem gadget chains. Common patterns include using URI::LDAP or Symbol creation to trigger side effects during deserialization, leading to unauthorized access or data manipulation. This risk is amplified when APIs accept and store serialized objects without enforcing strict allowlists on permitted classes or using safe parsing alternatives.
Cockroachdb-Specific Remediation in Rails — concrete code fixes
To mitigate insecure deserialization in Rails with CockroachDB, avoid deserializing user-controlled data using unsafe methods such as Marshal.load or YAML parsing. Prefer schema-validated JSON parsing and strict type casting. Below are concrete, CockroachDB-aware code examples.
1. Use strong parameters and JSON schema validation
Instead of relying on serialized columns that may invoke unsafe deserialization, validate incoming JSON against a schema and map attributes explicitly.
# app/models/order.rb
class Order < ApplicationRecord
# Store data as JSONB in CockroachDB; do not use serialized: true
attribute :metadata, :jsonb, default: {}
validates :metadata, json_schema: {
draft7: {
type: 'object',
properties: {
items: { type: 'array' },
total: { type: 'number' }
},
required: ['items']
}
}
end
2. Safe data parsing from CockroachDB JSONB fields
When reading JSONB data from CockroachDB, treat it as plain data structures and avoid eval-like operations.
# app/controllers/orders_controller.rb
def update
order = Order.find(params[:id])
# Parse JSONB safely; do not use YAML.safe_load or Marshal.load
incoming = JSON.parse(params[:order][:metadata])
# Use strong parameters to whitelist keys
order.update!(order_params(incoming))
end
def order_params(data)
data.permit(:total, items: [:name, :quantity])
end
3. Avoid Marshal.load on external data
If legacy data requires deserialization, restrict sources and use safer alternatives. Never call Marshal.load on input from CockroachDB or API payloads.
# app/services/data_importer.rb
class DataImporter
def self.safe_load_json(raw)
JSON.parse(raw, symbolize_names: true)
rescue JSON::ParserError => e
Rails.logger.error("Invalid JSON: #{e.message}")
{}
end
end
4. Use database constraints and Rails type casting
Leverage CockroachDB’s JSONB operators with Rails’ built-in type casting to enforce structure at the model level.
# db/migrate/20240101000000_add_metadata_to_orders.rb
class AddMetadataToOrders < ActiveRecord::Migration[7.0]
def change
add_column :orders, :metadata, :jsonb
add_check_constraint :orders,
"metadata ? 'items' AND jsonb_typeof(metadata->'items') = 'array'",
name: 'valid_order_metadata'
end
end