Mass Assignment in Grape with Hmac Signatures
Mass Assignment in Grape with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Mass Assignment in Grape APIs occurs when a client-supplied payload (typically JSON) maps directly to attributes on an Active Record or other model without explicit allowlisting. When Hmac Signatures are used for request authentication, developers may assume that because the request is authenticated, the parameters are safe. This assumption creates a critical gap: Hmac Signatures verify integrity and origin of the payload but do not limit which fields can be written to the model.
Consider a Grape endpoint that accepts user profile updates and uses an Hmac-SHA256 signature to validate the request body. The signature is computed over the raw payload and verified before the resource is located and updated. If the developer uses a method like params.except(:hmac_signature) and passes the remainder directly to an update call, an attacker who obtains or guesses a valid Hmac can forge requests that set any permitted attribute, including roles, admin flags, or association IDs. The signature validation passes, but mass assignment silently applies unauthorized changes.
This pattern is common when using Grape with Rails models or Sequel models and relying on parameter filters rather than explicit permit/strong parameters. The risk is especially pronounced when the Hmac is included as a header (e.g., X-API-Signature) and the developer focuses on verifying the signature while neglecting parameter whitelisting. Findings from scans often map this to OWASP API Top 10:2023 A05 (Broken Function Level Authorization) and CWE-915 (Improperly Controlled Modification of Object Attributes).
In scans, this appears as a BOLA/IDOR or Property Authorization finding when the endpoint modifies sensitive attributes without verifying authorization per property. The unauthenticated or low-privilege attacker can leverage mass assignment to escalate privileges or exfiltrate data indirectly by changing ownership pointers or administrative flags. Because the Hmac does not constrain the parameter set, the attack surface is wide even though authentication integrity checks pass.
Hmac Signatures-Specific Remediation in Grape — concrete code fixes
Remediation centers on two controls: strict signature verification and explicit parameter allowlisting. Do not treat Hmac validation as a substitute for input validation and authorization. Always parse the signature before processing the body, then filter parameters to a permitted set before any mass assignment.
Example: a Grape resource using Hmac-SHA256 with a shared secret. The signature is provided in the X-API-Signature header and covers the raw request body. The server recomputes the Hmac using the shared secret and compares it in constant time. After verification, only explicitly permitted attributes are forwarded to the model.
require 'openssl'
require 'json'
class App < Grape::API
format :json
before do
content_type :json
end
helpers do
def verify_hmac_signature(payload_body, received_signature, secret)
expected = OpenSSL::HMAC.hexdigest('sha256', secret, payload_body)
# Constant-time comparison to avoid timing attacks
ActiveSupport::SecurityUtils.secure_compare(
::Digest::SHA256.hexdigest(expected),
::Digest::SHA256.hexdigest(received_signature)
)
end
def permitted_params
# Explicitly allow only safe, expected fields
declared_params = params.slice(:first_name, :last_name, :email, :locale)
declared_params.permit(:first_name, :last_name, :email, :locale)
end
end
resource :profile do
desc 'Update profile with Hmac-signed request'
params do
requires :first_name, type: String, desc: 'First name'
requires :last_name, type: String, desc: 'Last name'
requires :email, type: String, desc: 'Email'
requires :locale, type: String, desc: 'Locale'
header :hmac_signature, type: String, desc: 'Hmac-SHA256 signature of raw body', required: true
end
put do
raw_body = request.body.read
# Rewind so downstream parsing can read again if needed
request.body.rewind
signature = headers['X-API-Signature']
secret = ENV.fetch('HMAC_SECRET') { 'fallback-secret-for-demo' }
unless verify_hmac_signature(raw_body, signature, secret)
error!('Invalid signature', 401)
end
# Parse JSON after signature verification
payload = JSON.parse(raw_body, symbolize_names: true)
# Use a permitted wrapper instead of passing params directly
safe_params = ActionController::Parameters.new(payload).permit(:first_name, :last_name, :email, :locale)
current_user.update!(safe_params)
{ status: 'ok' }
end
end
end
The key points:
- Read the raw body before JSON parsing to compute the Hmac over the exact bytes the sender used.
- Verify the signature with a constant-time comparison to prevent timing side channels.
- Do not use
paramsdirectly for updates; use a permit list that matches the business schema. - Keep the secret out of source code and rotate it periodically; store it in environment variables or a secrets manager.
If you use Grape validators, ensure they are strict and do not permit unknown keys. Combine this approach with framework-level strong parameters or an explicit permit list to enforce property-level authorization. Scans will flag endpoints that accept wide parameter sets even when Hmac is present, emphasizing that authentication integrity is not authorization.
Related CWEs: propertyAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-915 | Mass Assignment | HIGH |