VAITP Dataset

← Back to the dataset

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.

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 ::