Dns Cache Poisoning in Flask
How Dns Cache Poisoning Manifests in Flask
DNS cache poisoning (also known as DNS spoofing) occurs when an attacker injects false DNS records into a resolver's cache, causing clients to resolve legitimate domain names to malicious IP addresses. In Flask applications, this vulnerability typically manifests when the application makes outbound HTTP requests to external services using domain names that an attacker has poisoned, rather than through direct inbound requests to the Flask server itself.
Flask-specific code patterns that enable DNS cache poisoning exploitation include:
- Using
requestsorurllibto call external APIs based on user-controlled input without proper validation:
# Vulnerable: user input directly used in external request domain
@app.route('/fetch-image')
def fetch_image():
image_url = request.args.get('url', '') # e.g., 'http://evil.com/image.jpg'
# If DNS cache for 'evil.com' is poisoned to point to internal IP, SSRF may occur
response = requests.get(image_url) # No validation of hostname
return response.content
- Constructing redirect URLs from user input where the domain is not validated:
# Vulnerable: open redirect via poisoned DNS
@app.route('/redirect')
def redirect_user():
target = request.args.get('target')
# Attacker poisons DNS for 'trusted-site.com' to point to malicious server
# User sees 'trusted-site.com' in link but goes to attacker's IP
return redirect(target, code=302)
- Using service discovery or configuration values that resolve external domains without DNS security controls:
# Vulnerable: hardcoded service name that relies on DNS
import os
PAYMENT_SERVICE_HOST = os.getenv('PAYMENT_SERVICE_HOST', 'payment.internal')
# If attacker poisons DNS for 'payment.internal', requests go to malicious host
payment_url = f'http://{PAYMENT_SERVICE_HOST}/process'
requests.post(payment_url, json=payment_data)
The risk is amplified in Flask apps that:
- Act as proxies or gateways to internal services
- Fetch resources based on user-provided URLs (image fetchers, webhook validators, OEmbed consumers)
- Use service meshes or microservice architectures where inter-service communication relies on DNS
- Lack egress filtering or DNSSEC validation in their deployment environment
Attackers exploit this by poisoning DNS caches via techniques like Kaminsky-style attacks (predicting transaction IDs) or exploiting misconfigured resolvers. Once poisoned, any Flask app making outbound requests to the compromised domain will unknowingly communicate with attacker-controlled infrastructure, potentially leading to SSRF, data exfiltration, or credential theft.
Flask-Specific Detection
Detecting DNS cache poisoning risk in Flask applications requires identifying patterns where domain names used in outbound connections are influenced by user input or external configuration without adequate validation. Since DNS cache poisoning is an infrastructure-level attack, middleBrick does not detect the poisoning event itself but identifies Flask applications that are vulnerable to exploitation if DNS cache poisoning occurs.
middleBrick’s black-box scanning approach tests the unauthenticated attack surface by analyzing how the Flask endpoint handles input that could be used to construct malicious outbound requests. It looks for:
- Endpoints that accept URL or domain parameters and make HTTP requests to them
- Lack of hostname validation, allowlisting, or schema enforcement
- Potential for SSRF that could be leveraged via poisoned DNS
For example, when scanning a Flask endpoint like:
@app.route('/webhook') def incoming_webhook(): callback_url = request.json.get('callback_url') # No validation - if DNS for domain in callback_url is poisoned, SSRF occurs requests.post(callback_url, json={'status': 'received'}) return '', 202middleBrick will:
- Identify the endpoint accepts user-controlled input (
callback_url)- Detect that this input is used in an
outbound HTTP request- Flag missing validation (no check for allowed domains, schemas, or IP ranges)
- Correlate this with the Input Validation and SSRF security checks in its 12-check suite
- Provide a finding with severity based on exploitability and potential impact (e.g., access to internal metadata services)
The scan does not require agents, configuration, or credentials — only the public URL of the Flask application. It takes 5–15 seconds and returns a risk score (A–F) with detailed findings. For DNS cache poisoning exposure, middleBrick will highlight:
- Missing URL schema validation (allowing
http://orhttps://only) - Absence of hostname allowlisting or blocklisting (e.g., rejecting
localhost, private IP ranges) - No use of
urlparseto extract and validatenetlocbefore making requests
This enables developers to understand that their Flask application is susceptible to DNS cache poisoning–based SSRF and implement controls at the application layer, independent of network-level DNS security.
Flask-Specific Remediation
Flask applications can mitigate DNS cache poisoning risks by implementing strict input validation and outgoing request controls at the application layer. Since Flask does not include built-in SSRF protection, developers must use Python’s standard library or trusted libraries to validate and sanitize any user-influenced domain names before making outbound HTTP requests.
The following code examples demonstrate Flask-specific fixes using native Python features and the requests library.
1. Validate and sanitize user-provided URLs using urllib.parse
from flask import Flask, request, jsonify
import requests
from urllib.parse import urlparse
app = Flask(__name__)
ALLOWED_SCHEMES = {'http', 'https'}
BLOCKED_HOSTS = {'localhost', '127.0.0.1', '0.0.0.0', '[::1]'}
BLOCKED_IP_RANGES = [
('10.0.0.0', '10.255.255.255'),
('172.16.0.0', '172.31.255.255'),
('192.168.0.0', '192.168.255.255'),
('169.254.0.0', '169.254.255.255') # link-local
]
def is_safe_url(url):
try:
parsed = urlparse(url)
# Validate scheme
if parsed.scheme not in ALLOWED_SCHEMES:
return False
# Validate hostname exists
if not parsed.hostname:
return False
# Block explicit hostnames
if parsed.hostname.lower() in BLOCKED_HOSTS:
return False
# Block IP addresses in private/ranges
if parsed.hostname.replace('[', '').replace(']', ''):
# Simple IP check (for production, use ipaddress module)
ip = parsed.hostname
if ip.startswith('10.') or ip.startswith('192.168.') or \
ip.startswith('172.16.') and 16 <= int(ip.split('.')[1]) <= 31 or \
ip == '127.0.0.1' or ip == '169.254.169.254':
return False
return True
except Exception:
return False
@app.route('/safe-fetch')
def safe_fetch():
user_url = request.args.get('url')
if not user_url or not is_safe_url(user_url):
return jsonify({'error': 'Invalid or unsafe URL'}), 400
try:
response = requests.get(user_url, timeout=5)
return response.content, response.status_code, dict(response.headers)
except requests.RequestException as e:
return jsonify({'error': 'Request failed'}), 502
2. Use the ipaddress module for robust IP validation (Python 3.3+)
import ipaddress
from urllib.parse import urlparse
def is_safe_hostname(hostname):
if not hostname:
return False
try:
# Check if it's an IP address
ip = ipaddress.ip_address(hostname)
# Block private, loopback, link-local, reserved
if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved:
return False
except ValueError:
# Not an IP - assume it's a hostname; additional validation (e.g., allowlist) may apply
pass
return True
# In your Flask route:
parsed = urlparse(user_url)
if not is_safe_hostname(parsed.hostname):
return jsonify({'error': 'Hostname not allowed'}), 400
3. Implement an allowlist of trusted external services
For applications that call known third-party APIs, maintain an allowlist of approved domains:
ALLOWED_DOMAINS = {
'api.paymentprovider.com',
'cdn.imageservice.net',
'hooks.slack.com'
}
def is_allowed_domain(hostname):
if not hostname:
return False
hostname = hostname.lower().rstrip('.')
return hostname in ALLOWED_DOMAINS
# Usage in Flask view:
parsed = urlparse(callback_url)
if not is_allowed_domain(parsed.hostname):
return jsonify({'error': 'Domain not in allowlist'}), 400
# Proceed with request
requests.post(callback_url, json=payload)
4. Use session-level configuration in requests to enforce defaults
While not a validation substitute, configuring timeouts and limiting redirects reduces attack surface:
session = requests.Session()
session.max_redirects = 2
# Use in Flask app context or as a global with thread safety
response = session.get(url, timeout=(3, 10)) # (connect, read) timeout
These controls ensure that even if DNS cache poisoning occurs, the Flask application will not resolve or connect to unintended internal or malicious endpoints. Remediation focuses on validating user input, restricting outbound destinations, and using safe defaults — all achievable with Flask-compatible Python libraries without requiring agents or infrastructure changes.