HIGH dangling dnsjwt tokens

Dangling Dns with Jwt Tokens

How Dangling Dns Manifests in Jwt Tokens

JSON Web Tokens (JWT) often carry header parameters that point to remote resources for key material. The most relevant are jku (JWK Set URL), kid when it is a URL, and less commonly x5u (X.509 certificate URL). When a service trusts these URLs without strict validation, an attacker who can control the referenced domain can supply fraudulent keys and sign arbitrary tokens that will be accepted as valid.

A dangling DNS condition occurs when a domain name still resolves (or previously resolved) to a resource that has been de‑provisioned, such as a deleted cloud storage bucket, a removed Heroku dyno, or a terminated Kubernetes service. If the domain is still registered but points to nothing, an attacker can re‑register the same name (or exploit a sub‑domain takeover) and host a malicious JWKS endpoint at the exact URL the application expects.

Typical vulnerable code path looks like this in Node.js using the popular jsonwebtoken library:

const jwt = require('jsonwebtoken');
function verifyToken(token) {
  // No algorithm or key restrictions – library will fetch JWKS from jku if present
  return jwt.verify(token, undefined, { complete: true });
}

If the token contains a header like { "alg":"RS256", "jku":"https://attacker-controlled.example.com/keys.json"}, the library will download the JWKS from the attacker‑controlled domain, verify the signature with the attacker’s key, and treat the token as legitimate. The same risk exists when kid is a full URL and the library dereferences it to retrieve a public key.

In Python with PyJWT a similar pattern appears when using jwt.decode with a custom key‑fetching function that does not validate the URL:

import jwt
import requests

def get_key(header, payload):
    jku = header.get('jku')
    if jku:
        resp = requests.get(jku)   # No URL allow‑list or DNS check
        return resp.json()['keys'][0]
    # fallback to secret
    return SECRET

jwt.decode(token, key=get_key, algorithms=['RS256'])

Both examples illustrate how a dangling DNS‑hosted JWKS can be injected, leading to token forgery, privilege escalation, or unauthorized API access.

Jwt Tokens-Specific Detection

middleBrick detects dangling DNS issues in JWT handling by performing the following steps during its unauthenticated black‑box scan:

  • Identifies API endpoints that return JWTs (e.g., login, token refresh, or any endpoint that emits a Authorization: Bearer token).
  • Parses the JWT header to extract jku, kid (when it looks like a URL), and x5u fields.
  • For each extracted URL, performs a DNS lookup (A/AAAA and CNAME records). If the domain resolves to an IP address that belongs to a known de‑provisioned cloud service (e.g., an AWS S3 bucket endpoint, Azure blob storage, GitHub Pages, Heroku, Netlify, etc.) or returns no records, the URL is flagged as a potential dangling DNS target.
  • Optionally attempts an HTTP GET to the URL; if the response contains a valid JWKS structure, the finding is upgraded to confirmed.

Because middleBrick runs twelve security checks in parallel, this DNS‑dangling check is executed alongside authentication, BOLA/IDOR, input validation, and the LLM/AI security probes, giving you a comprehensive view of the token‑related attack surface.

Example of invoking the scan from the CLI:

npx middlebrick scan https://api.example.com/auth/login

The output includes a property_authorization section (where JWT validation falls) with a finding such as:

{
  "id": "JWT-DANGLED-JKU",
  "name": "Dangling DNS in JWK Set URL",
  "description": "The token header contains jku=https://old‑bucket.example.com/keys.json which resolves to a de‑provisioned S3 bucket. An attacker can re‑register the domain and host malicious keys.",
  "severity": "high",
  "remediation": "Validate jku/kid URLs against an allow‑list of trusted domains or disable remote JWKS fetching if not required."
}

In the Web Dashboard the same finding appears under the "Authentication" category, allowing you to track the risk score over time and set alerts via email, Slack, or Teams (available on the Pro and Enterprise tiers).

