MEDIUM dns rebindinggrapefirestore

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.

Frequently Asked Questions

Can DNS rebinding allow an attacker to bypass Firestore security rules?
DNS rebinding itself does not bypass Firestore security rules, but it can trick a user’s browser into making requests to a vulnerable Grape endpoint that then accesses Firestore with elevated credentials. Proper Firestore rules tied to user identity and strict CORS mitigate the impact.
Should I block private IP ranges at the Grape layer to prevent DNS rebinding?
While blocking private IP ranges at the network or load balancer is a good practice, in Grape you should also validate origins and avoid using the request’s origin for security decisions. Combine origin checks with robust Firestore security rules and avoid exposing service account credentials to client-side code.