VYPR
High severityNVD Advisory· Published Oct 24, 2023· Updated Sep 11, 2024

Server-Side Request Forgery Vulnerability in Custom Integration Upload

CVE-2023-46124

Description

Fides is an open-source privacy engineering platform for managing the fulfillment of data privacy requests in runtime environments, and the enforcement of privacy regulations in code. The Fides web application allows a custom integration to be uploaded as a ZIP file containing configuration and dataset definitions in YAML format. It was discovered that specially crafted YAML dataset and config files allow a malicious user to perform arbitrary requests to internal systems and exfiltrate data outside the environment (also known as a Server-Side Request Forgery). The application does not perform proper validation to block attempts to connect to internal (including localhost) resources. The vulnerability has been patched in Fides version 2.22.1.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
ethyca-fidesPyPI
< 2.22.12.22.1

Affected products

1

Patches

1
cd344d016b14

Merge pull request from GHSA-jq3w-9mgf-43m4

https://github.com/ethyca/fidesThomasOct 22, 2023via ghsa
5 files changed · +76 5
  • CHANGELOG.md+21 0 modified
    @@ -17,6 +17,27 @@ The types of changes are:
     
     ## [Unreleased](https://github.com/ethyca/fides/compare/2.22.0...main)
     
    +### Added
    +- Added support for 3 additional config variables in Fides.js: fidesEmbed, fidesDisableSaveApi, and fidesTcString [#4262](https://github.com/ethyca/fides/pull/4262)
    +- Added support for fidesEmbed, fidesDisableSaveApi, and fidesTcString to be passed into Fides.js via query param, cookie, or window object [#4297](https://github.com/ethyca/fides/pull/4297)
    +
    +### Fixed
    +- Cleans up CSS for fidesEmbed mode [#4306](https://github.com/ethyca/fides/pull/4306)
    +
    +### Added
    +- Added a `FidesPreferenceToggled` event to Fides.js to track when user preferences change without being saved [#4253](https://github.com/ethyca/fides/pull/4253)
    +- Add AC Systems to the TCF Overlay under Vendor Consents section [#4266](https://github.com/ethyca/fides/pull/4266/)
    +
    +### Changed
    +- Derive cookie storage info, privacy policy and legitimate interest disclosure URLs, and data retention data from the data map instead of directly from gvl.json [#4286](https://github.com/ethyca/fides/pull/4286)
    +
    +### Fixed
    +- Stacks that do not have any purposes will no longer render an empty purpose block [#4278](https://github.com/ethyca/fides/pull/4278)
    +- Forcing hidden sections to use display none [#4299](https://github.com/ethyca/fides/pull/4299)
    +
    +### Security
    +- Added hostname checks for external SaaS connector URLs [CVE](https://github.com/ethyca/fides/security/advisories/GHSA-jq3w-9mgf-43m4)
    +
     ## [2.22.0](https://github.com/ethyca/fides/compare/2.21.0...2.22.0)
     
     ### Added
    
  • src/fides/api/service/connectors/saas/authenticated_client.py+9 1 modified
    @@ -6,6 +6,7 @@
     from functools import wraps
     from time import sleep
     from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union
    +from urllib.parse import urlparse
     
     from loguru import logger
     from requests import PreparedRequest, Request, Response, Session
    @@ -20,6 +21,7 @@
         RateLimiterPeriod,
         RateLimiterRequest,
     )
    +from fides.api.util.saas_util import deny_unsafe_hosts
     from fides.config import CONFIG
     
     if TYPE_CHECKING:
    @@ -195,7 +197,7 @@ def send(
             Builds and executes an authenticated request.
             Optionally ignores:
               - all non-2xx/3xx responses if ignore_errors is set to True
    -          - no non-2xx/3xx repsones if ignore_errors is set to False
    +          - no non-2xx/3xx responses if ignore_errors is set to False
               - specific non-2xx/3xx responses if ignore_errors is set to a list of status codes
             """
             rate_limit_requests = self.build_rate_limit_requests()
    @@ -204,6 +206,12 @@ def send(
             prepared_request: PreparedRequest = self.get_authenticated_request(
                 request_params
             )
    +        if not prepared_request.url:
    +            raise ValueError("The URL for the prepared request is missing.")
    +
    +        # extract the hostname from the complete URL and verify its safety
    +        deny_unsafe_hosts(urlparse(prepared_request.url).netloc)
    +
             response = self.session.send(prepared_request)
     
             log_request_and_response_for_debugging(
    
  • src/fides/api/util/saas_util.py+25 1 modified
    @@ -2,8 +2,10 @@
     
     import json
     import re
    +import socket
     from collections import defaultdict, deque
    -from typing import Any, Dict, List, Optional, Set, Tuple
    +from ipaddress import IPv4Address, IPv6Address, ip_address
    +from typing import Any, Dict, List, Optional, Set, Tuple, Union
     
     import pydash
     import yaml
    @@ -15,6 +17,7 @@
     from fides.api.models.privacy_request import PrivacyRequest
     from fides.api.schemas.saas.saas_config import SaaSRequest
     from fides.api.schemas.saas.shared_schemas import SaaSRequestParams
    +from fides.config import CONFIG
     from fides.config.helpers import load_file
     
     FIDESOPS_GROUPED_INPUTS = "fidesops_grouped_inputs"
    @@ -24,6 +27,27 @@
     CUSTOM_PRIVACY_REQUEST_FIELDS = "custom_privacy_request_fields"
     
     
    +def deny_unsafe_hosts(host: str) -> str:
    +    """
    +    Verify that the provided host isn't a potentially unsafe one.
    +
    +    WARNING: IPv6 is _not_ supported and will throw an exception!
    +    """
    +    if CONFIG.dev_mode:
    +        return host
    +
    +    try:
    +        host_ip: Union[IPv4Address, IPv6Address] = ip_address(
    +            socket.gethostbyname(host)
    +        )
    +    except socket.gaierror:
    +        raise ValueError(f"Failed to resolve hostname: {host}")
    +
    +    if host_ip.is_link_local or host_ip.is_loopback:
    +        raise ValueError(f"Host '{host}' with IP Address '{host_ip}' is not safe!")
    +    return host
    +
    +
     def load_yaml_as_string(filename: str) -> str:
         yaml_file = load_file([filename])
         with open(yaml_file, "r", encoding="utf-8") as file:
    
  • tests/ops/models/test_saas_config.py+0 0 renamed
  • tests/ops/service/connectors/saas/test_authenticated_client.py+21 3 modified
    @@ -40,7 +40,7 @@ def test_connection_config(test_saas_config) -> ConnectionConfig:
     def test_saas_request() -> SaaSRequestParams:
         return SaaSRequestParams(
             method=HTTPMethod.GET,
    -        path="test_path",
    +        path="/test_path",
             query_params={},
         )
     
    @@ -55,22 +55,40 @@ def test_authenticated_client(
         test_connection_config, test_client_config
     ) -> AuthenticatedClient:
         return AuthenticatedClient(
    -        "https://test_uri", test_connection_config, test_client_config
    +        "https://ethyca.com", test_connection_config, test_client_config
         )
     
     
     @pytest.mark.unit_saas
     class TestAuthenticatedClient:
         @mock.patch.object(Session, "send")
         def test_client_returns_ok_response(
    -        self, send, test_authenticated_client, test_saas_request
    +        self,
    +        send,
    +        test_authenticated_client,
    +        test_saas_request,
    +        test_config_dev_mode_disabled,
         ):
             test_response = Response()
             test_response.status_code = 200
             send.return_value = test_response
             returned_response = test_authenticated_client.send(test_saas_request)
             assert returned_response == test_response
     
    +    @pytest.mark.parametrize(
    +        "ip_address", ["localhost", "127.0.0.1", "169.254.0.1", "169.254.169.254"]
    +    )
    +    def test_client_denied_url(
    +        self,
    +        test_authenticated_client: AuthenticatedClient,
    +        test_saas_request,
    +        test_config_dev_mode_disabled,
    +        ip_address,
    +    ):
    +        test_authenticated_client.uri = f"https://{ip_address}"
    +        with pytest.raises(ConnectionException):
    +            test_authenticated_client.send(test_saas_request)
    +
         @mock.patch.object(Session, "send")
         def test_client_retries_429_and_throws(
             self, send, test_authenticated_client, test_saas_request
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.