Dns Rebinding in Grape with Firestore
Dns Rebinding in Grape with Firestore — how this specific combination creates or exposes the vulnerability
DNS rebinding is a client-side attack that abuses DNS and TTL behavior to make a browser-side script believe a different host resolves to the local network. In a Grape-based API, this can become relevant when the API serves web assets or embeds JavaScript that communicates with a Firestore backend. An attacker can register a domain, serve malicious JavaScript, and then flip the DNS record to point to the local IP of a machine that can reach the Firestore project (for example, a development server or an internal admin interface). Because the browser’s same-origin policy is based on the hostname, the script can now make requests that appear to originate from an allowed origin, bypassing CORS or origin checks that the Grape endpoint may have applied naively.
When combined with Firestore, the risk is not that Firestore itself triggers DNS rebinding, but that a vulnerable Grape endpoint may expose Firestore credentials or tokens to browser-side code, or may relay requests to Firestore without sufficient origin and authorization checks. For example, a Grape route that accepts a document ID and proxies read requests to Cloud Firestore using a service account key on the server can be invoked via a rebinding victim page. If the route does not validate the request origin or enforce strict CORS, the attacker’s page can invoke the route on behalf of the authenticated user, and the server’s Firestore calls may use elevated credentials. This can lead to unauthorized Firestore reads or writes depending on the permissions of the service account used by the Grape app.
Consider a Grape endpoint that retrieves a document directly from Firestore using an ID supplied by the client. If an attacker’s page calls this endpoint with crafted document IDs, and the endpoint does not enforce strict access controls, the attacker can probe internal document namespaces or exfiltrate data through the rebind-induced origin bypass. Even if the Grape API validates tokens, DNS rebinding can undermine trust in the origin header, making it important to apply defense-in-depth measures such as strict CORS policies, anti-CSRF tokens, and tight Firestore security rules that do not rely solely on the caller’s origin.
Firestore-Specific Remediation in Grape — concrete code fixes
To mitigate DNS rebinding risks in a Grape service that interacts with Cloud Firestore, focus on origin validation, strict CORS, and least-privilege Firestore rules. The following examples show concrete patterns you can adopt.
1. Strict CORS and Origin Validation in Grape
Configure Grape to allow only known origins and avoid wildcard origins. Combine this with a preflight check and explicit header validation.
require 'grape'
class API < Grape::API
format :json
before do
allowed_origins = ['https://example.com', 'https://app.example.com']
origin = request.env['HTTP_ORIGIN']
if allowed_origins.include?(origin)
headers['Access-Control-Allow-Origin'] = origin
else
error!('Forbidden', 403)
end
headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS'
headers['Access-Control-Allow-Headers'] = 'Authorization,Content-Type'
end
options '*' do
{} # CORS preflight response
end
resource :documents do
desc 'Get a document by ID'
params do
requires :id, type: String, desc: 'Document ID'
end
get do
# Firestore retrieval logic here
end
end
end
2. Firestore Security Rules — enforce ownership and avoid public access
Define Firestore rules that tie access to the authenticated user’s identity and document ownership, rather than relying on network-level checks. If you use custom tokens or a backend service to sign in users, include the user ID in the token and reference it in rules.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId}/documents/{documentId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
// Example for shared documents with explicit access list
match /shared/{documentId} {
allow read: if request.auth != null && request.auth.token.shared_documents[documentId] == true;
allow write: if request.auth != null && request.auth.token.can_write[documentId] == true;
}
}
}
3. Server-side Firestore access from Grape with service accounts
If your Grape backend accesses Firestore using a service account, keep the credentials server-side and never expose them to the browser. Use the Google Auth Library to obtain an authorized client and enforce strict input validation before using document IDs.
require 'grape'
require 'google/cloud/firestore'
class PrivateAPI < Grape::API
format :json
# Initialize Firestore client once (e.g., via lazy initialization)
def firestore
@firestore ||= Google::Cloud::Firestore.new(
project_id: ENV['FIRESTORE_PROJECT_ID'],
credentials: { private_key_id: ENV['FIRESTORE_PRIVATE_KEY_ID'], private_key: ENV['FIRESTORE_PRIVATE_KEY'].gsub('\\n', "\n"), client_email: ENV['FIRESTORE_CLIENT_EMAIL'], client_id: ENV['FIRESTORE_CLIENT_ID'], auth_uri: 'https://accounts.google.com/o/oauth2/auth', token_uri: 'https://oauth2.googleapis.com/token' }
)
end
helpers do
def safe_document_id(input)
# Allow only alphanumeric, dash, and underscore to avoid injection via document path
raise Grape::Errors::BadRequest unless input&.match?(\A[\w\-]+\z)
input
end
end
resource :documents do
desc 'Fetch a document (server-side Firestore access)'
params do
requires :id, type: String, desc: 'Document ID'
requires :user_id, type: String, desc: 'User ID for ownership check'
end
get do
document_id = safe_document_id(params[:id])
user_id = params[:user_id]
doc_ref = firestore.doc("users/#{user_id}/documents/#{document_id}")
if doc_ref.get.exists?
doc_ref.data
else
error!('Not found', 404)
end
end
end
end
4. Defense in depth: anti-rebinding and CSRF protections
Even with CORS, consider adding a same-site cookie policy and anti-CSRF tokens for state-changing operations. Avoid allowing the Origin header to control server-side behavior, and validate referer carefully if used. For sensitive operations, require re-authentication or use per-request nonces to ensure requests originate from your own frontend context.