HIGH dns cache poisoningflaskbasic auth

Dns Cache Poisoning in Flask with Basic Auth

Dns Cache Poisoning in Flask with Basic Auth — how this specific combination creates or exposes the vulnerability

DNS cache poisoning (also known as DNS spoofing) occurs when an attacker inserts false DNS records into a resolver’s cache, causing a client to be directed to a malicious host. In a Flask application that relies on Basic Authentication, this can have compounding effects because the client may unknowingly send credentials to an attacker-controlled endpoint.

Consider a Flask service that authenticates users via HTTP Basic Auth and then makes outbound HTTP requests to a backend API using a hostname that is resolved via DNS. If a resolver used by the client or server returns a poisoned record, the outbound request from Flask may be sent to an IP controlled by an attacker. Because Flask includes the Authorization header with the request, credentials can be exfiltrated or the attacker can perform actions on behalf of the client depending on the trust model.

The risk is not that Flask itself caches DNS in an unsafe way by default; the issue arises from the runtime environment’s DNS resolution combined with how the application uses Basic Auth. For example, if Flask makes requests with the requests library to a URL like https://api.internal.example.com/resource, and api.internal.example.com resolves to a malicious IP, the Authorization header is still sent to that malicious server. An attacker can then harvest credentials or inject false responses, leading to further compromise.

In a black-box scan, middleBrick tests for SSRF and external network interactions that could be influenced by DNS manipulation. When Basic Auth is present, findings related to insufficient hostname validation or missing certificate checks become higher severity because credentials are at risk of being sent to unintended endpoints.

Basic Auth-Specific Remediation in Flask — concrete code fixes

Remediation focuses on ensuring that Basic Auth credentials are only sent to verified, expected endpoints and that hostname resolution is not left to uncontrolled external resolvers. Below are concrete Flask patterns to reduce risk when using HTTP Basic Auth.

1. Pin hostnames with requests using a custom adapter or verify against an allowlist

Instead of relying solely on DNS at runtime, validate the hostname or IP against an allowlist before making outbound calls. This prevents a poisoned DNS entry from redirecting credentials.

import requests
from requests.adapters import HTTPAdapter

class HostnameVerificationAdapter(HTTPAdapter):
    def __init__(self, allowed_hosts=None, *args, **kwargs):
        self.allowed_hosts = set(allowed_hosts or [])
        super().__init__(*args, **kwargs)

    def send(self, request, **kwargs):
        if request.url.lower().startswith('https://'):
            hostname = request.url.split('/')[2].split(':')[0]
            if hostname not in self.allowed_hosts:
                raise ConnectionError(f'Hostname not allowed: {hostname}')
        return super().send(request, **kwargs)

session = requests.Session()
session.mount('https://', HostnameVerificationAdapter(allowed_hosts=['api.trusted.example.com']))
session.get('https://api.trusted.example.com/resource', auth=('user', 'password'))

2. Use certificate pinning or strict TLS verification

Ensure that SSL/TLS verification is enabled and consider certificate pinning for critical services. This prevents an attacker with a poisoned DNS entry from using a valid but unauthorized certificate unless they also possess the pinned cert or key.

import requests

response = requests.get(
    'https://api.internal.example.com/resource',
    auth=('user', 'password'),
    verify='/path/to/ca-bundle.pem'  # or a pinned cert file
)

3. Avoid embedding credentials in URLs; use headers instead

Credentials in URLs can be logged more easily and may be more susceptible to exposure via DNS-based redirection. Use the Authorization header explicitly and avoid constructing URLs with embedded user:pass.

from flask import Flask, request
import requests

app = Flask(__name__)

@app.route('/proxy')
def proxy():
    username = request.authorization.username
    password = request.authorization.password
    # Explicit header usage, no credentials in URL
    resp = requests.get(
        'https://api.internal.example.com/resource',
        headers={'Authorization': f'Basic {b64encode(f"{username}:{password}".encode()).decode()}'}
    )
    return resp.content

4. Validate and sanitize target hostnames from user input

If your Flask app accepts hostnames or URLs from users, strictly validate them to prevent open redirects or SSRF that could bypass DNS expectations.

from urllib.parse import urlparse

def is_safe_hostname(url, allowed_domains):
    parsed = urlparse(url)
    return parsed.hostname and any(parsed.hostname.endswith(d) for d in allowed_domains)

if is_safe_hostname(user_url, ['example.com', 'trusted.org']):
    requests.get(url, auth=('user', 'password'))

Frequently Asked Questions

Does DNS cache poisoning require authentication to exploit in a Flask app using Basic Auth?
It depends on the architecture. If Flask makes outbound authenticated requests to a hostname that can be poisoned, credentials can be stolen even when Flask itself does not cache DNS. The vulnerability is in the combination of unresolved hostnames and transmitted Basic Auth headers.
Can middleBrick detect risks related to DNS cache poisoning in Flask with Basic Auth?
middleBrick scans for SSRF and network interactions that could be influenced by DNS manipulation. In scans that involve authentication, findings related to hostname validation and certificate checks are surfaced with remediation guidance to help reduce the risk of credentials being redirected.