HIGH dns rebindingexpresscockroachdb

Dns Rebinding in Express with Cockroachdb

Dns Rebinding in Express with Cockroachdb — how this specific combination creates or exposes the vulnerability

DNS rebinding is a client-side attack where a malicious webpage resolves a hostname to an attacker-controlled IP, then switches the DNS answer to a different IP (often a private/internal address) within the same session. When an Express backend uses CockroachDB and trusts request-derived hostnames or connection parameters without validation, rebinding can redirect a victim browser’s request to an internal CockroachDB HTTP or database endpoint that would otherwise be unreachable from the internet.

Consider an Express route that forms a CockroachDB connection string from a request query, for example a multi-tenant identifier or a node hostname provided by the client:

app.get('/tenant/:id', (req, res) => {
  const host = req.query.host; // attacker-controlled
  const port = req.query.port || 8080;
  const url = `http://${host}:${port}/_status/health`;
  fetch(url).then(r => r.text()).then(data => res.send(data));
});

If the client supplies host=internal-host.cockroachdb.local, DNS rebinding can cause the browser to first resolve internal-host.cockroachdb.local to an attacker IP, then to the private IP of a CockroachDB node (e.g., 10.1.2.3). The victim’s browser sends the request with credentials or cookies for the backend, and the backend may forward them to the internal CockroachDB service. CockroachDB’s Admin UI or HTTP endpoints may then be invoked unintentionally, exposing cluster health, node details, or session-bound configuration data. Because the backend uses the attacker-supplied host and port, the usual network perimeter defenses (firewalls, private IPs) are bypassed via the victim’s browser, which is allowed to reach the internal address after rebinding.

In a real-world scenario, an Express service that dynamically builds a CockroachDB SQL connection string from request input compounds the issue. For example:

const { Client } = require('pg');
app.get('/query', (req, res) => {
  const { host, database, user, password } = req.query;
  const client = new Client({
    host,
    database: database || 'defaultdb',
    user,
    password,
    port: 26257
  });
  client.connect().then(() => client.query('SELECT 1')).then(r => res.json(r.rows));
});

An attacker can supply a rebinding hostname that resolves first to a public address they control, then to the CockroachDB node’s private IP. The victim’s cookies or proxy authentication may enable the backend to connect to the database with higher privileges, revealing cluster metadata or enabling unintended SQL interactions. Because the backend does not validate the host against an allowlist, the browser’s same-origin policy can be abused to reach internal endpoints that should never be exposed to external scripts.

To summarize, the combination of Express dynamically using request-supplied host/port values to reach CockroachDB, plus the browser’s ability to rebind hostnames after page load, creates a path where an attacker can pivot from a public-facing endpoint to internal database services, bypassing network-based protections. This is a server-side request forgery (SSRF) flavor enabled by DNS rebinding, where the victim’s browser is the pivot and the backend forwards requests to internal CockroachDB endpoints.

Cockroachdb-Specific Remediation in Express — concrete code fixes

Remediation focuses on never trusting client input for network destinations and enforcing strict allowlists. Avoid dynamic hostname or port parameters when connecting to CockroachDB from Express. Use environment configuration and parameterized queries instead.

1. Use fixed connection configuration. Define CockroachDB connection parameters via environment variables or configuration files, never from request data:

// BAD: dynamic host from request
// const { host } = req.query;
// const client = new Client({ host, port: 26257, ... });

// GOOD: fixed configuration
require('dotenv').config();
const { Client } = require('pg');
const client = new Client({
  host: process.env.COCKROACH_HOST || 'localhost',
  port: Number(process.env.COCKROACH_PORT) || 26257,
  database: process.env.COCKROACH_DB || 'defaultdb',
  user: process.env.COCKROACH_USER || 'root',
  password: process.env.COCKROACH_PASSWORD || '',
  ssl: {
    rejectUnauthorized: true
  }
});
await client.connect();

2. If multi-tenancy requires routing, use an allowlist map. Map tenant identifiers to predefined, validated connection configs:

const tenantConfigs = {
  'tenantA': { host: '10.0.1.10', ssl: { rejectUnauthorized: true } },
  'tenantB': { host: '10.0.2.20', ssl: { rejectUnauthorized: true } }
};
app.get('/tenant/:id/query', async (req, res) => {
  const cfg = tenantConfigs[req.params.id];
  if (!cfg) return res.status(400).send('invalid tenant');
  const client = new Client({ ...cfg, port: 26257, database: 'appdb', user: 'appuser', password: process.env.COCKROACH_APP_PWD });
  await client.connect();
  const result = await client.query('SELECT now()');
  await client.end();
  res.json(result.rows);
});

3. Avoid exposing database endpoints via HTTP fetches. If you must call an internal CockroachDB status endpoint, perform the call server-side with strict host validation and no browser cookies:

const allowedHosts = new Set(['status.cockroachdb.internal']);
app.get('/health/proxy', async (req, res) => {
  const host = req.query.host;
  if (!host || !allowedHosts.has(host)) return res.status(403).send('forbidden');
  // Use a server-side HTTP client with timeout and no cookies
  const response = await fetch(`http://${host}:8080/_status/health`, { headers: { 'x-internal': 'true' } });
  const text = await response.text();
  res.type('text').send(text);
});

4. Enforce TLS and certificate validation. CockroachDB should be accessed with TLS that validates server certificates to prevent downgrade or interception. In development, use strict CA or self-signed certs with explicit CA bundles:

const fs = require('fs');
const sslCa = fs.readFileSync('/path/to/ca.crt');
const client = new Client({
  host: process.env.COCKROACH_HOST,
  ssl: {
    ca: sslCa,
    rejectUnauthorized: true
  }
});

5. Harden Express against SSRF and rebinding. Add a DNS-safe list or block private IP ranges for any user-supplied values, and use non-browser contexts for internal calls:

function isPrivateIp(ip) {
  return /^10\.|^192\.168\.|^172\.(1[6-9]|2[0-9]|3[01])\./.test(ip);
}
app.get('/fetch-status', async (req, res) => {
  const host = req.query.host;
  // basic validation: reject if private or not in allowed set
  if (!host || isPrivateIp(new URL(`http://${host}`).hostname)) {
    return res.status(400).send('invalid host');
  }
  // server-side fetch without forwarding browser cookies
  const response = await fetch(`http://${host}:8080/health`, { headers: { 'x-forwarded-by': 'middlebrick-check' } });
  const txt = await response.text();
  res.type('text/plain').send(txt);
});

These patterns ensure CockroachDB connections are stable, predictable, and not subject to DNS rebinding or SSRF pivots from Express endpoints.

Frequently Asked Questions

Can DNS rebinding affect authenticated API calls to CockroachDB from Express?
Yes. If Express uses request-derived hostnames to form CockroachDB connections and does not validate them, a browser-initiated request can be rebinded to an internal address. The backend may forward credentials or cookies, allowing an attacker to reach internal endpoints via the victim’s authenticated context. Always use fixed configurations and avoid dynamic host inputs.
Does middleBrick detect DNS rebinding risks in Express services using CockroachDB?
middleBrick scans unauthenticated attack surfaces and tests input validation and SSRF classes of issues. It can surface findings related to dynamic host usage and improper trust in request-derived parameters that may enable rebinding or SSRF against CockroachDB endpoints. Use the CLI (middlebrick scan ) or the Dashboard to review categorized findings and remediation guidance.