CRITICAL insecure deserializationrailsmongodb

Insecure Deserialization in Rails with Mongodb

Insecure Deserialization in Rails with Mongodb — how this specific combination creates or exposes the vulnerability

Insecure deserialization occurs when an application accepts and processes serialized data from untrusted sources. In a Ruby on Rails stack using MongoDB as the primary datastore, this typically involves the application deserializing user-controlled payloads (e.g., params, cookies, or HTTP bodies) into Ruby objects before storing or acting on them. MongoDB Ruby driver and Object Document Mapper (ODM) libraries like Mongoid can store complex Ruby types such as Date, Time, or custom classes; this flexibility becomes dangerous when deserialization of attacker-supplied data is used to instantiate objects.

For example, Rails params are often deserialized from JSON or form data. If the app passes raw params to a method that reconstructs Ruby objects (e.g., using YAML.safe_load without strict whitelisting or using libraries that invoke eval-like behavior), an attacker can craft payloads that execute code during deserialization. In a MongoDB context, if the application stores user input into documents and later reconstructs those documents using unsafe deserialization (e.g., BSON decoding that allows arbitrary class instantiation), the attacker may achieve remote code execution (RCE) or tamper with server-side logic. Common patterns include storing serialized objects in a field and then loading them back with YAML.load or similar unsafe methods, bypassing Rails’ permitted parameters and model validations.

Real-world attack patterns mirror known CVEs and OWASP API Top 10 risks: injection of malicious class definitions that execute code when the object is instantiated or when callbacks (e.g., after_find) run. Because MongoDB stores rich document structures, attackers can embed serialized Ruby objects or use type confusion to escalate privileges, bypass authentication checks, or achieve persistence. The risk is compounded when endpoints that expose deserialization functionality do not enforce strict input validation, rely on default BSON decoders that accept Ruby-type hints, or allow client-supplied class names in JSON/MessagePack payloads.

Mongodb-Specific Remediation in Rails — concrete code fixes

Apply strict deserialization controls and avoid unsafe methods when working with MongoDB in Rails. Prefer JSON parsing over YAML, and never pass raw user input to deserialization routines that can instantiate arbitrary Ruby classes.

1. Use safe JSON parsing and strong parameters

Rails’ built-in JSON parser does not invoke arbitrary class deserialization. Ensure your controllers use strong parameters and avoid custom deserializers that rely on YAML or Marshal.

class ProfilesController < ApplicationController
  def create
    # Safe: JSON parsing produces plain hashes/arrays, no arbitrary class instantiation
    data = JSON.parse(params[:profile_data], symbolize_names: true)
    profile = current_user.profiles.new(data.slice(:bio, :preferences))
    if profile.save
      render json: profile, status: :created
    else
      render json: { errors: profile.errors }, status: :unprocessable_entity
    end
  end
end

2. Avoid unsafe YAML.load; use safe_load with explicit classes

If you must deserialize YAML (rare in typical Rails APIs), always use YAML.safe_load with a whitelist of permitted classes and never allow user input to specify class names.

# config/initializers/yaml.rb
YAML_ALLOWED_CLASSES = [Date, Time, TrueClass, FalseClass, NilClass, String, Integer, Float, Array, Hash]

# In a service object that processes stored BSON/YAML fields
class DeserializeProfile
  def self.call(serialized)
    # Only permit specific classes; reject arbitrary Ruby objects
    YAML.safe_load(serialized, permitted_classes: YAML_ALLOWED_CLASSES)
  end
end

# Usage in a model or controller
profile_data = DeserializeProfile.call(params[:legacy_payload])

3. Configure MongoDB/ODM to reject Ruby-type hints

When using Mongoid, disable loading of Ruby-specific type hints (e.g., _type) that enable arbitrary class instantiation from BSON. Validate and cast fields explicitly instead of relying on automatic deserialization.

# config/mongoid.yml
production:
  options:
    # Avoid automatic type casting that may instantiate unexpected classes
    allow_dynamic_fields: false

# In a Mongoid model, prefer explicit casting and validation
class PaymentLog
  include Mongoid::Document
  field :event_type, type: String
  field :metadata, type: Hash, default: {}

  # Custom setter to reject unexpected class keys
  def metadata=(value)
    raise ArgumentError, "metadata must be a plain hash" unless value.is_a?(Hash)
    super(value.stringify_keys)
  end
end

# Safe retrieval without invoking eval-like behavior
raw = collection.find(filter).first
# Parse BSON carefully; do not allow raw Ruby object reconstruction
plain = { event_type: raw["event_type"], metadata: raw["metadata"] }

4. Validate and sanitize before persistence

Treat any field that may have been deserialized as hostile. Normalize and validate before saving to MongoDB. For example, if your API accepts JSON that could be transformed into a serialized Ruby object, ensure the schema and permitted keys are strictly enforced.

class Api::V1::DocumentsController < ApplicationController
  def create
    doc_params = params.require(:document).permit(:title, :body, tags: [])
    # No YAML/Marshal loading; store only whitelisted fields
    doc = current_user.documents.create!(doc_params)
    render json: doc, status: :created
  end
end

5. Use BSON decoding options that restrict class instantiation

When interacting with MongoDB drivers directly, configure BSON to avoid loading Ruby-specific type metadata that can trigger unsafe deserialization.

# Safe BSON decoding in a service object
require "bson"

raw_bson = params[:bson_payload] # raw binary or base64 as needed
# Decode without allowing arbitrary Ruby class instantiation
decoder = BSON::Decoder.new(allow_array: true, allow_hash: true)
# Do NOT pass allow_ruby: true
plain = decoder.decode(raw_bson)

# Use plain hashes/arrays only; map to models explicitly
attrs = plain.slice("title", "count")
Record.create!(attrs)

Frequently Asked Questions

Does middleBrick detect insecure deserialization patterns in Rails APIs using MongoDB?
Yes. middleBrick runs 12 security checks in parallel, including Input Validation and Unsafe Consumption, to identify insecure deserialization risks in Rails APIs that interact with MongoDB. Findings include severity, remediation guidance, and mappings to frameworks such as OWASP API Top 10.
How can I safely handle serialized fields when using Mongoid with MongoDB?
Avoid YAML.load or Marshal for user-supplied data. Use JSON parsing with strict parameters, apply YAML.safe_load with a strict permitted_classes list, disable dynamic fields in Mongoid, and validate/sanitize all input before persisting to MongoDB. middleBrick’s scans can help identify unsafe patterns and provide prioritized remediation steps.