Insecure Deserialization in Grape with Mutual Tls
Insecure Deserialization in Grape with Mutual Tls
Insecure deserialization in a Grape API occurs when user-supplied data is deserialized in a way that allows an attacker to execute arbitrary code or alter application behavior. Grape is a REST-like microframework for Ruby that often uses JSON and sometimes Ruby Marshal for payloads. When combined with Mutual TLS (mTLS), where both client and server present certificates, the transport security may create a false sense of safety.
With mTLS enabled, the server validates client certificates before application logic runs. If the endpoint accepting deserialized data does not properly validate or sanitize the payload, an authenticated client (holding a valid certificate) can still send malicious serialized objects. Common frameworks like ActiveSupport::MessageEncryptor or libraries performing Ruby Marshal deserialization are frequent targets. Real-world findings from scans have included deserialization vectors tied to CVE-2015-3226 (Ruby YAML) and CVE-2013-0156 (Ruby Marshal), where crafted payloads can lead to remote code execution or sensitive data access.
In a black-box scan, middleBrick tests unauthenticated attack surfaces, but with mTLS the server may reject requests lacking a valid client cert. To test such endpoints securely, scans can be configured to present valid client certificates while checking whether deserialization occurs on the server side and whether input validation is applied. Even with mTLS, if the Grape app uses strong parameters or custom deserializers without strict type checks or sandboxing, an authenticated client can exploit these to tamper with object graphs, invoke dangerous methods, or leak internal state.
An example vulnerable Grape route might look like this, showing the risk when deserializing raw input without validation:
class VulnerableAPI < Grape::API
format :json
helpers do
def deserialize_payload(raw)
# Dangerous: deserializes untrusted input
Marshal.load(Base64.strict_decode64(raw))
end
end
post :process do
payload = deserialize_payload(params[:data])
{ result: payload.execute }
end
end
In this scenario, the presence of mTLS may lead developers to assume the channel is fully trusted, but the application remains vulnerable to deserialization attacks from authenticated clients. Proper remediation involves avoiding Marshal for untrusted data, using safe formats like JSON, and validating schemas and types before processing.
Mutual Tls-Specific Remediation in Grape
Remediation focuses on safe deserialization practices and ensuring mTLS is correctly integrated without creating a false security boundary. Avoid Ruby Marshal for untrusted payloads; prefer JSON with schema validation. When mTLS is required, enforce client certificate validation at the endpoint and treat authenticated clients as potentially malicious regarding payload content.
Use strong parameters and type checks, and consider using dedicated libraries that do not allow arbitrary class deserialization. Below are concrete, safe code examples for a Grape API with mTLS enabled.
Safe JSON Deserialization with mTLS Context
Define a helper that parses JSON strictly and validates required fields and types. This avoids the dangers of Marshal while working naturally in a mTLS environment where client identity is known but payload content must still be validated.
class SecureAPI < Grape::API
format :json
helpers do
def require_client_cert
# In a real deployment, mTLS info is available in request.client_cert
# This helper can be expanded to verify certificate attributes
error!('Client certificate required', 403) unless request.client_cert
end
def safe_params
declared_params = declared(params, include_missing: false)
# Validate presence and types
declared_params.require(:action).value(String)
declared_params.require(:user_id).value(Integer)
rescue Grape::Exceptions::Validation => e
error!(e.message, 400)
end
end
before { require_client_cert }
post :execute do
opts = safe_params
# Process based on a controlled set of actions, no deserialization of untrusted objects
{ status: 'ok', action: opts[:action], user_id: opts[:user_id] }
end
end
Example with Explicit Schema Validation (Optional Gem Integration)
If you need to handle structured payloads, use a schema validator like dry-validation or simple_ schema instead of deserialization. This ensures only expected data shapes are accepted.
require 'dry-validation'
module Validators
PayloadSchema = Dry::Validation.Schema do
required(:action).filled(value?: 'create' | 'update' | 'delete')
required(:user_id).filled(:int?, gt? 0)
optional(:metadata).hash?.maybe
end
end
class ValidatedAPI < Grape::API
format :json
helpers do
def validate_payload(input)
result = Validators::PayloadSchema.call(input)
result.success? ? result.to_h : (error!(result.errors.to_h, 400))
end
end
post :submit do
payload = validate_payload(params)
{ received: payload }
end
end
mTLS Configuration Notes
Ensure your server enforces client certificates and that Grape can access the certificate for authorization decisions. Do not rely on mTLS alone to validate business logic inputs. Combine mTLS with input validation, allowlists, and secure coding practices to reduce risk.