Flask URL Host Injection Vulnerability

High Risk Injection
FlaskPythonHost InjectionURL ManipulationSSRFOpen RedirectWeb Security

What it is

Application accepts user-controlled input to construct URL hosts without proper validation or sanitization, enabling potential Host Header Injection attacks, open redirects, or SSRF vulnerabilities.

from flask import Flask, request, redirect @app.route('/redirect') def handle_redirect(): # Vulnerable: User can control the host host = request.args.get('host') target_url = f'https://{host}/api/data' return redirect(target_url) @app.route('/proxy') def proxy_request(): # Vulnerable: Host header directly used host = request.headers.get('Host') api_url = f'http://{host}/internal/api' return requests.get(api_url).text
from flask import Flask, request, redirect from urllib.parse import urlparse # Allowlist of permitted hosts ALLOWED_HOSTS = ['api.example.com', 'secure.example.com'] @app.route('/redirect') def handle_redirect(): # Secure: Validate host against allowlist host = request.args.get('host') if host not in ALLOWED_HOSTS: return 'Invalid host', 400 target_url = f'https://{host}/api/data' return redirect(target_url) @app.route('/proxy') def proxy_request(): # Secure: Use configured host instead of user input api_host = 'internal-api.local' # Fixed internal host api_url = f'http://{api_host}/internal/api' return requests.get(api_url).text

💡 Why This Fix Works

See fix suggestions for detailed explanation.

Why it happens

Flask views directly access Host header: host = request.host or request.headers.get('Host'). Attackers manipulate Host header in HTTP requests. Used in URL construction for redirects or emails, enabling cache poisoning, password reset poisoning, SSRF, or web cache deception attacks.

Root causes

Using request.host or request.headers['Host'] Without Validation

Flask views directly access Host header: host = request.host or request.headers.get('Host'). Attackers manipulate Host header in HTTP requests. Used in URL construction for redirects or emails, enabling cache poisoning, password reset poisoning, SSRF, or web cache deception attacks.

Constructing URLs with request.host_url or request.url_root

Views build URLs using Flask's request.host_url or request.url_root: reset_link = request.host_url + 'reset/' + token. These derive from Host header. Attacker-controlled domain in password reset emails sends tokens to malicious sites. Affects URL generation in templates and API responses.

Not Configuring SERVER_NAME in Flask Application

Missing SERVER_NAME configuration allows Flask to trust Host header: app.config['SERVER_NAME'] not set. Without SERVER_NAME, request.host returns client-supplied header value. Application accepts any Host value, enabling host header injection for all URL generation and redirect operations.

Using X-Forwarded-Host Header Without Validation Behind Proxies

Behind proxies, applications trust X-Forwarded-Host: host = request.headers.get('X-Forwarded-Host', request.host). If proxy misconfigured or absent, attackers set X-Forwarded-Host directly. ProxyFix middleware without proper num_proxies configuration accepts forged headers, enabling injection through multiple header vectors.

Building Redirects with Unvalidated Host Headers

Redirect logic uses Host header: return redirect(f'http://{request.host}/dashboard'). Attackers control destination domain through Host manipulation. Open redirect vulnerability combined with host injection enables phishing. After_request handlers constructing Location headers inherit same vulnerability through request.host usage.

Fixes

1

Configure SERVER_NAME in Flask Application Settings

Set explicit SERVER_NAME: app.config['SERVER_NAME'] = 'example.com:443'. Flask validates Host header against SERVER_NAME, rejecting mismatches. Use environment-specific configuration: os.environ.get('SERVER_NAME', 'localhost:5000'). Makes request.host return trusted configured value instead of header value.

2

Validate Host Header Against Allowlist of Permitted Domains

Implement before_request validation: ALLOWED_HOSTS = ['example.com', 'www.example.com']; if request.host not in ALLOWED_HOSTS: abort(400). Check both request.host and X-Forwarded-Host if behind proxy. Reject requests with invalid hosts early in request lifecycle before processing.

3

Use url_for() with _external=True Instead of request.host

Generate URLs with url_for('endpoint', _external=True) which uses SERVER_NAME: reset_link = url_for('reset_password', token=token, _external=True). Flask constructs URL from configuration, not headers. Ensures consistent trusted domain in generated URLs, emails, and redirects.

4

Configure ProxyFix Middleware with Correct num_proxies Parameter

If behind proxy, use ProxyFix properly: from werkzeug.middleware.proxy_fix import ProxyFix; app.wsgi_app = ProxyFix(app.wsgi_app, x_host=1, x_for=1). Set num_proxies or x_host to number of trusted proxies. Prevents accepting forged X-Forwarded headers from clients.

5

Use Relative URLs Instead of Absolute URLs Where Possible

For redirects and links, prefer relative paths: return redirect('/dashboard') instead of building absolute URLs with host. Browser handles domain resolution. For external URLs, use hardcoded trusted domain: SITE_URL = 'https://example.com'; return redirect(f'{SITE_URL}/dashboard').

6

Implement Middleware to Normalize and Validate Host Header

Create middleware validating Host header: @app.before_request validate_host(). Check format, charset, and domain. Set request.environ['HTTP_HOST'] to trusted value. Log suspicious Host headers for monitoring. Combine with rate limiting on invalid hosts to detect scanning attempts.

Detect This Vulnerability in Your Code

Sourcery automatically identifies flask url host injection vulnerability and many other security issues in your codebase.