Dangling Dns in Hapi with Basic Auth
Dangling Dns in Hapi with Basic Auth — how this specific combination creates or exposes the vulnerability
A dangling DNS reference occurs when an application resolves a hostname to an internal or unexpected IP address after the hostname is looked up, typically because the DNS record changes or is not validated after resolution. In Hapi, this risk can emerge when route handlers use a hostname configured via environment variables or a config file and perform repeated DNS lookups, especially when combined with Basic Authentication credentials that are transmitted with each request.
When Basic Auth is used in Hapi, the server often wraps routes with an authentication strategy that validates credentials on every incoming request. If during that validation or within a downstream handler the server or a plugin resolves a hostname (for example to call a backend service or to validate resource ownership), an attacker who can influence the target hostname or poison DNS may redirect the connection to a malicious host. Because Basic Auth sends credentials with every request, a dangling DNS outcome may expose those credentials to an unintended endpoint or allow an attacker to intercept or manipulate authenticated traffic.
Consider a scenario where a Hapi route calls an external user service using a hostname like users.internal.example.com. If that DNS name resolves differently after a change, or if the application caches a stale address incorrectly, the route may send authenticated requests to a rogue server. The combination of Basic Auth and dynamic DNS resolution means credentials are reliably sent with each request, increasing the impact of a dangling DNS condition. This pattern is relevant to checks such as SSRF and Inventory Management in middleBrick’s 12 security checks, which look for unauthenticated endpoints and unexpected network interactions.
In an OpenAPI/Swagger spec analysis, a dangling DNS issue may not be directly visible in the YAML or JSON, but it can be inferred when external hostnames are referenced in servers or callbacks and runtime probing reveals that those names resolve to unexpected addresses. middleBrick’s runtime checks compare spec definitions to observed behavior, flagging cases where unauthenticated access to endpoints or SSRF-like patterns appear alongside external hostname references.
To reduce risk, validate and pin hostnames, avoid relying on mutable DNS for critical routing, and ensure that credentials are not automatically forwarded to dynamically resolved origins. Tools like middleBrick’s CLI can scan an unauthenticated attack surface and highlight related findings such as BOLA/IDOR or SSRF that may accompany DNS-related weaknesses.
Basic Auth-Specific Remediation in Hapi — concrete code fixes
Remediation focuses on removing reliance on mutable DNS for authentication decisions and ensuring that Basic Auth credentials are only accepted from trusted, explicitly defined endpoints. Below are concrete Hapi examples that demonstrate secure patterns.
1. Use IP or fixed hostname with strict validation
Instead of resolving a dynamic hostname on each request, resolve once at startup and enforce a fixed expected value. If you must use DNS, validate the resolved address against an allowlist.
// server.js
const Hapi = require('@hapi/hapi');
const dns = require('dns').promises;
const init = async () => {
const server = Hapi.server({
port: 3000,
host: 'localhost'
});
// Resolve once at startup
const resolved = await dns.resolve4('users.internal.example.com');
const expectedIp = '192.0.2.10'; // pinned or allowlisted
if (!resolved.includes(expectedIp)) {
throw new Error('Unexpected DNS resolution for users service');
}
server.auth.scheme('custombasic', (server) => ({
authenticate(request, h) {
const credentials = request.auth.credentials;
// validate credentials against a fixed backend at expectedIp
return h.authenticated({ credentials });
}
}));
server.auth.strategy('simple', 'custombasic');
server.route({
method: 'GET',
path: '/profile',
config: {
auth: 'simple',
handler: (request, h) => {
// call backend at fixed, validated address
return `Authenticated user: ${request.auth.credentials.username}`;
}
}
});
await server.start();
console.log('Server running on %s', server.info.uri);
};
process.on('unhandledRejection', (err) => {
console.error(err);
process.exit(1);
});
init();
2. Reject requests when hostname changes at runtime
If you must accept a target hostname from input, reject the request when the hostname does not match an expected pattern or when DNS resolution yields an unexpected category (e.g., private IPs). Do not automatically follow redirects to a new hostname derived from user input.
// validateRoute.js
const Hapi = require('@hapi/hapi');
const url = require('url');
const allowedHost = 'api.trusted.example.com';
const validateTargetHost = (host) => {
const parsed = new url.URL(host);
return parsed.hostname === allowedHost;
};
const server = Hapi.server({ port: 4000, host: 'localhost' });
server.auth.scheme('proxyauth', (server) => ({
authenticate(request, h) {
const target = request.query.target;
if (!target || !validateTargetHost(target)) {
return h.authenticated({ credentials: { scope: 'trusted' } });
}
return h.unauthenticated({ message: 'Invalid target host' });
}
}));
server.auth.strategy('proxy', 'proxyauth');
server.route({
method: 'GET',
path: '/proxy',
config: {
auth: 'proxy',
handler: (request, h) => {
// Safe to proceed because host was validated
return { status: 'proxied to trusted host' };
}
}
});
module.exports = server;
3. Prefer token-based auth over sending credentials with every DNS-dependent call
Basic Auth sends credentials with every request, which increases exposure if DNS resolution is involved. Where possible, use short-lived tokens and avoid embedding credentials in URLs or headers that traverse unresolved hostnames.
// tokenRoute.js
const Hapi = require('@hapi/hapi');
const server = Hapi.server({ port: 5000, host: 'localhost' });
server.auth.scheme('token', (server) => ({
authenticate(request, h) {
const token = request.headers.authorization?.split(' ')[1];
if (!token || token !== process.env.API_TOKEN) {
return h.unauthenticated({ message: 'Invalid token' });
}
return h.authenticated({ credentials: { scope: 'api' } });
}
}));
server.auth.strategy('token-auth', 'token');
server.route({
method: 'POST',
path: '/action',
config: {
auth: 'token-auth',
handler: (request, h) => {
return { status: 'action executed' };
}
}
});
await server.start();
Operational practices
- Pin DNS records or use IP addresses for critical backend calls.
- Monitor DNS changes and automate alerts if unexpected resolutions occur.
- Ensure that Basic Auth is only used over TLS to protect credentials in transit.
- Use middleBrick’s CLI to scan your API and review findings related to authentication and external calls.