Skip to content

[Data] Fix SSRF vulnerability in DatabricksUCDatasource external URL … - #65798

Open
hoamgh wants to merge 2 commits into
ray-project:masterfrom
hoamgh:fix/ssrf-databricks-uc
Open

[Data] Fix SSRF vulnerability in DatabricksUCDatasource external URL …#65798
hoamgh wants to merge 2 commits into
ray-project:masterfrom
hoamgh:fix/ssrf-databricks-uc

Conversation

@hoamgh

@hoamgh hoamgh commented Aug 30, 2026

Copy link
Copy Markdown

Fix SSRF vulnerability in DatabricksUCDatasource (fixes #65669).

databricks_uc_datasource.py unconditionally calls requests.get(external_url)
with a URL from the Databricks API response JSON, without any SSRF validation.
A compromised or MITM'd server could redirect cluster nodes to internal services
(e.g., cloud metadata at 169.254.169.254) for data exfiltration.

Changes:

  • Add _validate_external_url() that enforces HTTPS-only and blocks
    private/loopback/link-local/reserved IPs via DNS resolution
  • Add 9 unit tests covering scheme validation, IP blocking, DNS failure,
    and end-to-end SSRF prevention

Related issue number

Fixes #65669

Checks

  • I've signed all my commits
  • I've run scripts/format.sh to lint the changes in this PR
  • I've included any doc changes needed for https://docs.ray.io/en/master/
  • I've added any new APIs to the API Reference
  • I've made sure the tests are passing

@hoamgh
hoamgh requested a review from a team as a code owner August 30, 2026 15:48

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces SSRF validation for external URLs in the Databricks UC datasource by verifying that URLs use HTTPS and do not resolve to private, loopback, link-local, or reserved IP addresses. Comprehensive unit tests are also added to cover these validation scenarios. The review feedback highlights a critical security vulnerability where the validation can be bypassed via HTTP redirects, as requests.get follows redirects by default. It is recommended to disable automatic redirects by setting allow_redirects=False and explicitly raising an error for 3xx status codes.

Comment on lines +238 to 241
raw_response = requests.get(
external_url, auth=None, headers=None
)
raw_response.raise_for_status()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

By default, requests.get automatically follows HTTP redirects (3xx status codes). If a compromised or malicious Databricks server returns an external URL that redirects to an internal/private IP address (e.g., http://169.254.169.254), the initial _validate_external_url check will be bypassed because it only validates the initial URL. This leads to a redirect-based SSRF vulnerability.

To prevent this, we should disable automatic redirects by setting allow_redirects=False and explicitly raise an error if a 3xx redirect status code is returned.

Additionally, note that there is a potential Time-of-Check to Time-of-Use (TOCTOU) DNS Rebinding vulnerability because the DNS resolution in _validate_external_url is separate from the DNS resolution performed by requests.get. A malicious DNS server could return a public IP during validation and a private IP during the actual request. While resolving DNS rebinding completely in Python requests requires a custom transport adapter or DNS resolver, disabling redirects is a critical first step that mitigates the most common redirect-based SSRF vectors.

Suggested change
raw_response = requests.get(
external_url, auth=None, headers=None
)
raw_response.raise_for_status()
raw_response = requests.get(
external_url, auth=None, headers=None, allow_redirects=False
)
if 300 <= raw_response.status_code < 400:
raise ValueError(
f"Redirection is not allowed for Databricks external URLs "
f"to prevent SSRF attacks, got status: {raw_response.status_code}"
)
raw_response.raise_for_status()

Comment thread python/ray/data/_internal/datasource/databricks_uc_datasource.py
Comment thread python/ray/data/_internal/datasource/databricks_uc_datasource.py
@hoamgh
hoamgh force-pushed the fix/ssrf-databricks-uc branch from 637ec83 to 51decd7 Compare August 30, 2026 16:15
Comment thread python/ray/data/_internal/datasource/databricks_uc_datasource.py
@hoamgh
hoamgh force-pushed the fix/ssrf-databricks-uc branch 2 times, most recently from 8590809 to 01d8acb Compare August 30, 2026 16:43
Comment thread python/ray/data/_internal/datasource/databricks_uc_datasource.py
@ray-gardener ray-gardener Bot added data Ray Data-related issues community-contribution Contributed by the community labels Aug 30, 2026
@hoamgh
hoamgh force-pushed the fix/ssrf-databricks-uc branch 3 times, most recently from 93923ba to 1eafca4 Compare August 31, 2026 04:16
Comment thread python/ray/data/_internal/datasource/databricks_uc_datasource.py
Comment thread python/ray/data/tests/datasource/test_databricks_uc_datasource.py
@hoamgh
hoamgh force-pushed the fix/ssrf-databricks-uc branch from 1eafca4 to d99c0f0 Compare August 31, 2026 04:30
Comment thread python/ray/data/_internal/datasource/databricks_uc_datasource.py
auth=None,
headers=None,
allow_redirects=False,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DNS rebinding bypasses URL validation

Medium Severity

Hostname resolution in _validate_external_url is not bound to the later requests.get. A compromised Databricks response can return an attacker-controlled host that answers with a public IP during validation and a private or link-local IP when the fetch runs, bypassing the SSRF checks.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d99c0f0. Configure here.

Comment thread python/ray/data/tests/datasource/test_databricks_uc_datasource.py
@hoamgh
hoamgh force-pushed the fix/ssrf-databricks-uc branch 4 times, most recently from 99b8d29 to 1a57218 Compare August 31, 2026 05:38
Comment thread python/ray/data/_internal/datasource/databricks_uc_datasource.py
@hoamgh
hoamgh force-pushed the fix/ssrf-databricks-uc branch from 3d38014 to e7843c1 Compare September 1, 2026 06:17
Comment thread python/ray/data/_internal/datasource/databricks_uc_datasource.py
@hoamgh
hoamgh force-pushed the fix/ssrf-databricks-uc branch from 14357c5 to d2b179a Compare September 1, 2026 06:32
Comment thread python/ray/data/_internal/datasource/databricks_uc_datasource.py
@hoamgh
hoamgh force-pushed the fix/ssrf-databricks-uc branch from f29a0da to de1f5a4 Compare September 1, 2026 06:52
…fetching

_validate_external_url() is added to prevent Server-Side Request Forgery
(SSRF) attacks when fetching data from Databricks external links.
The function validates URLs before fetching by:
- Rejecting non-HTTPS schemes.
- Rejecting URLs containing basic authentication ('@' in netloc) to
  prevent parser mismatch vulnerabilities (urllib.parse vs urllib3).
- Resolving hostnames and blocking loopback, link-local, reserved,
  and multicast IP addresses.
- Allowing RFC1918 private IPs and CGNAT to naturally fall through
  to support VPC PrivateLink endpoints per bug review.
- Enforcing allow_redirects=False and raising a ValueError if
  a redirect response (3xx) is returned from Databricks.

Fixes ray-project#65669

Signed-off-by: hoamgh <88762703+hoamgh@users.noreply.github.com>
@hoamgh
hoamgh force-pushed the fix/ssrf-databricks-uc branch from de1f5a4 to 2ddfbdb Compare September 1, 2026 06:57

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 9115c1a. Configure here.

f"IP address {ip}, which is blocked to prevent "
f"SSRF attacks. If you are using Databricks VPC "
f"PrivateLink, set RAY_DATABRICKS_ALLOW_PRIVATE_IPS=1."
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SSRF filter misses CGNAT address range

Medium Severity

_validate_external_url treats an address as safe unless it is loopback, link-local, reserved, multicast, unspecified, or is_private. In Python, 100.64.0.0/10 is neither is_private nor is_global, so CGNAT and similar internal addresses pass, including well-known cloud metadata such as 100.100.100.200. A compromised Databricks response can still send workers at those hosts.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9115c1a. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-contribution Contributed by the community data Ray Data-related issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SSRF: databricks_uc_datasource.py unconditionally fetches external_url from server response

1 participant