CVE-2026-45568
SSRF in zrok Python SDK proxy allows requests to attacker-chosen URLs.
- CVSS 9.9
- CWE-22
- Input Validation and Sanitization
- Remote
zrok is software for sharing web services, files, and network resources. Prior to 2.0.3, zrok's Python SDK ProxyShare Flask proxy route accepts an absolute URL in the request path and passes it to urllib.parse.urljoin, allowing the requested path to replace the configured target host and causing requests.request to return a server-side response from an attacker-chosen URL. This issue is fixed in version 2.0.3.
- CWE
- CWE-22
- CVSS base score
- 9.9
- Published
- 2026-07-16
- OWASP
- A10 Server-Side Request Forgery (SSRF)
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Input Validation and Sanitization
- Subcategory
- Server-Side Request Forgery (SSRF)
- Accessibility scope
- Remote
- Impact
- Information Disclosure
- Affected component
- zrok
- Fixed by upgrading
- Yes
Solution
Upgrade zrok to version 2.0.3 or later.
Vulnerable code sample
import requests
from flask import Flask, Response, request
from urllib.parse import urljoin
# This represents the intended configuration of the proxy.
# The proxy is supposed to forward all requests to this backend service.
TARGET_HOST = "http://internal-service.local:8080/"
app = Flask(__name__)
@app.route('/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])
def proxy(path):
"""
This proxy route is vulnerable. It accepts an arbitrary path and joins it
with the TARGET_HOST. However, urljoin's behavior with an absolute URL
in the second argument creates an SSRF vulnerability.
Example of intended use:
GET /api/users -> requests.get("http://internal-service.local:8080/api/users")
Example of exploit:
GET /http://attacker.com/data -> requests.get("http://attacker.com/data")
The TARGET_HOST is completely ignored.
"""
# VULNERABLE LINE: urljoin will discard the base (TARGET_HOST) if the path
# is an absolute URL (e.g., starts with http:// or https://).
final_url = urljoin(TARGET_HOST, path)
# The server then makes a request to the attacker-controlled final_url.
try:
# Forward the original request's method, headers, and data.
resp = requests.request(
method=request.method,
url=final_url,
headers={key: value for (key, value) in request.headers if key != 'Host'},
data=request.get_data(),
cookies=request.cookies,
allow_redirects=False,
stream=True
)
# Stream the response from the target back to the client.
return Response(
resp.iter_content(chunk_size=1024),
status=resp.status_code,
headers=dict(resp.headers)
)
except requests.exceptions.RequestException as e:
return Response(f"Error proxying request: {e}", status=502)
if __name__ == '__main__':
# To test:
# 1. Run this script.
# 2. In another terminal, run a simple web server, e.g., `python3 -m http.server 9090`
# 3. Make a request to the proxy: `curl http://127.0.0.1:5000/http://127.0.0.1:9090/`
# You will see the request logged by the simple web server on port 9090,
# demonstrating that the proxy made a request to an arbitrary host.
app.run(port=5000, debug=True)Patched code sample
import urllib.parse
from flask import Flask, request, abort
import requests
# This is a representative Flask application demonstrating the fix.
# The vulnerability existed in the zrok Python SDK's proxy logic.
app = Flask(__name__)
# The intended, trusted internal service for the proxy.
TARGET_HOST = "http://internal-service.local:8080/"
@app.route('/proxy/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE'])
def proxy(path):
"""
A proxy route that forwards requests to a configured TARGET_HOST.
This function includes a fix to prevent an SSRF vulnerability.
The vulnerable version would pass the `path` directly to urljoin:
# VULNERABLE CODE:
# target_url = urllib.parse.urljoin(TARGET_HOST, path)
# If path was "http://attacker.com", target_url would become "http://attacker.com",
# ignoring TARGET_HOST completely.
"""
# --- FIX FOR CVE-2026-45568 ---
# 1. Parse the incoming path to inspect its structure.
parsed_path = urllib.parse.urlparse(path)
# 2. Check if the path contains a scheme (e.g., "http:") or a
# network location (e.g., "//attacker.com"). The presence of either
# indicates an attempt to specify an absolute URL.
if parsed_path.scheme or parsed_path.netloc:
# 3. If an absolute URL is detected, reject the request immediately.
abort(400, "Absolute URLs are not permitted in the request path.")
# --- END OF FIX ---
# The path is now considered safe to be joined with the base URL.
# lstrip('/') is an added precaution against path traversal issues within urljoin.
target_url = urllib.parse.urljoin(TARGET_HOST, path.lstrip('/'))
# The request is now only ever sent to the configured TARGET_HOST.
try:
resp = requests.request(
method=request.method,
url=target_url,
headers={key: value for (key, value) in request.headers if key.lower() != 'host'},
data=request.get_data(),
allow_redirects=False,
timeout=5
)
return (resp.content, resp.status_code, resp.headers.items())
except requests.exceptions.RequestException as e:
return (f"Error proxying request: {e}", 502)Payload
http://169.254.169.254/latest/meta-data/
Cite this entry
@misc{vaitp:cve202645568,
title = {{SSRF in zrok Python SDK proxy allows requests to attacker-chosen URLs.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-45568},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-45568/}}
}
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 ::
