rok Python ProxyShare can be used as an SSRF proxy through absolute URL paths
Description
Summary
Alice exposes a Python SDK ProxyShare with a fixed target URL. Bob sends a request to the share with an absolute URL in the path. The Flask handler passes that path to urllib.parse.urljoin, which replaces Alice's configured target host with Bob's host and returns the server-side response to Bob.
Details
The Python SDK proxy route accepts every path under the share:
@app.route('/', defaults={'path': ''}, methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])
@app.route('/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])
def proxy(path):
It constructs the outbound URL with urljoin and then sends the request:
url = urllib.parse.urljoin(self.target, path)
resp = requests.request(
method=request.method,
url=url,
headers={key: value for (key, value) in request.headers
if key.lower() not in HOP_BY_HOP_HEADERS},
data=request.get_data(),
cookies=request.cookies,
allow_redirects=False,
stream=True,
verify=self.verify_ssl
)
When path is `http://127.0.0.1:19190/metadata`, urljoin(self.target, path) returns `http://127.0.0.1:19190/metadata`. The proxy sends the request to Bob's chosen URL rather than Alice's target.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Python SDK ProxyShare in zrok uses urllib.parse.urljoin, allowing an attacker to redirect the proxy to an arbitrary host via an absolute URL in the request path.
Vulnerability
The zrok Python SDK ProxyShare class registers a Flask route that accepts any path under the share [1][2][3]. The handler calls urllib.parse.urljoin(self.target, path) to build the outbound URL. When the attacker supplies an absolute URL in the path parameter (e.g., http://127.0.0.1:19190/metadata), urljoin replaces the configured target host with the attacker-supplied host. This allows the proxy to be redirected to an arbitrary destination chosen by the attacker, rather than forwarding traffic only to the intended private service.
Exploitation
An attacker who can send HTTP requests to the share (typically any remote user who knows the share URL) simply includes an absolute URL in the path portion of the request, for example https://public-share.example.com/http://internal-service:8080/secret. No authentication is required beyond what the share itself provides; the default configuration allows any valid HTTP request method (GET, POST, PUT, DELETE, PATCH, OPTIONS). The Flask route captures the entire path and passes it directly to urljoin, so the attacker does not need any special network position beyond being able to reach the public share endpoint.
Impact
An attacker can force the proxy to send requests to any arbitrary host reachable from the zrok proxy server. This enables server-side request forgery (SSRF) against internal services, cloud metadata endpoints (e.g., http://169.254.169.254/), or other systems on the proxy's network. The attacker receives the server's response, potentially leaking sensitive information such as cloud provider credentials, internal service configurations, or secrets from other endpoints that trust the proxy's IP address.
Mitigation
A fix is available in the zrok repository and advisory [1][2][3]. The recommended resolution is to validate that the path parameter does not contain an absolute URL by checking for a scheme before calling urljoin, or by using a safe URL construction method that does not interpret absolute paths as full URLs. Users should update their zrok Python SDK to the patched version as soon as it is released. Until then, no workaround is provided in the available references, so administrators should restrict access to ProxyShare endpoints to trusted clients only.
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1Patches
170eb2fe0eb63announce fix
1 file changed · +4 −0
CHANGELOG.md+4 −0 modified@@ -1,5 +1,9 @@ # CHANGELOG +## v0.4.48 + +FIX: the Python SDK erroneously assumed the enabled zrok environment contained a config.json file, and was changed to only load it if the file was present (https://github.com/openziti/zrok/pull/853/). + ## v0.4.47 CHANGE: the Docker instance will wait for the ziti container healthy status (contribution from Ben Wong @bwong365 - https://github.com/openziti/zrok/pull/790)
Vulnerability mechanics
Synthesis attempt was rejected by the grounding validator. Re-run pending.
References
2News mentions
0No linked articles in our index yet.