Open Redirect in Grape with Mongodb
Open Redirect in Grape with Mongodb — how this specific combination creates or exposes the vulnerability
An open redirect in a Grape API that uses MongoDB for user or configuration data occurs when an endpoint accepts a user-supplied URL or host value and then redirects the client to that location without strict validation. If the application stores redirect URIs, return URLs, or referrer-like values in MongoDB, and later uses those values to construct a redirect response, an attacker who can inject or influence a document in the collection may be able to redirect users to arbitrary destinations.
For example, consider an OAuth or post-login flow where a client passes a redirect_to query parameter. The server may look up a default landing page from MongoDB based on the client identifier, then redirect to the user-supplied value if present. If the stored redirect value is not canonicalized and validated, an attacker who can modify a MongoDB document (e.g., via a compromised admin account or an IDOR/BOLA bug) can set a malicious redirect target that later users are sent to. Even without direct write access, an open redirect may be possible if the application trusts user-controlled fields when building the redirect URL, such as concatenating a hostname from a MongoDB document with a path provided by the client.
Grape routes typically handle parameters via request params and may construct Location headers using values from MongoDB. Because MongoDB documents can contain nested fields and flexible schemas, an attacker might leverage stored URLs or hosts to poison the redirect logic. Common patterns include storing callback URLs for integrations or multi-tenant configurations. If the API does not validate that the redirect target belongs to a trusted set of domains, the API effectively becomes a redirector for phishing or malware distribution sites. This becomes more impactful when combined with other findings such as IDOR or BOLA, where an attacker can read or alter documents that affect redirect behavior.
In a security scan, this pattern is flagged as an open redirect because the Location header is derived from data that is not strictly constrained to safe, expected values. Even if the redirect is client-side (e.g., JavaScript window.location), storing the destination in MongoDB and failing to enforce strict allowlists or canonicalization enables an attacker to control the final URL seen by users. The presence of MongoDB as a configuration or data store amplifies the impact because maliciously crafted documents can persist and affect multiple requests or tenants.
Mongodb-Specific Remediation in Grape — concrete code fixes
To remediate open redirect risks when using MongoDB with Grape, validate and canonicalize any redirect target before using it in a response Location header. Prefer server-side mapping of redirect keys to canonical URLs stored in MongoDB, and avoid directly using user-supplied values in Location headers. Below are concrete examples that demonstrate safe patterns.
Example 1: Whitelisted redirect keys
Store allowed redirect URIs or keys in MongoDB and resolve them on the server. Do not concatenate user input directly into the Location value.
require 'grape'
require 'mongo'
client = Mongo::Client.new(['127.0.0.1:27017'], database: 'myapp')
class RedirectAPI < Grape::API
helpers do
def canonical_redirect_key(key)
# Only allow known keys mapped to safe URIs in MongoDB
mapping = client[:redirect_mappings].find(key: key).first
mapping&.fetch('uri', nil)
end
end
get '/complete' do
key = params[:redirect_key]
uri = canonical_redirect_key(key)
if uri && uri.start_with?('https://trusted.example.com/')
redirect uri, 302
else
error!('Invalid redirect target', 400)
end
end
end
Example 2: Server-side resolved callback URLs
When clients register a callback, store the full URL in MongoDB after strict validation. On completion, use the stored value rather than re-accepting user input.
require 'grape'
require 'mongo'
require 'uri'
client = Mongo::Client.new(['127.0.0.1:27017'], database: 'myapp')
def safe_redirect_uri(input_url)
uri = URI.parse(input_url)
# Enforce HTTPS and a specific host
return nil unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
return nil unless uri.host == 'app.example.com'
# Ensure path is safe and does not contain unexpected redirects
uri.path = '/auth/callback' if uri.path.empty?
uri.to_s
rescue URI::InvalidURIError
nil
end
class OAuthAPI < Grape::API
post '/register_callback' do
raw = params[:uri]
safe = safe_redirect_uri(raw)
unless safe
error!('Invalid callback URI', 400)
end
client[:callbacks].insert_one(owner_id: current_user.id, uri: safe)
{ status: 'registered' }
end
get '/finish' do
doc = client[:callbacks].find(owner_id: current_user.id).first
redirect doc['uri'], 302 if doc
end
Example 3: Reject non-whitelisted hosts
When constructing redirects from MongoDB-stored values, enforce a strict host allowlist and avoid trusting the stored protocol-relative or fully-qualified URLs without checks.
require 'grape'
require 'mongo'
client = Mongo::Client.new(['127.0.0.1:27017'], database: 'myapp')
class SafeRedirectEndpoint < Grape::API
ALLOWED_HOSTS = %w[app.example.com ui.example.com]
get '/external' do
key = params[:key]
entry = client[:redirects].find(key: key).first
target = entry&.fetch('url', nil)
if target
uri = URI.parse(target)
if ALLOWED_HOSTS.include?(uri.host)
redirect target, 302
else
error!('Redirect host not allowed', 403)
end
else
error!('Not found', 404)
end
end
end