Insecure Deserialization in Grape with Bearer Tokens
Insecure Deserialization in Grape with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Insecure deserialization occurs when an application processes untrusted serialized objects without sufficient validation. In a Grape API that uses Bearer tokens for authentication, the combination of per-request token parsing and object deserialization can amplify the attack surface. Grape typically parses the Authorization header to extract the Bearer token and may use it to identify the current user or scope access. Separately, Grape may deserialize incoming payloads—such as JSON, MessagePack, or ActiveModel serializations—often with frameworks like ActiveModel::Serializer or custom deserializers.
If deserialization logic is too permissive (e.g., using Ruby’s Marshal.load on user-controlled data or unsafe YAML/JSON deserialization with custom object instantiation), an attacker can craft serialized content that executes code or alters runtime behavior when processed server-side. Even when Bearer tokens are validated for presence and format, an authenticated request carrying a malicious payload can trigger unsafe deserialization. This is especially relevant when token handling and deserialization are implemented in separate layers without a clear security boundary, since a valid Bearer token may bypass auth checks while a malicious object exploits deserialization to escalate impact.
Consider a Grape endpoint that accepts serialized settings or preferences:
class SettingsResource < Grape::API
before { authenticate! }
desc 'Update settings', requires: [:token]
params do
requires :data, type: String, desc: 'Serialized settings'
end
put '/settings' do
settings = Marshal.load(Base64.decode64(params[:data]))
current_user.update(settings: settings)
{ status: 'ok' }
end
end
Here, the token is validated by authenticate!, but the data parameter is deserialized with Marshal.load. An attacker who obtains or guesses a valid Bearer token (e.g., via leakage or token reuse) can supply a specially crafted Base64-encoded payload that, when deserialized, executes arbitrary Ruby code in the context of the application. This exemplifies how Bearer token authentication does not mitigate insecure deserialization; it may even enable it by ensuring the attacker’s request passes auth.
Other vectors include accepting serialized objects in JSON with custom class hints (e.g., "__type": "CustomClass") or using YAML for complex structures. If the API deserializes such inputs without strict type allowlisting or schema enforcement, it can lead to remote code execution or sensitive data manipulation. The risk is compounded when endpoints accept both authentication tokens and rich serialized data, as the token validates identity while the deserialization path remains unchecked.
Bearer Tokens-Specific Remediation in Grape — concrete code fixes
Remediation focuses on eliminating unsafe deserialization and ensuring Bearer token handling does not create a false sense of security. First, avoid deserializing user input with methods that can execute code, such as Ruby’s Marshal, YAML with custom types, or ActiveSupport::JSON.decode with unsafe object instantiation. Use strict, schema-based deserialization for simple data types only.
Second, keep token validation and data processing logically separated and enforce least privilege. Tokens should be validated early, but their validity must not skip input validation. Below is a secure pattern for Grape that decodes a Bearer token without trusting it for object deserialization:
class AuthTokenEntity
attr_reader :token, :user
def initialize(token)
@token = token
@user = User.find_by(api_token: token) # or JWT verification
end
def valid?
!user.nil?
end
end
class SettingsResource < Grape::API
helpers do
def current_user
@current_user ||= begin
token = request.env['HTTP_AUTHORIZATION']&.to_s&
.match(/^Bearer\s+(.+)$/)&
.try(:[], 1)
return nil unless token
AuthTokenEntity.new(token).user
end
end
def authenticate!
error!('Unauthorized', 401) unless current_user
end
end
desc 'Update settings', requires: [:token]
params do
requires :settings, type: Hash, desc: 'Strongly typed settings hash'
end
put '/settings' do
authenticate!
# Validate and whitelist keys instead of deserializing arbitrary objects
allowed = params[:settings].slice(:theme, :notifications, :timezone)
current_user.update(allowed)
{ status: 'updated' }
end
end
Key points in the fix:
- Token extraction is explicit and does not implicitly grant trust to the token for data handling.
- Deserialization is replaced with schema-based parameter whitelisting using
Hash#sliceor similar allowlisting techniques. - If complex objects are needed, use explicit constructors or mappers rather than generic deserializers.
- Consider using JSON Schema validation or a params wrapper to enforce types and structures without invoking unsafe deserialization.
For JWT-based Bearer tokens, verify signatures and claims without relying on the payload to dictate object types. If the API must accept serialized user input, enforce strict type constraints and avoid eval-like operations. MiddleBrick scans can detect dangerous deserialization patterns in your Grape endpoints and map findings to frameworks such as OWASP API Top 10 and CWE to prioritize remediation.