Jwt Tokens-Specific Remediation

The most effective remediation is to eliminate reliance on remote key material unless absolutely necessary. If remote JWKS URLs must be used, enforce strict validation before trusting any key retrieved from those URLs.

1. Disable remote JWKS/kid URLs when not needed
If your application only uses a fixed set of signing keys (e.g., a self‑issued RS256 key), configure the JWT library to ignore jku and kid URL parameters.

Node.js (jsonwebtoken) example:

const jwt = require('jsonwebtoken');
const fs = require('fs');
const PUBLIC_KEY = fs.readFileSync('/etc/keys/public.pem');

function verifyToken(token) {
  // No options that allow jku/kid URL fetching
  return jwt.verify(token, PUBLIC_KEY, { algorithms: ['RS256'] });
}

Python (PyJWT) example:

import jwt

PUBLIC_KEY = open('/etc/keys/public.pem').read()

def verify_token(token):
    return jwt.decode(token, PUBLIC_KEY, algorithms=['RS256'])

2. Implement an allow‑list of trusted domains
When remote key fetching is required (e.g., integrating with an external IdP that publishes JWKS), restrict the URLs to known, controlled domains and verify that each domain resolves to an IP address you own.

Node.js example using jwks-rsa with a custom allow‑list:

const jwksRsa = require('jwks-rsa');

const client = jwksRsa({
  jwksUri: 'https://login.example.com/.well-known/jwks.json',
  // restrict to this host only
  timeout: 3000
});

function getKey(header, callback){
  if (header.jku) {
    // Reject any jku that is not the trusted IdP
    return callback(new Error('Unauthorized jku'));
  }
  client.getSigningKey(header.kid, (err, key) => {
    if (err) return callback(err);
    callback(null, key.getPublicKey());
  });
}

// Usage
jwt.verify(token, getKey, { algorithms: ['RS256'] });

Python example with PyJWT and requests that validates the host against an allow‑list:

import jwt
import requests
from urllib.parse import urlparse

TRUSTED_HOSTS = {'login.example.com', 'auth.partner.com'}

def fetch_jwks(url):
    parsed = urlparse(url)
    if parsed.hostname not in TRUSTED_HOSTS:
        raise ValueError(f'Untrusted JWKS host: {parsed.hostname}')
    resp = requests.get(url, timeout=5)
    resp.raise_for_status()
    return resp.json()

def get_key(header, payload):
    jku = header.get('jku')
    if jku:
        jwks = fetch_jwks(jku)
        # find matching kid
        for key in jwks['keys']:
            if key['kid'] == header.get('kid'):
                return jwt.algorithms.RSAAlgorithm.from_jwk(key)
    raise ValueError('Unable to retrieve signing key')

jwt.decode(token, key=get_key, algorithms=['RS256'])

3. Enforce TLS certificate pinning Ensure that any HTTPS request to a JWKS endpoint validates the server’s certificate chain and, if possible, pins the expected leaf certificate or public key to prevent man‑in‑the‑middle attacks that could arise even when DNS is not dangling.

By applying these controls—disabling unnecessary remote key fetching, restricting URLs to trusted hosts, and validating TLS—you eliminate the attack surface that dangling DNS exploits in JWT processing.

Frequently Asked Questions

Does middleBrick modify my API or block malicious tokens?
No. middleBrick only scans the unauthenticated attack surface and reports findings. It does not alter your API, block traffic, or automatically fix vulnerabilities. The output includes remediation guidance that you must apply in your code or configuration.
Can I use middleBrick to verify that my JWKS endpoint is not dangling before deploying?
Yes. By submitting your API’s base URL to middleBrick (via the Dashboard, CLI, or GitHub Action), the scanner will inspect any JWTs returned, extract jku/kid/x5u URLs, and check their DNS records. If a domain points to a de‑provisioned resource or lacks records, the finding will be reported so you can address it before release.