HIGH dns rebindingfeathersjsfirestore

Dns Rebinding in Feathersjs with Firestore

Dns Rebinding in Feathersjs with Firestore — how this specific combination creates or exposes the vulnerability

DNS rebinding is a network-based attack where an attacker forces a victim’s browser to resolve a domain to an internal IP address, bypassing same-origin policies. In a Feathersjs application that integrates with Google Firestore, this becomes relevant when the server exposes administrative or configuration endpoints that interact with Firestore and are reachable from the client-side runtime.

Feathersjs is a JavaScript framework for real-time applications. When a Feathersjs server is configured to use Firestore directly from server-side service methods, the client never holds Firestore credentials. However, if the Feathersjs app also serves client-side JavaScript or API routes that dynamically construct Firestore references based on request parameters or hostnames, DNS rebinding can trick the client into sending requests that resolve to internal or unintended hosts. This can expose management endpoints, misroute Firestore calls, or allow an attacker to pivot from a public hostname to a local admin interface that shares the same origin due to permissive CORS or host-header handling.

In practice, a vulnerable Feathersjs + Firestore setup might include a service file that uses the Firestore SDK with a service account initialized from environment variables. If the server’s hostname is used to determine which Firestore project or collection to query, an attacker can register a domain that initially resolves to the public IP of the Feathersjs server and then rebind to an internal IP (e.g., 127.0.0.1 or 192.168.x.x). Because the client trusts the origin, the browser sends authenticated cookies or tokens, and the Feathersjs server may process the request using its elevated service-account permissions, leading to unintended Firestore reads or writes.

Key conditions that make this combination risky:

  • The Feathersjs app uses host-based routing or parameter-driven Firestore project/collection selection without strict hostname allowlisting.
  • Firestore initialization on the server uses broad IAM permissions that are unnecessary for routine client operations.
  • CORS or VPC firewall rules do not restrict inbound traffic to known, expected sources, allowing rebinding attempts from external networks.

To detect this class of issue, middleBrick scans the unauthenticated attack surface of a Feathersjs endpoint, including headers, CORS configuration hints, and observable runtime behavior when the hostname is manipulated. While middleBrick does not fix the implementation, its findings include remediation guidance to tighten hostname validation and Firestore scope.

Firestore-Specific Remediation in Feathersjs — concrete code fixes

Remediation focuses on removing dynamic host-based selection, tightening Firestore scope, and ensuring client requests cannot influence server-side resource resolution. Below are concrete, realistic code examples for a Feathersjs service that safely interacts with Firestore.

1. Avoid host-based or dynamic project selection

Do not derive the Firestore project ID or collection names from request hostnames or untrusted parameters. Instead, hardcode or securely inject configuration.

// services/messages/messages.service.js
const { Service } = require('feathersjs');
const { initializeApp, cert } = require('firebase-admin/app');
const { getFirestore } = require('firebase-admin/firestore');

// Initialize once with a fixed project ID
if (!initializeApp.apps.length) {
  initializeApp({
    credential: cert({
      projectId: process.env.FIREBASE_PROJECT_ID,
      clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
      privateKey: process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n')
    })
  });
}

const firestore = getFirestore();

class MessagesService extends Service {
  async find(params) {
    // Fixed collection; no dynamic project/collection from params
    const messagesRef = firestore.collection('messages');
    const snapshot = await messagesRef.limit(20).get();
    return snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
  }

  async get(id, params) {
    const doc = await firestore.collection('messages').doc(id).get();
    if (!doc.exists) {
      throw new Error('not-found');
    }
    return { id: doc.id, ...doc.data() };
  }
}

module.exports = function () {
  const app = this;
  app.use('/messages', new MessagesService());
};

2. Validate and restrict origins at the Feathersjs layer

Even though DNS rebinding operates at the DNS/HTTP layer, you can reduce impact by validating the Host header and enforcing strict CORS rules in Feathersjs.

// app.js
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const cors = require('@koa/cors');

const app = express(feathers());

// Explicitly allow only expected origins
app.configure(cors({
  origin: ['https://your-trusted-domain.com'],
  methods: ['GET', 'POST', 'PATCH', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true
}));

// Basic host validation middleware to reject unexpected Host headers
app.use((req, res, next) => {
  const allowedHosts = new Set(['api.your-trusted-domain.com']);
  const host = req.get('host')?.split(':')[0];
  if (!host || !allowedHosts.has(host)) {
    return res.status(400).send('Invalid host');
  }
  next();
});

app.use('/messages', require('./services/messages'));

// Error handler and other configs...

3. Use Firestore security rules and least-privilege IAM

On the server side, ensure the service account used by Feathersjs has the minimum required permissions. For client-facing operations, rely on Firestore security rules rather than broad IAM roles. The following is a rules snippet that complements server-side code by enforcing ownership-based access.

// firestore.rules
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /messages/{messageId} {
      allow read, write: if request.auth != null && request.auth.uid == request.resource.data.userId;
    }
  }
}

Note: Rules are enforced only when clients access Firestore directly. For server-side usage by Feathersjs, IAM scoping at the service account level is the primary control; combine both for defense in depth.

4. Sanitize and validate all inputs before using them in Firestore queries

Never pass user input directly into collection or document references. Use validation libraries and parameterized lookups.

const { body, validationResult } = require('express-validator');

class SafeMessagesService extends Service {
  async create(data, params) {
    const errors = validationResult(params);
    if (!errors.isEmpty()) {
      throw new Error('invalid-params');
    }
    // data.userId should come from authenticated request context, not user input
    const docRef = firestore.collection('messages').doc();
    await docRef.set({
      ...data,
      userId: params.user.id,
      createdAt: new Date()
    });
    return { id: docRef.id, ...data, userId: params.user.id };
  }
}

Frequently Asked Questions

Can DNS rebinding affect client-side Firestore access if the client uses the Firebase SDK directly?
If your Feathersjs app follows best practice and keeps service-account access server-side, client-side Firebase SDK usage should be limited to authenticated operations governed by Firestore rules. DNS rebinding primarily risks server endpoints that dynamically resolve internal hosts; mitigate by strict host validation and avoiding client-influenced resource selection.
Does enabling CORS in Feathersjs fully prevent DNS rebinding risks with Firestore?
CORS controls browser-origin policy enforcement but does not prevent DNS rebinding at the network level. It is one layer alongside host-header validation, secure IAM, and fixed configuration. Always combine CORS with server-side host checks and least-privilege service accounts.